diff --git a/config/pkg.json b/config/pkg.json index 216640fd..74de6fd3 100644 --- a/config/pkg.json +++ b/config/pkg.json @@ -34,5 +34,13 @@ "extract-files": { "upx": "{pkg_root_path}/bin/upx" } + }, + "upx-x86_64-win": { + "type": "ghrel", + "repo": "upx/upx", + "match": "upx.+-win64\\.zip", + "extract-files": { + "upx-*-win64/upx.exe": "{pkg_root_path}/bin/upx.exe" + } } } diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 2fe93534..5fab99fc 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -169,6 +169,21 @@ class LinuxBuilder extends UnixBuilderBase $enableMicro = ($build_target & BUILD_TARGET_MICRO) === BUILD_TARGET_MICRO; $enableEmbed = ($build_target & BUILD_TARGET_EMBED) === BUILD_TARGET_EMBED; + // upx pack and strip for micro + if ($this->getOption('with-upx-pack', false)) { + FileSystem::replaceFileRegex( + SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', + '/POST_MICRO_BUILD_COMMANDS=.*/', + 'POST_MICRO_BUILD_COMMANDS=\$(STRIP) \$(MICRO_STRIP_FLAGS) \$(SAPI_MICRO_PATH) && ' . $this->getOption('upx-exec') . ' --best \$(SAPI_MICRO_PATH)', + ); + } elseif (!$this->getOption('no-strip', false)) { + FileSystem::replaceFileRegex( + SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag', + '/POST_MICRO_BUILD_COMMANDS=.*/', + 'POST_MICRO_BUILD_COMMANDS=true', + ); + } + shell()->cd(SOURCE_PATH . '/php-src') ->exec( "{$this->getOption('ld_library_path')} " . @@ -238,6 +253,10 @@ class LinuxBuilder extends UnixBuilderBase if (!$this->getOption('no-strip', false)) { shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-all php'); + } elseif ($this->getOption('with-upx-pack')) { + shell()->cd(SOURCE_PATH . '/php-src/sapi/cli') + ->exec('strip --strip-all php') + ->exec($this->getOption('upx-exec') . ' --best php'); } $this->deployBinary(BUILD_TARGET_CLI); @@ -267,10 +286,6 @@ class LinuxBuilder extends UnixBuilderBase ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("make -j{$this->concurrency} {$vars} micro"); - if (!$this->getOption('no-strip', false)) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-all micro.sfx'); - } - $this->deployBinary(BUILD_TARGET_MICRO); if ($this->phar_patched) { @@ -293,6 +308,10 @@ class LinuxBuilder extends UnixBuilderBase if (!$this->getOption('no-strip', false)) { shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-all php-fpm'); + } elseif ($this->getOption('with-upx-pack')) { + shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm') + ->exec('strip --strip-all php-fpm') + ->exec($this->getOption('upx-exec') . ' --best php-fpm'); } $this->deployBinary(BUILD_TARGET_FPM); diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index 38bfd783..f2f8fcc4 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -72,6 +72,19 @@ class WindowsBuilder extends BuilderBase $zts = $this->zts ? '--enable-zts=yes ' : '--enable-zts=no '; + // with-upx-pack for phpmicro + $makefile = FileSystem::convertPath(SOURCE_PATH . '/php-src/sapi/micro/Makefile.frag.w32'); + if ($this->getOption('with-upx-pack', false)) { + if (!file_exists($makefile . '.originfile')) { + copy($makefile, $makefile . '.originfile'); + FileSystem::replaceFileStr($makefile, '$(MICRO_SFX):', "_MICRO_UPX = {$this->getOption('upx-exec')} --best $(MICRO_SFX)\n$(MICRO_SFX):"); + FileSystem::replaceFileStr($makefile, '@$(_MICRO_MT)', "@$(_MICRO_MT)\n\t@$(_MICRO_UPX)"); + } + } elseif (file_exists($makefile . '.originfile')) { + copy($makefile . '.originfile', $makefile); + unlink($makefile . '.originfile'); + } + cmd()->cd(SOURCE_PATH . '\php-src') ->exec( "{$this->sdk_prefix} configure.bat --task-args \"" . @@ -293,6 +306,12 @@ class WindowsBuilder extends BuilderBase BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx", default => throw new RuntimeException('Deployment does not accept type ' . $type), }; + + // with-upx-pack for cli + if ($this->getOption('with-upx-pack', false) && $type === BUILD_TARGET_CLI) { + cmd()->exec($this->getOption('upx-exec') . ' --best ' . escapeshellarg($src)); + } + logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file'); FileSystem::createDir(BUILD_ROOT_PATH . '\bin'); diff --git a/src/SPC/command/BuildCliCommand.php b/src/SPC/command/BuildCliCommand.php index 1cdfa51e..44d16873 100644 --- a/src/SPC/command/BuildCliCommand.php +++ b/src/SPC/command/BuildCliCommand.php @@ -37,6 +37,8 @@ class BuildCliCommand extends BuildCommand $this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts'); $this->addOption('with-added-patch', 'P', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Inject patch script outside'); $this->addOption('without-micro-ext-test', null, null, 'Disable phpmicro with extension test code'); + + $this->addOption('with-upx-pack', null, null, 'Compress / pack binary using UPX tool (linux/windows only)'); } public function handle(): int @@ -61,6 +63,28 @@ class BuildCliCommand extends BuildCommand if ($rule === BUILD_TARGET_ALL) { logger()->warning('--build-all option makes `--no-strip` always true, be aware!'); } + // Check upx + $suffix = PHP_OS_FAMILY === 'Windows' ? '.exe' : ''; + if ($this->getOption('with-upx-pack')) { + // only available for linux for now + if (!in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) { + logger()->error('UPX is only available on Linux and Windows!'); + return static::FAILURE; + } + // need to install this manually + if (!file_exists(PKG_ROOT_PATH . '/bin/upx' . $suffix)) { + global $argv; + logger()->error('upx does not exist, please install it first:'); + logger()->error(''); + logger()->error("\t" . $argv[0] . ' install-pkg upx'); + logger()->error(''); + return static::FAILURE; + } + // exclusive with no-strip + if ($this->getOption('no-strip')) { + logger()->warning('--with-upx-pack conflicts with --no-strip, --no-strip won\'t work!'); + } + } try { // create builder $builder = BuilderProvider::makeBuilderByInput($this->input); @@ -83,6 +107,10 @@ class BuildCliCommand extends BuildCommand if ($this->input->getOption('disable-opcache-jit')) { $indent_texts['Opcache JIT'] = 'disabled'; } + if ($this->input->getOption('with-upx-pack') && in_array(PHP_OS_FAMILY, ['Linux', 'Windows'])) { + $indent_texts['UPX Pack'] = 'enabled'; + $builder->setOption('upx-exec', FileSystem::convertPath(PKG_ROOT_PATH . '/bin/upx' . $suffix)); + } try { $ver = $builder->getPHPVersion(); $indent_texts['PHP Version'] = $ver; diff --git a/src/SPC/store/PackageManager.php b/src/SPC/store/PackageManager.php index 3eb68977..336bbd66 100644 --- a/src/SPC/store/PackageManager.php +++ b/src/SPC/store/PackageManager.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace SPC\store; +use SPC\exception\FileSystemException; use SPC\exception\WrongUsageException; class PackageManager @@ -38,12 +39,26 @@ class PackageManager // if contains extract-files, we just move this file to destination, and remove extract dir if (is_array($config['extract-files'] ?? null) && is_assoc_array($config['extract-files'])) { + $scandir = FileSystem::scanDirFiles($extract, true, true); foreach ($config['extract-files'] as $file => $target) { $target = FileSystem::convertPath(FileSystem::replacePathVariable($target)); if (!is_dir($dir = dirname($target))) { f_mkdir($dir, 0755, true); } logger()->debug("Moving package [{$pkg_name}] file {$file} to {$target}"); + // match pattern, needs to scan dir + $file = FileSystem::convertPath($file); + $found = false; + foreach ($scandir as $item) { + if (match_pattern($file, $item)) { + $file = $item; + $found = true; + break; + } + } + if ($found === false) { + throw new FileSystemException('Unable to find extract-files item: ' . $file); + } rename(FileSystem::convertPath($extract . '/' . $file), $target); } FileSystem::removeDir($extract); diff --git a/src/globals/functions.php b/src/globals/functions.php index 7b7540b3..85bcfbad 100644 --- a/src/globals/functions.php +++ b/src/globals/functions.php @@ -47,6 +47,13 @@ function arch2gnu(string $arch): string }; } +function match_pattern(string $pattern, string $subject): bool +{ + $pattern = str_replace(['\*', '\\\\.*'], ['.*', '\*'], preg_quote($pattern, '/')); + $pattern = '/^' . $pattern . '$/i'; + return preg_match($pattern, $subject) === 1; +} + function quote(string $str, string $quote = '"'): string { return $quote . $str . $quote;