From 5a6a33303c5242032a3bbe0211786b2e762a3811 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 31 Oct 2025 16:48:21 +0800 Subject: [PATCH 01/22] Export php binary debug symbols for mac, linux and windows --- src/SPC/builder/linux/LinuxBuilder.php | 89 +++++----------------- src/SPC/builder/macos/MacOSBuilder.php | 48 +++++------- src/SPC/builder/unix/UnixBuilderBase.php | 71 +++++++++++++---- src/SPC/builder/windows/WindowsBuilder.php | 78 ++++++++++++------- src/SPC/store/FileSystem.php | 3 + 5 files changed, 151 insertions(+), 138 deletions(-) diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 3995c3a85..5ffa6a4d6 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -5,7 +5,6 @@ namespace SPC\builder\linux; use SPC\builder\unix\UnixBuilderBase; -use SPC\exception\PatchException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; @@ -194,14 +193,6 @@ protected function buildCli(): void SourcePatcher::patchFile('musl_static_readline.patch', SOURCE_PATH . '/php-src', true); } - if (!$this->getOption('no-strip', false)) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/cli')->exec('strip --strip-unneeded php'); - } - if ($this->getOption('with-upx-pack')) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/cli') - ->exec(getenv('UPX_EXEC') . ' --best php'); - } - $this->deployBinary(BUILD_TARGET_CLI); } @@ -213,14 +204,6 @@ protected function buildCgi(): void ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("make {$concurrency} {$vars} cgi"); - if (!$this->getOption('no-strip', false)) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/cgi')->exec('strip --strip-unneeded php-cgi'); - } - if ($this->getOption('with-upx-pack')) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/cgi') - ->exec(getenv('UPX_EXEC') . ' --best php-cgi'); - } - $this->deployBinary(BUILD_TARGET_CGI); } @@ -232,29 +215,29 @@ protected function buildMicro(): void if ($this->getPHPVersionID() < 80000) { throw new WrongUsageException('phpmicro only support PHP >= 8.0!'); } - if ($this->getExt('phar')) { - $this->phar_patched = true; - SourcePatcher::patchMicroPhar($this->getPHPVersionID()); - } - - $enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; - $vars = $this->getMakeExtraVars(); - - // patch fake cli for micro - $vars['EXTRA_CFLAGS'] .= $enable_fake_cli; - $vars = SystemUtil::makeEnvVarString($vars); - $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; + try { + if ($this->getExt('phar')) { + $this->phar_patched = true; + SourcePatcher::patchMicroPhar($this->getPHPVersionID()); + } - shell()->cd(SOURCE_PATH . '/php-src') - ->exec('sed -i "s|//lib|/lib|g" Makefile') - ->exec("make {$concurrency} {$vars} micro"); + $enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; + $vars = $this->getMakeExtraVars(); - $this->processMicroUPX(); + // patch fake cli for micro + $vars['EXTRA_CFLAGS'] .= $enable_fake_cli; + $vars = SystemUtil::makeEnvVarString($vars); + $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; - $this->deployBinary(BUILD_TARGET_MICRO); + shell()->cd(SOURCE_PATH . '/php-src') + ->exec('sed -i "s|//lib|/lib|g" Makefile') + ->exec("make {$concurrency} {$vars} micro"); - if ($this->phar_patched) { - SourcePatcher::unpatchMicroPhar(); + $this->deployBinary(BUILD_TARGET_MICRO); + } finally { + if ($this->phar_patched) { + SourcePatcher::unpatchMicroPhar(); + } } } @@ -269,13 +252,6 @@ protected function buildFpm(): void ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("make {$concurrency} {$vars} fpm"); - if (!$this->getOption('no-strip', false)) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm')->exec('strip --strip-unneeded php-fpm'); - } - if ($this->getOption('with-upx-pack')) { - shell()->cd(SOURCE_PATH . '/php-src/sapi/fpm') - ->exec(getenv('UPX_EXEC') . ' --best php-fpm'); - } $this->deployBinary(BUILD_TARGET_FPM); } @@ -390,31 +366,4 @@ private function getMakeExtraVars(): array 'EXTRA_LDFLAGS_PROGRAM' => "-L{$lib} {$static} -pie", ]); } - - /** - * Strip micro.sfx for Linux. - * The micro.sfx does not support UPX directly, but we can remove UPX-info segment to adapt. - * This will also make micro.sfx with upx-packed more like a malware fore antivirus :( - */ - private function processMicroUPX(): void - { - if (version_compare($this->getMicroVersion(), '0.2.0') >= 0 && !$this->getOption('no-strip', false)) { - shell()->exec('strip --strip-unneeded ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx'); - - if ($this->getOption('with-upx-pack')) { - // strip first - shell()->exec(getenv('UPX_EXEC') . ' --best ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx'); - // cut binary with readelf - [$ret, $out] = shell()->execWithResult('readelf -l ' . SOURCE_PATH . '/php-src/sapi/micro/micro.sfx | awk \'/LOAD|GNU_STACK/ {getline; print $1, $2, $3, $4, $6, $7}\''); - $out[1] = explode(' ', $out[1]); - $offset = $out[1][0]; - if ($ret !== 0 || !str_starts_with($offset, '0x')) { - throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output'); - } - $offset = hexdec($offset); - // remove upx extra wastes - file_put_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx', substr(file_get_contents(SOURCE_PATH . '/php-src/sapi/micro/micro.sfx'), 0, $offset)); - } - } - } } diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index bcfaf6cf0..d3bc5d194 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -189,9 +189,6 @@ protected function buildCli(): void $shell = shell()->cd(SOURCE_PATH . '/php-src'); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; $shell->exec("make {$concurrency} {$vars} cli"); - if (!$this->getOption('no-strip', false)) { - $shell->exec('dsymutil -f sapi/cli/php')->exec('strip -S sapi/cli/php'); - } $this->deployBinary(BUILD_TARGET_CLI); } @@ -202,9 +199,6 @@ protected function buildCgi(): void $shell = shell()->cd(SOURCE_PATH . '/php-src'); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; $shell->exec("make {$concurrency} {$vars} cgi"); - if (!$this->getOption('no-strip', false)) { - $shell->exec('dsymutil -f sapi/cgi/php-cgi')->exec('strip -S sapi/cgi/php-cgi'); - } $this->deployBinary(BUILD_TARGET_CGI); } @@ -216,31 +210,30 @@ protected function buildMicro(): void if ($this->getPHPVersionID() < 80000) { throw new WrongUsageException('phpmicro only support PHP >= 8.0!'); } - if ($this->getExt('phar')) { - $this->phar_patched = true; - SourcePatcher::patchMicroPhar($this->getPHPVersionID()); - } - $enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; - $vars = $this->getMakeExtraVars(); + try { + if ($this->getExt('phar')) { + $this->phar_patched = true; + SourcePatcher::patchMicroPhar($this->getPHPVersionID()); + } - // patch fake cli for micro - $vars['EXTRA_CFLAGS'] .= $enable_fake_cli; - $vars = SystemUtil::makeEnvVarString($vars); + $enable_fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; + $vars = $this->getMakeExtraVars(); - $shell = shell()->cd(SOURCE_PATH . '/php-src'); - // build - $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; - $shell->exec("make {$concurrency} {$vars} micro"); - // strip - if (!$this->getOption('no-strip', false)) { - $shell->exec('dsymutil -f sapi/micro/micro.sfx')->exec('strip -S sapi/micro/micro.sfx'); - } + // patch fake cli for micro + $vars['EXTRA_CFLAGS'] .= $enable_fake_cli; + $vars = SystemUtil::makeEnvVarString($vars); - $this->deployBinary(BUILD_TARGET_MICRO); + $shell = shell()->cd(SOURCE_PATH . '/php-src'); + // build + $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; + $shell->exec("make {$concurrency} {$vars} micro"); - if ($this->phar_patched) { - SourcePatcher::unpatchMicroPhar(); + $this->deployBinary(BUILD_TARGET_MICRO); + } finally { + if ($this->phar_patched) { + SourcePatcher::unpatchMicroPhar(); + } } } @@ -254,9 +247,6 @@ protected function buildFpm(): void $shell = shell()->cd(SOURCE_PATH . '/php-src'); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; $shell->exec("make {$concurrency} {$vars} fpm"); - if (!$this->getOption('no-strip', false)) { - $shell->exec('dsymutil -f sapi/fpm/php-fpm')->exec('strip -S sapi/fpm/php-fpm'); - } $this->deployBinary(BUILD_TARGET_FPM); } diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 0d123d1f0..8122bf2c8 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -6,6 +6,7 @@ use SPC\builder\BuilderBase; use SPC\builder\linux\SystemUtil as LinuxSystemUtil; +use SPC\exception\PatchException; use SPC\exception\SPCException; use SPC\exception\SPCInternalException; use SPC\exception\ValidationException; @@ -210,21 +211,71 @@ protected function sanityCheck(int $build_target): void /** * Deploy the binary file to the build bin path. * - * @param int $type Type integer, one of BUILD_TARGET_CLI, BUILD_TARGET_MICRO, BUILD_TARGET_FPM + * @param int $type Type integer, one of BUILD_TARGET_CLI, BUILD_TARGET_MICRO, BUILD_TARGET_FPM, BUILD_TARGET_CGI, BUILD_TARGET_FRANKENPHP */ - protected function deployBinary(int $type): bool + protected function deployBinary(int $type): void { + FileSystem::createDir(BUILD_BIN_PATH); + $copy_files = []; $src = match ($type) { BUILD_TARGET_CLI => SOURCE_PATH . '/php-src/sapi/cli/php', BUILD_TARGET_MICRO => SOURCE_PATH . '/php-src/sapi/micro/micro.sfx', BUILD_TARGET_FPM => SOURCE_PATH . '/php-src/sapi/fpm/php-fpm', BUILD_TARGET_CGI => SOURCE_PATH . '/php-src/sapi/cgi/php-cgi', + BUILD_TARGET_FRANKENPHP => BUILD_BIN_PATH . '/frankenphp', default => throw new SPCInternalException("Deployment does not accept type {$type}"), }; - logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file'); - FileSystem::createDir(BUILD_BIN_PATH); - shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_BIN_PATH)); - return true; + $no_strip_option = (bool) $this->getOption('no-strip', false); + $upx_option = (bool) $this->getOption('with-upx-pack', false); + + // Generate debug symbols if needed + $copy_files[] = $src; + if (!$no_strip_option && PHP_OS_FAMILY === 'Darwin') { + shell() + ->exec("dsymutil -f {$src}") // generate .dwarf file + ->exec("strip -S {$src}"); // strip unneeded symbols + $copy_files[] = "{$src}.dwarf"; + } elseif (!$no_strip_option && PHP_OS_FAMILY === 'Linux') { + shell() + ->exec("objcopy --only-keep-debug {$src} {$src}.debug") // extract debug symbols + ->exec("objcopy --strip-debug --add-gnu-debuglink={$src}.debug {$src}") // link debug symbols + ->exec("strip --strip-unneeded {$src}"); // strip unneeded symbols + $copy_files[] = "{$src}.debug"; + } + + // Compress binary with UPX if needed (only for Linux) + if ($upx_option && PHP_OS_FAMILY === 'Linux') { + if ($no_strip_option) { + logger()->warning('UPX compression is not recommended when --no-strip is enabled.'); + } + logger()->info("Compressing {$src} with UPX"); + shell()->exec(getenv('UPX_EXEC') . " --best {$src}"); + + // micro needs special section handling in LinuxBuilder. + // The micro.sfx does not support UPX directly, but we can remove UPX-info segment to adapt. + // This will also make micro.sfx with upx-packed more like a malware fore antivirus :( + if ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0) { + // strip first + // cut binary with readelf + [$ret, $out] = shell()->execWithResult("readelf -l {$src} | awk '/LOAD|GNU_STACK/ {getline; print \$1, \$2, \$3, \$4, \$6, \$7}'"); + $out[1] = explode(' ', $out[1]); + $offset = $out[1][0]; + if ($ret !== 0 || !str_starts_with($offset, '0x')) { + throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output'); + } + $offset = hexdec($offset); + // remove upx extra wastes + file_put_contents($src, substr(file_get_contents($src), 0, $offset)); + } + } + + // Copy files + foreach ($copy_files as $file) { + if (!file_exists($file)) { + throw new SPCInternalException("Deploy failed. Cannot find file: {$file}"); + } + FileSystem::copy($file, BUILD_BIN_PATH . '/' . basename($file)); + } } /** @@ -325,13 +376,7 @@ protected function buildFrankenphp(): void ->setEnv($env) ->exec("xcaddy build --output frankenphp {$xcaddyModules}"); - if (!$this->getOption('no-strip', false) && file_exists(BUILD_BIN_PATH . '/frankenphp')) { - if (PHP_OS_FAMILY === 'Linux') { - shell()->cd(BUILD_BIN_PATH)->exec('strip --strip-unneeded frankenphp'); - } else { // macOS doesn't understand strip-unneeded - shell()->cd(BUILD_BIN_PATH)->exec('strip -S frankenphp'); - } - } + $this->deployBinary(BUILD_TARGET_FRANKENPHP); } /** diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index 1892d8014..35b1623df 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -67,21 +67,6 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void $zts = $this->zts ? '--enable-zts=yes ' : '--enable-zts=no '; - // with-upx-pack for phpmicro - if ($enableMicro && version_compare($this->getMicroVersion(), '0.2.0') < 0) { - $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 = ' . getenv('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'); - } - } - $opcache_jit = !$this->getOption('disable-opcache-jit', false); $opcache_jit_arg = $opcache_jit ? '--enable-opcache-jit=yes ' : '--enable-opcache-jit=no '; @@ -145,7 +130,7 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void } } - public function testPHP(int $build_target = BUILD_TARGET_NONE) + public function testPHP(int $build_target = BUILD_TARGET_NONE): void { $this->sanityCheck($build_target); } @@ -156,8 +141,23 @@ public function buildCli(): void $extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; + // Add debug symbols for release build if --no-strip is specified + // We need to modify CFLAGS to replace /Ox with /Zi and add /DEBUG to LDFLAGS + $debug_overrides = ''; + if ($this->getOption('no-strip', false)) { + // Read current CFLAGS from Makefile and replace optimization flags + $makefile_content = file_get_contents(SOURCE_PATH . '\php-src\Makefile'); + if (preg_match('/^CFLAGS=(.+?)$/m', $makefile_content, $matches)) { + $cflags = $matches[1]; + // Replace /Ox (full optimization) with /Zi (debug info) and /Od (disable optimization) + // Keep optimization for speed: /O2 /Zi instead of /Od /Zi + $cflags = str_replace('/Ox ', '/O2 /Zi ', $cflags); + $debug_overrides = '"CFLAGS=' . $cflags . '" "LDFLAGS=/DEBUG /LTCG /INCREMENTAL:NO" "LDFLAGS_CLI=/DEBUG" '; + } + } + // add nmake wrapper - FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cli_wrapper.bat', "nmake /nologo LIBS_CLI=\"ws2_32.lib shell32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= %*"); + FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cli_wrapper.bat', "nmake /nologo {$debug_overrides}LIBS_CLI=\"ws2_32.lib shell32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= %*"); cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cli_wrapper.bat --task-args php.exe"); @@ -170,8 +170,19 @@ public function buildCgi(): void $extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; + // Add debug symbols for release build if --no-strip is specified + $debug_overrides = ''; + if ($this->getOption('no-strip', false)) { + $makefile_content = file_get_contents(SOURCE_PATH . '\php-src\Makefile'); + if (preg_match('/^CFLAGS=(.+?)$/m', $makefile_content, $matches)) { + $cflags = $matches[1]; + $cflags = str_replace('/Ox ', '/O2 /Zi ', $cflags); + $debug_overrides = '"CFLAGS=' . $cflags . '" "LDFLAGS=/DEBUG /LTCG /INCREMENTAL:NO" "LDFLAGS_CGI=/DEBUG" '; + } + } + // add nmake wrapper - FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cgi_wrapper.bat', "nmake /nologo LIBS_CGI=\"ws2_32.lib kernel32.lib advapi32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= %*"); + FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_cgi_wrapper.bat', "nmake /nologo {$debug_overrides}LIBS_CGI=\"ws2_32.lib kernel32.lib advapi32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= %*"); cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cgi_wrapper.bat --task-args php-cgi.exe"); @@ -202,9 +213,20 @@ public function buildMicro(): void $extra_libs = getenv('SPC_EXTRA_LIBS') ?: ''; + // Add debug symbols for release build if --no-strip is specified + $debug_overrides = ''; + if ($this->getOption('no-strip', false) && !$this->getOption('debug', false)) { + $makefile_content = file_get_contents(SOURCE_PATH . '\php-src\Makefile'); + if (preg_match('/^CFLAGS=(.+?)$/m', $makefile_content, $matches)) { + $cflags = $matches[1]; + $cflags = str_replace('/Ox ', '/O2 /Zi ', $cflags); + $debug_overrides = '"CFLAGS=' . $cflags . '" "LDFLAGS=/DEBUG /LTCG /INCREMENTAL:NO" "LDFLAGS_MICRO=/DEBUG" '; + } + } + // add nmake wrapper $fake_cli = $this->getOption('with-micro-fake-cli', false) ? ' /DPHP_MICRO_FAKE_CLI" ' : ''; - $wrapper = "nmake /nologo LIBS_MICRO=\"ws2_32.lib shell32.lib {$extra_libs}\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" %*"; + $wrapper = "nmake /nologo {$debug_overrides}LIBS_MICRO=\"ws2_32.lib shell32.lib {$extra_libs}\" CFLAGS_MICRO=\"/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1{$fake_cli}\" %*"; FileSystem::writeFile(SOURCE_PATH . '\php-src\nmake_micro_wrapper.bat', $wrapper); // phar patch for micro @@ -335,28 +357,32 @@ public function sanityCheck(mixed $build_target): void * * @param int $type Deploy type */ - public function deployBinary(int $type): bool + public function deployBinary(int $type): void { + $rel_type = 'Release'; // TODO: Debug build support $ts = $this->zts ? '_TS' : ''; $src = match ($type) { - BUILD_TARGET_CLI => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\php.exe", - BUILD_TARGET_MICRO => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\micro.sfx", - BUILD_TARGET_CGI => SOURCE_PATH . "\\php-src\\x64\\Release{$ts}\\php-cgi.exe", + BUILD_TARGET_CLI => [SOURCE_PATH . "\\php-src\\x64\\{$rel_type}{$ts}", 'php.exe', 'php.pdb'], + BUILD_TARGET_MICRO => [SOURCE_PATH . "\\php-src\\x64\\{$rel_type}{$ts}", 'micro.sfx', 'micro.pdb'], + BUILD_TARGET_CGI => [SOURCE_PATH . "\\php-src\\x64\\{$rel_type}{$ts}", 'php-cgi.exe', 'php-cgi.pdb'], default => throw new SPCInternalException("Deployment does not accept type {$type}"), }; // with-upx-pack for cli and micro if ($this->getOption('with-upx-pack', false)) { if ($type === BUILD_TARGET_CLI || $type === BUILD_TARGET_CGI || ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0)) { - cmd()->exec(getenv('UPX_EXEC') . ' --best ' . escapeshellarg($src)); + cmd()->exec(getenv('UPX_EXEC') . ' --best ' . escapeshellarg("{$src[0]}\\{$src[1]}")); } } logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file'); FileSystem::createDir(BUILD_BIN_PATH); - cmd()->exec('copy ' . escapeshellarg($src) . ' ' . escapeshellarg(BUILD_BIN_PATH . '\\')); - return true; + cmd()->exec('copy ' . escapeshellarg("{$src[0]}\\{$src[1]}") . ' ' . escapeshellarg(BUILD_BIN_PATH . '\\')); + if ($this->getOption('no-strip', false) && file_exists("{$src[0]}\\{$src[2]}")) { + logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' debug symbols'); + cmd()->exec('copy ' . escapeshellarg("{$src[0]}\\{$src[2]}") . ' ' . escapeshellarg(BUILD_BIN_PATH . '\\')); + } } /** diff --git a/src/SPC/store/FileSystem.php b/src/SPC/store/FileSystem.php index f6ce371e0..86daefc43 100644 --- a/src/SPC/store/FileSystem.php +++ b/src/SPC/store/FileSystem.php @@ -174,6 +174,9 @@ public static function copy(string $from, string $to): void logger()->debug("Copying file from {$from} to {$to}"); $dst_path = FileSystem::convertPath($to); $src_path = FileSystem::convertPath($from); + if ($src_path === $dst_path) { + return; + } if (!copy($src_path, $dst_path)) { throw new FileSystemException('Cannot copy file from ' . $src_path . ' to ' . $dst_path); } From 9738fcd6cd75827ec21d8462957b077fc69f742e Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 31 Oct 2025 16:48:36 +0800 Subject: [PATCH 02/22] Update env.ini docs --- config/env.ini | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/config/env.ini b/config/env.ini index a1f149c59..1047c3f70 100644 --- a/config/env.ini +++ b/config/env.ini @@ -1,40 +1,41 @@ -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; static-php-cli (spc) env configuration ; ; This file is used to set default env vars for static-php-cli build. ; As dynamic build process, some of these vars can be overwritten by CLI options. ; And you can also overwrite these vars by setting them in your shell environment. +; The value should be changed only if you know what you are doing. Otherwise, please leave them as default. ; ; We need to use some pre-defined internal env vars, like `BUILD_ROOT_PATH`, `DOWNLOAD_PATH`, etc. -; Please note that these vars cannot be defined in this file, they are only be defined before static-php-cli running. +; Please note that these vars cannot be defined in this file, they should only be defined before static-php-cli running. ; -; Here's a list of env vars, these value cannot be changed anywhere: +; Here's a list of env vars, these variables will be defined if not defined: ; -; SPC_VERSION: the version of static-php-cli. -; WORKING_DIR: the working directory of the build process. (default: `$(pwd)`) -; ROOT_DIR: the root directory of static-php-cli. (default: `/path/to/static-php-cli`, when running in phar or micro mode: `phar://path/to/spc.phar`) ; BUILD_ROOT_PATH: the root path of the build process. (default: `$(pwd)/buildroot`) ; BUILD_INCLUDE_PATH: the path of the include files. (default: `$BUILD_ROOT_PATH/include`) ; BUILD_LIB_PATH: the path of the lib files. (default: `$BUILD_ROOT_PATH/lib`) ; BUILD_BIN_PATH: the path of the bin files. (default: `$BUILD_ROOT_PATH/bin`) -; PKG_ROOT_PATH: the root path of the package files. (default: `$(pwd)/pkgroot`) +; BUILD_MODULES_PATH: the path of the php modules (shared extensions) files. (default: `$BUILD_ROOT_PATH/modules`) +; PKG_ROOT_PATH: the root path of the package files. (default: `$(pwd)/pkgroot/$GNU_ARCH-{darwin|linux|windows}`) ; SOURCE_PATH: the path of the source files. (default: `$(pwd)/source`) ; DOWNLOAD_PATH: the path of the download files. (default: `$(pwd)/downloads`) -; CPU_COUNT: the count of the CPU cores. (default: `$(nproc)`) -; SPC_ARCH: the arch of the current system, for some libraries needed `--host=XXX` args. (default: `$(uname -m)`, e.g. `x86_64`, `aarch64`, `arm64`) -; GNU_ARCH: the GNU arch of the current system. (default: `$(uname -m)`, e.g. `x86_64`, `aarch64`) -; MAC_ARCH: the MAC arch of the current system. (default: `$(uname -m)`, e.g. `x86_64`, `arm64`) +; PATH: (*nix only) static-php-cli will add `$BUILD_BIN_PATH` to PATH. +; PKG_CONFIG_PATH: (*nix only) static-php-cli will set `$BUILD_LIB_PATH/pkgconfig` to PKG_CONFIG_PATH. ; -; * These vars are only be defined in Unix (macOS, Linux, FreeBSD)Builder and cannot be changed anywhere: -; PATH: static-php-cli will add `$BUILD_BIN_PATH` to PATH. -; PKG_CONFIG: static-php-cli will set `$BUILD_BIN_PATH/pkg-config` to PKG_CONFIG. -; PKG_CONFIG_PATH: static-php-cli will set `$BUILD_LIB_PATH/pkgconfig` to PKG_CONFIG_PATH. +; Here's a list of env vars, these variables is defined in SPC and cannot be changed anywhere: ; -; * These vars are only be defined in LinuxBuilder and cannot be changed anywhere: -; SPC_LINUX_DEFAULT_CC: the default compiler for linux. (For alpine linux: `gcc`, default: `$GNU_ARCH-linux-musl-gcc`) -; SPC_LINUX_DEFAULT_CXX: the default c++ compiler for linux. (For alpine linux: `g++`, default: `$GNU_ARCH-linux-musl-g++`) -; SPC_LINUX_DEFAULT_AR: the default archiver for linux. (For alpine linux: `ar`, default: `$GNU_ARCH-linux-musl-ar`) -; SPC_EXTRA_PHP_VARS: the extra vars for building php, used in `configure` and `make` command. +; SPC_VERSION: the version of static-php-cli. +; WORKING_DIR: the working directory of the build process. (default: `$(pwd)`) +; ROOT_DIR: the root directory of static-php-cli. (default: `/path/to/static-php-cli`, when running in phar or micro mode: `phar://path/to/spc.phar`) +; CPU_COUNT: the count of the CPU cores. (default: `$(nproc)`) +; SPC_ARCH: the arch of the current system, for some libraries needed `--host=XXX` args. (default: `$(uname -m)`, e.g. `x86_64`, `aarch64`, `arm64`) +; GNU_ARCH: the GNU arch of the current system. (default: `$(uname -m)`, e.g. `x86_64`, `aarch64`) +; MAC_ARCH: the MAC arch of the current system. (default: `$(uname -m)`, e.g. `x86_64`, `arm64`) +; PKG_CONFIG: (*nix only) static-php-cli will set `$BUILD_BIN_PATH/pkg-config` to PKG_CONFIG. +; SPC_LINUX_DEFAULT_CC: (linux only) the default compiler for linux. (For alpine linux: `gcc`, default: `$GNU_ARCH-linux-musl-gcc`) +; SPC_LINUX_DEFAULT_CXX: (linux only) the default c++ compiler for linux. (For alpine linux: `g++`, default: `$GNU_ARCH-linux-musl-g++`) +; SPC_LINUX_DEFAULT_AR: (linux only) the default archiver for linux. (For alpine linux: `ar`, default: `$GNU_ARCH-linux-musl-ar`) +; SPC_EXTRA_PHP_VARS: (linux only) the extra vars for building php, used in `configure` and `make` command. [global] ; Build concurrency for make -jN, default is CPU_COUNT, this value are used in every libs. From 5e3e7eccbfe2b8c6d72089b01a43f122a481a1b1 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Fri, 31 Oct 2025 16:48:55 +0800 Subject: [PATCH 03/22] Update version to 2.7.7 --- src/SPC/ConsoleApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/ConsoleApplication.php b/src/SPC/ConsoleApplication.php index da143de1a..9a957223c 100644 --- a/src/SPC/ConsoleApplication.php +++ b/src/SPC/ConsoleApplication.php @@ -34,7 +34,7 @@ */ final class ConsoleApplication extends Application { - public const string VERSION = '2.7.6'; + public const string VERSION = '2.7.7'; public function __construct() { From 757af25d8fca4a68124ec9960b056c39b8e0a0a2 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sat, 1 Nov 2025 00:49:50 +0800 Subject: [PATCH 04/22] Suggestions --- src/SPC/builder/unix/UnixBuilderBase.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 8122bf2c8..5d4abbfcc 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -238,7 +238,7 @@ protected function deployBinary(int $type): void } elseif (!$no_strip_option && PHP_OS_FAMILY === 'Linux') { shell() ->exec("objcopy --only-keep-debug {$src} {$src}.debug") // extract debug symbols - ->exec("objcopy --strip-debug --add-gnu-debuglink={$src}.debug {$src}") // link debug symbols + ->exec("objcopy --add-gnu-debuglink={$src}.debug {$src}") // link debug symbols ->exec("strip --strip-unneeded {$src}"); // strip unneeded symbols $copy_files[] = "{$src}.debug"; } @@ -274,7 +274,10 @@ protected function deployBinary(int $type): void if (!file_exists($file)) { throw new SPCInternalException("Deploy failed. Cannot find file: {$file}"); } - FileSystem::copy($file, BUILD_BIN_PATH . '/' . basename($file)); + // ignore copy to self + if (realpath($file) !== realpath(BUILD_BIN_PATH . '/' . basename($file))) { + shell()->exec('cp ' . escapeshellarg($file) . ' ' . escapeshellarg(BUILD_BIN_PATH . '/')); + } } } From f09c18e78f694a0a0653effbe5a59a6364426591 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 6 Nov 2025 16:24:59 +0800 Subject: [PATCH 05/22] Use separated deploy functions --- src/SPC/builder/Extension.php | 7 + src/SPC/builder/freebsd/BSDBuilder.php | 6 +- src/SPC/builder/linux/LinuxBuilder.php | 95 ++++++++++---- src/SPC/builder/macos/MacOSBuilder.php | 8 +- src/SPC/builder/unix/UnixBuilderBase.php | 143 ++++++++++++--------- src/SPC/builder/windows/WindowsBuilder.php | 49 +++++-- src/globals/test-extensions.php | 8 +- 7 files changed, 201 insertions(+), 115 deletions(-) diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index c9cd06577..321537646 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -448,6 +448,13 @@ public function buildUnixShared(): void ->exec('make clean') ->exec('make -j' . $this->builder->concurrency) ->exec('make install'); + + // process *.so file + $soFile = BUILD_MODULES_PATH . '/' . $this->getName() . '.so'; + if (!file_exists($soFile)) { + throw new ValidationException("extension {$this->getName()} build failed: {$soFile} not found", validation_module: "Extension {$this->getName()} build"); + } + $this->builder->deployBinary($soFile, $soFile, false); } /** diff --git a/src/SPC/builder/freebsd/BSDBuilder.php b/src/SPC/builder/freebsd/BSDBuilder.php index 0c1d1ee44..b746dff56 100644 --- a/src/SPC/builder/freebsd/BSDBuilder.php +++ b/src/SPC/builder/freebsd/BSDBuilder.php @@ -153,7 +153,7 @@ protected function buildCli(): void if (!$this->getOption('no-strip', false)) { $shell->exec('strip sapi/cli/php'); } - $this->deployBinary(BUILD_TARGET_CLI); + $this->deploySAPIBinary(BUILD_TARGET_CLI); } /** @@ -184,7 +184,7 @@ protected function buildMicro(): void if (!$this->getOption('no-strip', false)) { shell()->cd(SOURCE_PATH . '/php-src/sapi/micro')->exec('strip --strip-unneeded micro.sfx'); } - $this->deployBinary(BUILD_TARGET_MICRO); + $this->deploySAPIBinary(BUILD_TARGET_MICRO); if ($this->phar_patched) { SourcePatcher::unpatchMicroPhar(); @@ -206,7 +206,7 @@ protected function buildFpm(): void if (!$this->getOption('no-strip', false)) { $shell->exec('strip sapi/fpm/php-fpm'); } - $this->deployBinary(BUILD_TARGET_FPM); + $this->deploySAPIBinary(BUILD_TARGET_FPM); } /** diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 5ffa6a4d6..3f5d3b3c6 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -5,6 +5,7 @@ namespace SPC\builder\linux; use SPC\builder\unix\UnixBuilderBase; +use SPC\exception\PatchException; use SPC\exception\WrongUsageException; use SPC\store\Config; use SPC\store\FileSystem; @@ -193,7 +194,7 @@ protected function buildCli(): void SourcePatcher::patchFile('musl_static_readline.patch', SOURCE_PATH . '/php-src', true); } - $this->deployBinary(BUILD_TARGET_CLI); + $this->deploySAPIBinary(BUILD_TARGET_CLI); } protected function buildCgi(): void @@ -204,7 +205,7 @@ protected function buildCgi(): void ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("make {$concurrency} {$vars} cgi"); - $this->deployBinary(BUILD_TARGET_CGI); + $this->deploySAPIBinary(BUILD_TARGET_CGI); } /** @@ -233,7 +234,11 @@ protected function buildMicro(): void ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("make {$concurrency} {$vars} micro"); - $this->deployBinary(BUILD_TARGET_MICRO); + // deploy micro.sfx + $dst = $this->deploySAPIBinary(BUILD_TARGET_MICRO); + + // patch after UPX-ed micro.sfx + $this->processUpxedMicroSfx($dst); } finally { if ($this->phar_patched) { SourcePatcher::unpatchMicroPhar(); @@ -252,7 +257,7 @@ protected function buildFpm(): void ->exec('sed -i "s|//lib|/lib|g" Makefile') ->exec("make {$concurrency} {$vars} fpm"); - $this->deployBinary(BUILD_TARGET_FPM); + $this->deploySAPIBinary(BUILD_TARGET_FPM); } /** @@ -272,10 +277,48 @@ protected function buildEmbed(): void ->exec('sed -i "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" Makefile') ->exec("make {$concurrency} INSTALL_ROOT=" . BUILD_ROOT_PATH . " {$vars} install-sapi {$install_modules} install-build install-headers install-programs"); + // process libphp.so for shared embed + $libphpSo = BUILD_LIB_PATH . '/libphp.so'; + if (file_exists($libphpSo)) { + // deploy libphp.so + $this->deployBinary($libphpSo, $libphpSo, false); + // post actions: rename libphp.so to libphp-.so if -release is set in LDFLAGS + $this->processLibphpSoFile($libphpSo); + } + + // process libphp.a for static embed + if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { + $AR = getenv('AR') ?: 'ar'; + f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a'); + // export dynamic symbols + SystemUtil::exportDynamicSymbols(BUILD_LIB_PATH . '/libphp.a'); + } + + // patch embed php scripts + $this->patchPhpScripts(); + } + + /** + * Return extra variables for php make command. + */ + private function getMakeExtraVars(): array + { + $config = (new SPCConfigUtil($this, ['libs_only_deps' => true, 'absolute_libs' => true]))->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs')); + $static = SPCTarget::isStatic() ? '-all-static' : ''; + $lib = BUILD_LIB_PATH; + return array_filter([ + 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), + 'EXTRA_LIBS' => $config['libs'], + 'EXTRA_LDFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), + 'EXTRA_LDFLAGS_PROGRAM' => "-L{$lib} {$static} -pie", + ]); + } + + private function processLibphpSoFile(string $libphpSo): void + { $ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: ''; $libDir = BUILD_LIB_PATH; $modulesDir = BUILD_MODULES_PATH; - $libphpSo = "{$libDir}/libphp.so"; $realLibName = 'libphp.so'; $cwd = getcwd(); @@ -337,33 +380,29 @@ protected function buildEmbed(): void } } } - - if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { - $AR = getenv('AR') ?: 'ar'; - f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a'); - // export dynamic symbols - SystemUtil::exportDynamicSymbols(BUILD_LIB_PATH . '/libphp.a'); - } - - if (!$this->getOption('no-strip', false) && file_exists(BUILD_LIB_PATH . '/' . $realLibName)) { - shell()->cd(BUILD_LIB_PATH)->exec("strip --strip-unneeded {$realLibName}"); - } - $this->patchPhpScripts(); } /** - * Return extra variables for php make command. + * Patch micro.sfx after UPX compression. + * micro needs special section handling in LinuxBuilder. + * The micro.sfx does not support UPX directly, but we can remove UPX + * info segment to adapt. + * This will also make micro.sfx with upx-packed more like a malware fore antivirus */ - private function getMakeExtraVars(): array + private function processUpxedMicroSfx(string $dst): void { - $config = (new SPCConfigUtil($this, ['libs_only_deps' => true, 'absolute_libs' => true]))->config($this->ext_list, $this->lib_list, $this->getOption('with-suggested-exts'), $this->getOption('with-suggested-libs')); - $static = SPCTarget::isStatic() ? '-all-static' : ''; - $lib = BUILD_LIB_PATH; - return array_filter([ - 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), - 'EXTRA_LIBS' => $config['libs'], - 'EXTRA_LDFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), - 'EXTRA_LDFLAGS_PROGRAM' => "-L{$lib} {$static} -pie", - ]); + if ($this->getOption('with-upx-pack') && version_compare($this->getMicroVersion(), '0.2.0') >= 0) { + // strip first + // cut binary with readelf + [$ret, $out] = shell()->execWithResult("readelf -l {$dst} | awk '/LOAD|GNU_STACK/ {getline; print \$1, \$2, \$3, \$4, \$6, \$7}'"); + $out[1] = explode(' ', $out[1]); + $offset = $out[1][0]; + if ($ret !== 0 || !str_starts_with($offset, '0x')) { + throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output'); + } + $offset = hexdec($offset); + // remove upx extra wastes + file_put_contents($dst, substr(file_get_contents($dst), 0, $offset)); + } } } diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index d3bc5d194..204a712d0 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -189,7 +189,7 @@ protected function buildCli(): void $shell = shell()->cd(SOURCE_PATH . '/php-src'); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; $shell->exec("make {$concurrency} {$vars} cli"); - $this->deployBinary(BUILD_TARGET_CLI); + $this->deploySAPIBinary(BUILD_TARGET_CLI); } protected function buildCgi(): void @@ -199,7 +199,7 @@ protected function buildCgi(): void $shell = shell()->cd(SOURCE_PATH . '/php-src'); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; $shell->exec("make {$concurrency} {$vars} cgi"); - $this->deployBinary(BUILD_TARGET_CGI); + $this->deploySAPIBinary(BUILD_TARGET_CGI); } /** @@ -229,7 +229,7 @@ protected function buildMicro(): void $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; $shell->exec("make {$concurrency} {$vars} micro"); - $this->deployBinary(BUILD_TARGET_MICRO); + $this->deploySAPIBinary(BUILD_TARGET_MICRO); } finally { if ($this->phar_patched) { SourcePatcher::unpatchMicroPhar(); @@ -247,7 +247,7 @@ protected function buildFpm(): void $shell = shell()->cd(SOURCE_PATH . '/php-src'); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; $shell->exec("make {$concurrency} {$vars} fpm"); - $this->deployBinary(BUILD_TARGET_FPM); + $this->deploySAPIBinary(BUILD_TARGET_FPM); } /** diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 4c90ebdb9..570d8093f 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -6,7 +6,6 @@ use SPC\builder\BuilderBase; use SPC\builder\linux\SystemUtil as LinuxSystemUtil; -use SPC\exception\PatchException; use SPC\exception\SPCException; use SPC\exception\SPCInternalException; use SPC\exception\ValidationException; @@ -79,6 +78,82 @@ public function proveLibs(array $sorted_libraries): void $this->lib_list = $sorted_libraries; } + /** + * Strip unneeded symbols from binary file. + */ + public function stripBinary(string $binary_path): void + { + shell()->exec(match (PHP_OS_FAMILY) { + 'Darwin' => "strip -S {$binary_path}", + 'Linux' => "strip --strip-unneeded {$binary_path}", + default => throw new SPCInternalException('stripBinary is only supported on Linux and macOS'), + }); + } + + /** + * Extract debug information from binary file. + * + * @param string $binary_path the path to the binary file, including executables, shared libraries, etc + */ + public function extractDebugInfo(string $binary_path): string + { + $target_dir = BUILD_ROOT_PATH . '/debug'; + FileSystem::createDir($target_dir); + $basename = basename($binary_path); + $debug_file = "{$target_dir}/{$basename}" . (PHP_OS_FAMILY === 'Darwin' ? '.dwarf' : '.debug'); + shell()->exec(match (PHP_OS_FAMILY) { + 'Darwin' => "dsymutil -f {$binary_path} -o {$debug_file}", + 'Linux' => "objcopy ---only-keep-debug {$binary_path} {$debug_file}", + default => throw new SPCInternalException('extractDebugInfo is only supported on Linux and macOS'), + }); + return $debug_file; + } + + /** + * Deploy the binary file from src to dst. + */ + public function deployBinary(string $src, string $dst, bool $executable = true): string + { + // UPX for linux + $upx_option = (bool) $this->getOption('with-upx-pack', false); + + // file must exists + if (!file_exists($src)) { + throw new SPCInternalException("Deploy failed. Cannot find file: {$src}"); + } + // dst dir must exists + FileSystem::createDir(dirname($dst)); + + // ignore copy to self + if (realpath($src) !== realpath($dst)) { + shell()->exec('cp ' . escapeshellarg($src) . ' ' . escapeshellarg($dst)); + } + + // file exist + if (!file_exists($dst)) { + throw new SPCInternalException("Deploy failed. Cannot find file after copy: {$dst}"); + } + + // extract debug info + $this->extractDebugInfo($dst); + + // strip + if (!$this->getOption('no-strip', false)) { + $this->stripBinary($dst); + } + + // Compress binary with UPX if needed (only for Linux) + if ($upx_option && PHP_OS_FAMILY === 'Linux' && $executable) { + if ($this->getOption('no-strip', false)) { + logger()->warning('UPX compression is not recommended when --no-strip is enabled.'); + } + logger()->info("Compressing {$dst} with UPX"); + shell()->exec(getenv('UPX_EXEC') . " --best {$dst}"); + } + + return $dst; + } + /** * Sanity check after build complete. */ @@ -209,14 +284,10 @@ protected function sanityCheck(int $build_target): void } /** - * Deploy the binary file to the build bin path. - * - * @param int $type Type integer, one of BUILD_TARGET_CLI, BUILD_TARGET_MICRO, BUILD_TARGET_FPM, BUILD_TARGET_CGI, BUILD_TARGET_FRANKENPHP + * Deploy binaries that produces executable SAPI */ - protected function deployBinary(int $type): void + protected function deploySAPIBinary(int $type): string { - FileSystem::createDir(BUILD_BIN_PATH); - $copy_files = []; $src = match ($type) { BUILD_TARGET_CLI => SOURCE_PATH . '/php-src/sapi/cli/php', BUILD_TARGET_MICRO => SOURCE_PATH . '/php-src/sapi/micro/micro.sfx', @@ -225,60 +296,8 @@ protected function deployBinary(int $type): void BUILD_TARGET_FRANKENPHP => BUILD_BIN_PATH . '/frankenphp', default => throw new SPCInternalException("Deployment does not accept type {$type}"), }; - $no_strip_option = (bool) $this->getOption('no-strip', false); - $upx_option = (bool) $this->getOption('with-upx-pack', false); - - // Generate debug symbols if needed - $copy_files[] = $src; - if (!$no_strip_option && PHP_OS_FAMILY === 'Darwin') { - shell() - ->exec("dsymutil -f {$src}") // generate .dwarf file - ->exec("strip -S {$src}"); // strip unneeded symbols - $copy_files[] = "{$src}.dwarf"; - } elseif (!$no_strip_option && PHP_OS_FAMILY === 'Linux') { - shell() - ->exec("objcopy --only-keep-debug {$src} {$src}.debug") // extract debug symbols - ->exec("objcopy --add-gnu-debuglink={$src}.debug {$src}") // link debug symbols - ->exec("strip --strip-unneeded {$src}"); // strip unneeded symbols - $copy_files[] = "{$src}.debug"; - } - - // Compress binary with UPX if needed (only for Linux) - if ($upx_option && PHP_OS_FAMILY === 'Linux') { - if ($no_strip_option) { - logger()->warning('UPX compression is not recommended when --no-strip is enabled.'); - } - logger()->info("Compressing {$src} with UPX"); - shell()->exec(getenv('UPX_EXEC') . " --best {$src}"); - - // micro needs special section handling in LinuxBuilder. - // The micro.sfx does not support UPX directly, but we can remove UPX-info segment to adapt. - // This will also make micro.sfx with upx-packed more like a malware fore antivirus :( - if ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0) { - // strip first - // cut binary with readelf - [$ret, $out] = shell()->execWithResult("readelf -l {$src} | awk '/LOAD|GNU_STACK/ {getline; print \$1, \$2, \$3, \$4, \$6, \$7}'"); - $out[1] = explode(' ', $out[1]); - $offset = $out[1][0]; - if ($ret !== 0 || !str_starts_with($offset, '0x')) { - throw new PatchException('phpmicro UPX patcher', 'Cannot find offset in readelf output'); - } - $offset = hexdec($offset); - // remove upx extra wastes - file_put_contents($src, substr(file_get_contents($src), 0, $offset)); - } - } - - // Copy files - foreach ($copy_files as $file) { - if (!file_exists($file)) { - throw new SPCInternalException("Deploy failed. Cannot find file: {$file}"); - } - // ignore copy to self - if (realpath($file) !== realpath(BUILD_BIN_PATH . '/' . basename($file))) { - shell()->exec('cp ' . escapeshellarg($file) . ' ' . escapeshellarg(BUILD_BIN_PATH . '/')); - } - } + $dst = BUILD_BIN_PATH . '/' . basename($src); + return $this->deployBinary($src, $dst); } /** @@ -379,7 +398,7 @@ protected function buildFrankenphp(): void ->setEnv($env) ->exec("xcaddy build --output frankenphp {$xcaddyModules}"); - $this->deployBinary(BUILD_TARGET_FRANKENPHP); + $this->deploySAPIBinary(BUILD_TARGET_FRANKENPHP); } /** diff --git a/src/SPC/builder/windows/WindowsBuilder.php b/src/SPC/builder/windows/WindowsBuilder.php index 35b1623df..e4f1578c4 100644 --- a/src/SPC/builder/windows/WindowsBuilder.php +++ b/src/SPC/builder/windows/WindowsBuilder.php @@ -161,7 +161,7 @@ public function buildCli(): void cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cli_wrapper.bat --task-args php.exe"); - $this->deployBinary(BUILD_TARGET_CLI); + $this->deploySAPIBinary(BUILD_TARGET_CLI); } public function buildCgi(): void @@ -186,7 +186,7 @@ public function buildCgi(): void cmd()->cd(SOURCE_PATH . '\php-src')->exec("{$this->sdk_prefix} nmake_cgi_wrapper.bat --task-args php-cgi.exe"); - $this->deployBinary(BUILD_TARGET_CGI); + $this->deploySAPIBinary(BUILD_TARGET_CGI); } public function buildEmbed(): void @@ -243,7 +243,7 @@ public function buildMicro(): void } } - $this->deployBinary(BUILD_TARGET_MICRO); + $this->deploySAPIBinary(BUILD_TARGET_MICRO); } public function proveLibs(array $sorted_libraries): void @@ -357,8 +357,18 @@ public function sanityCheck(mixed $build_target): void * * @param int $type Deploy type */ - public function deployBinary(int $type): void + public function deploySAPIBinary(int $type): void { + logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file'); + + $debug_dir = BUILD_ROOT_PATH . '\debug'; + $bin_path = BUILD_BIN_PATH; + + // create dirs + FileSystem::createDir($debug_dir); + FileSystem::createDir($bin_path); + + // determine source path for different SAPI $rel_type = 'Release'; // TODO: Debug build support $ts = $this->zts ? '_TS' : ''; $src = match ($type) { @@ -368,20 +378,31 @@ public function deployBinary(int $type): void default => throw new SPCInternalException("Deployment does not accept type {$type}"), }; - // with-upx-pack for cli and micro - if ($this->getOption('with-upx-pack', false)) { - if ($type === BUILD_TARGET_CLI || $type === BUILD_TARGET_CGI || ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0)) { - cmd()->exec(getenv('UPX_EXEC') . ' --best ' . escapeshellarg("{$src[0]}\\{$src[1]}")); - } + $src = "{$src[0]}\\{$src[1]}"; + $dst = BUILD_BIN_PATH . '\\' . basename($src); + + // file must exists + if (!file_exists($src)) { + throw new SPCInternalException("Deploy failed. Cannot find file: {$src}"); } + // dst dir must exists + FileSystem::createDir(dirname($dst)); - logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' file'); - FileSystem::createDir(BUILD_BIN_PATH); + // copy file + if (realpath($src) !== realpath($dst)) { + cmd()->exec('copy ' . escapeshellarg($src) . ' ' . escapeshellarg($dst)); + } - cmd()->exec('copy ' . escapeshellarg("{$src[0]}\\{$src[1]}") . ' ' . escapeshellarg(BUILD_BIN_PATH . '\\')); + // extract debug info in buildroot/debug if ($this->getOption('no-strip', false) && file_exists("{$src[0]}\\{$src[2]}")) { - logger()->info('Deploying ' . $this->getBuildTypeName($type) . ' debug symbols'); - cmd()->exec('copy ' . escapeshellarg("{$src[0]}\\{$src[2]}") . ' ' . escapeshellarg(BUILD_BIN_PATH . '\\')); + cmd()->exec('copy ' . escapeshellarg("{$src[0]}\\{$src[2]}") . ' ' . escapeshellarg($debug_dir)); + } + + // with-upx-pack for cli and micro + if ($this->getOption('with-upx-pack', false)) { + if ($type === BUILD_TARGET_CLI || $type === BUILD_TARGET_CGI || ($type === BUILD_TARGET_MICRO && version_compare($this->getMicroVersion(), '0.2.0') >= 0)) { + cmd()->exec(getenv('UPX_EXEC') . ' --best ' . escapeshellarg($dst)); + } } } diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 43ce5d152..2caf56b00 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -31,7 +31,7 @@ 'ubuntu-22.04-arm', // bin/spc-gnu-docker for arm64 'ubuntu-24.04-arm', // bin/spc for arm64 // 'windows-2022', // .\bin\spc.ps1 - // 'windows-2025', + 'windows-2025', ]; // whether enable thread safe @@ -51,13 +51,13 @@ // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`). $extensions = match (PHP_OS_FAMILY) { 'Linux', 'Darwin' => 'rdkafka', - 'Windows' => 'bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,iconv,xml,mbstring,mbregex,mysqlnd,openssl,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip', + 'Windows' => 'bcmath,bz2,calendar,ctype,zlib,zip', }; // If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`). $shared_extensions = match (PHP_OS_FAMILY) { - 'Linux' => '', - 'Darwin' => '', + 'Linux' => 'snmp', + 'Darwin' => 'snmp', 'Windows' => '', }; From 8c8cb701743c90ca3be1f501fea87339d853efe7 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 6 Nov 2025 16:27:11 +0800 Subject: [PATCH 06/22] phpstan fix --- src/SPC/builder/Extension.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SPC/builder/Extension.php b/src/SPC/builder/Extension.php index 321537646..08b403e61 100644 --- a/src/SPC/builder/Extension.php +++ b/src/SPC/builder/Extension.php @@ -4,6 +4,7 @@ namespace SPC\builder; +use SPC\builder\unix\UnixBuilderBase; use SPC\exception\EnvironmentException; use SPC\exception\SPCException; use SPC\exception\ValidationException; @@ -454,7 +455,9 @@ public function buildUnixShared(): void if (!file_exists($soFile)) { throw new ValidationException("extension {$this->getName()} build failed: {$soFile} not found", validation_module: "Extension {$this->getName()} build"); } - $this->builder->deployBinary($soFile, $soFile, false); + /** @var UnixBuilderBase $builder */ + $builder = $this->builder; + $builder->deployBinary($soFile, $soFile, false); } /** From a45f314447a7004f5fb346a9b89332062248c060 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 6 Nov 2025 16:31:24 +0800 Subject: [PATCH 07/22] Remove for pure test --- src/globals/test-extensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/globals/test-extensions.php b/src/globals/test-extensions.php index 2caf56b00..62d7d4112 100644 --- a/src/globals/test-extensions.php +++ b/src/globals/test-extensions.php @@ -51,7 +51,7 @@ // If you want to test your added extensions and libs, add below (comma separated, example `bcmath,openssl`). $extensions = match (PHP_OS_FAMILY) { 'Linux', 'Darwin' => 'rdkafka', - 'Windows' => 'bcmath,bz2,calendar,ctype,zlib,zip', + 'Windows' => 'bcmath', }; // If you want to test shared extensions, add them below (comma separated, example `bcmath,openssl`). From f6b091498f51db2798f13f578c3be56f849453a9 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 6 Nov 2025 16:51:46 +0800 Subject: [PATCH 08/22] Fix missing debug link and debug option --- src/SPC/builder/unix/UnixBuilderBase.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 570d8093f..941b3f47f 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -101,11 +101,15 @@ public function extractDebugInfo(string $binary_path): string FileSystem::createDir($target_dir); $basename = basename($binary_path); $debug_file = "{$target_dir}/{$basename}" . (PHP_OS_FAMILY === 'Darwin' ? '.dwarf' : '.debug'); - shell()->exec(match (PHP_OS_FAMILY) { - 'Darwin' => "dsymutil -f {$binary_path} -o {$debug_file}", - 'Linux' => "objcopy ---only-keep-debug {$binary_path} {$debug_file}", - default => throw new SPCInternalException('extractDebugInfo is only supported on Linux and macOS'), - }); + if (PHP_OS_FAMILY === 'Darwin') { + shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}"); + } elseif (PHP_OS_FAMILY === 'Linux') { + shell() + ->exec("objcopy --only-keep-debug {$binary_path} {$debug_file}") + ->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}"); + } else { + throw new SPCInternalException('extractDebugInfo is only supported on Linux and macOS'); + } return $debug_file; } From f4b03ae8356dbd099ee3f5db20e4b10bbd64e002 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 9 Nov 2025 17:12:22 +0800 Subject: [PATCH 09/22] Introduce standalone DirDiff util class --- src/SPC/store/DirDiff.php | 95 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/SPC/store/DirDiff.php diff --git a/src/SPC/store/DirDiff.php b/src/SPC/store/DirDiff.php new file mode 100644 index 000000000..8cd5c1d40 --- /dev/null +++ b/src/SPC/store/DirDiff.php @@ -0,0 +1,95 @@ +reset(); + } + + /** + * Reset the baseline to current state. + */ + public function reset(): void + { + $this->before = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + + if ($this->track_content_changes) { + $this->before_file_hashes = []; + foreach ($this->before as $file) { + $this->before_file_hashes[$file] = md5_file($this->dir . DIRECTORY_SEPARATOR . $file); + } + } + } + + /** + * Get the list of incremented files. + * + * @param bool $relative Return relative paths or absolute paths + * @return array List of incremented files + */ + public function getIncrementFiles(bool $relative = false): array + { + $after = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + $diff = array_diff($after, $this->before); + if ($relative) { + return $diff; + } + return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $diff); + } + + /** + * Get the list of changed files (including new files). + * + * @param bool $relative Return relative paths or absolute paths + * @param bool $include_new_files Include new files as changed files + * @return array List of changed files + */ + public function getChangedFiles(bool $relative = false, bool $include_new_files = true): array + { + $after = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + $changed = []; + foreach ($after as $file) { + if (isset($this->before_file_hashes[$file])) { + $after_hash = md5_file($this->dir . DIRECTORY_SEPARATOR . $file); + if ($after_hash !== $this->before_file_hashes[$file]) { + $changed[] = $file; + } + } elseif ($include_new_files) { + // New file, consider as changed + $changed[] = $file; + } + } + if ($relative) { + return $changed; + } + return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $changed); + } + + /** + * Get the list of removed files. + * + * @param bool $relative Return relative paths or absolute paths + * @return array List of removed files + */ + public function getRemovedFiles(bool $relative = false): array + { + $after = FileSystem::scanDirFiles($this->dir, relative: true) ?: []; + $removed = array_diff($this->before, $after); + if ($relative) { + return $removed; + } + return array_map(fn ($f) => $this->dir . DIRECTORY_SEPARATOR . $f, $removed); + } +} From 987ad4b8461fbe3f04e3a11688dc8cadaa7288a9 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Sun, 9 Nov 2025 17:13:41 +0800 Subject: [PATCH 10/22] Use diff to detect and deploy-patch new shared extensions that built with php --- src/SPC/builder/linux/LinuxBuilder.php | 10 ++++++++++ src/SPC/builder/macos/MacOSBuilder.php | 17 +++++++++++++++++ src/SPC/builder/unix/UnixBuilderBase.php | 2 ++ 3 files changed, 29 insertions(+) diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 3f5d3b3c6..68db5688c 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -8,6 +8,7 @@ use SPC\exception\PatchException; use SPC\exception\WrongUsageException; use SPC\store\Config; +use SPC\store\DirDiff; use SPC\store\FileSystem; use SPC\store\SourcePatcher; use SPC\util\GlobalEnvManager; @@ -270,6 +271,9 @@ protected function buildEmbed(): void return Config::getExt($ext->getName(), 'build-with-php') === true; }); $install_modules = $sharedExts ? 'install-modules' : ''; + + // detect changes in module path + $diff = new DirDiff(BUILD_MODULES_PATH, true); $vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars()); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; shell()->cd(SOURCE_PATH . '/php-src') @@ -286,6 +290,12 @@ protected function buildEmbed(): void $this->processLibphpSoFile($libphpSo); } + // process shared extensions build-with-php + $increment_files = $diff->getChangedFiles(); + foreach ($increment_files as $increment_file) { + $this->deployBinary($increment_file, $increment_file, false); + } + // process libphp.a for static embed if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { $AR = getenv('AR') ?: 'ar'; diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index 204a712d0..849b350f9 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -8,6 +8,7 @@ use SPC\builder\unix\UnixBuilderBase; use SPC\exception\WrongUsageException; use SPC\store\Config; +use SPC\store\DirDiff; use SPC\store\FileSystem; use SPC\store\SourcePatcher; use SPC\util\GlobalEnvManager; @@ -262,9 +263,25 @@ protected function buildEmbed(): void $install_modules = $sharedExts ? 'install-modules' : ''; $vars = SystemUtil::makeEnvVarString($this->getMakeExtraVars()); $concurrency = getenv('SPC_CONCURRENCY') ? '-j' . getenv('SPC_CONCURRENCY') : ''; + + $diff = new DirDiff(BUILD_MODULES_PATH, true); + shell()->cd(SOURCE_PATH . '/php-src') + ->exec('sed -i "" "s|^EXTENSION_DIR = .*|EXTENSION_DIR = /' . basename(BUILD_MODULES_PATH) . '|" Makefile') ->exec("make {$concurrency} INSTALL_ROOT=" . BUILD_ROOT_PATH . " {$vars} install-sapi {$install_modules} install-build install-headers install-programs"); + $libphp = BUILD_LIB_PATH . '/libphp.dylib'; + if (file_exists($libphp)) { + $this->deployBinary($libphp, $libphp, false); + // macOS currently have no -release option for dylib, so we just rename it here + } + + // process shared extensions build-with-php + $increment_files = $diff->getChangedFiles(); + foreach ($increment_files as $increment_file) { + $this->deployBinary($increment_file, $increment_file, false); + } + if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'static') { $AR = getenv('AR') ?: 'ar'; f_passthru("{$AR} -t " . BUILD_LIB_PATH . "/libphp.a | grep '\\.a$' | xargs -n1 {$AR} d " . BUILD_LIB_PATH . '/libphp.a'); diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 941b3f47f..15488a67e 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -118,6 +118,8 @@ public function extractDebugInfo(string $binary_path): string */ public function deployBinary(string $src, string $dst, bool $executable = true): string { + logger()->debug('Deploying binary from ' . $src . ' to ' . $dst); + // UPX for linux $upx_option = (bool) $this->getOption('with-upx-pack', false); From 9edb9417a125c52239fc9038779a1d15ca667f4d Mon Sep 17 00:00:00 2001 From: DubbleClick Date: Fri, 5 Sep 2025 21:36:25 +0700 Subject: [PATCH 11/22] add --with-frankenphp-app option to embed an app # Conflicts: # config/lib.json # src/SPC/builder/unix/UnixBuilderBase.php --- config/env.ini | 2 +- config/lib.json | 10 +++- config/source.json | 10 ++++ src/SPC/builder/unix/UnixBuilderBase.php | 61 +++++++++++++++++++++--- src/SPC/command/BuildPHPCommand.php | 1 + 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/config/env.ini b/config/env.ini index 1047c3f70..cfda622e7 100644 --- a/config/env.ini +++ b/config/env.ini @@ -45,7 +45,7 @@ SPC_SKIP_PHP_VERSION_CHECK="no" ; Ignore some check item for bin/spc doctor command, comma separated (e.g. SPC_SKIP_DOCTOR_CHECK_ITEMS="if homebrew has installed") SPC_SKIP_DOCTOR_CHECK_ITEMS="" ; extra modules that xcaddy will include in the FrankenPHP build -SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/frankenphp/caddy --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" +SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" ; The display message for php version output (PHP >= 8.4 available) PHP_BUILD_PROVIDER="static-php-cli ${SPC_VERSION}" diff --git a/config/lib.json b/config/lib.json index 528777e17..640d4ea92 100644 --- a/config/lib.json +++ b/config/lib.json @@ -7,12 +7,14 @@ "source": "php-src", "lib-depends": [ "lib-base", - "micro" + "micro", + "frankenphp" ], "lib-depends-macos": [ "lib-base", "micro", - "libxml2" + "libxml2", + "frankenphp" ], "lib-suggests-linux": [ "libacl", @@ -968,5 +970,9 @@ "zstd.h", "zstd_errors.h" ] + }, + "frankenphp": { + "source": "frankenphp", + "type": "target" } } diff --git a/config/source.json b/config/source.json index ee3f8948b..4a1990815 100644 --- a/config/source.json +++ b/config/source.json @@ -363,6 +363,16 @@ "path": "LICENSE" } }, + "frankenphp": { + "type": "ghtar", + "repo": "php/frankenphp", + "prefer-stable": true, + "provide-pre-build": false, + "license": { + "type": "file", + "path": "LICENSE" + } + }, "icu-static-win": { "type": "url", "url": "https://dl.static-php.dev/static-php-cli/deps/icu-static-windows-x64/icu-static-windows-x64.zip", diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 15488a67e..0692d4a37 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -13,6 +13,7 @@ use SPC\store\Config; use SPC\store\FileSystem; use SPC\store\pkg\GoXcaddy; +use SPC\store\SourceManager; use SPC\toolchain\GccNativeToolchain; use SPC\toolchain\ToolchainManager; use SPC\util\DependencyUtil; @@ -344,22 +345,70 @@ protected function patchPhpScripts(): void } } + /** + * Process the --with-frankenphp-app option + * Creates app.tar and app.checksum in source/frankenphp directory + */ + protected function processFrankenphpApp(): void + { + $frankenphpSourceDir = SOURCE_PATH . '/frankenphp'; + SourceManager::initSource(['frankenphp'], ['frankenphp']); + $frankenphpAppPath = $this->getOption('with-frankenphp-app'); + + if ($frankenphpAppPath) { + if (!is_dir($frankenphpAppPath)) { + throw new WrongUsageException("The path provided to --with-frankenphp-app is not a valid directory: {$frankenphpAppPath}"); + } + $appTarPath = $frankenphpSourceDir . '/app.tar'; + logger()->info("Creating app.tar from {$frankenphpAppPath}"); + + shell()->exec('tar -cf ' . escapeshellarg($appTarPath) . ' -C ' . escapeshellarg($frankenphpAppPath) . ' .'); + + $checksum = hash_file('md5', $appTarPath); + file_put_contents($frankenphpSourceDir . '/app_checksum.txt', $checksum); + } else { + FileSystem::removeFileIfExists($frankenphpSourceDir . '/app.tar'); + FileSystem::removeFileIfExists($frankenphpSourceDir . '/app_checksum.txt'); + file_put_contents($frankenphpSourceDir . '/app.tar', ''); + file_put_contents($frankenphpSourceDir . '/app_checksum.txt', ''); + } + } + + protected function getFrankenPHPVersion(): string + { + $goModPath = SOURCE_PATH . '/frankenphp/caddy/go.mod'; + + if (!file_exists($goModPath)) { + throw new SPCInternalException("FrankenPHP caddy/go.mod file not found at {$goModPath}, why did we not download FrankenPHP?"); + } + + $content = file_get_contents($goModPath); + if (preg_match('/github\.com\/dunglas\/frankenphp\s+v?(\d+\.\d+\.\d+)/', $content, $matches)) { + return $matches[1]; + } + + throw new SPCInternalException('Could not find FrankenPHP version in caddy/go.mod'); + } + protected function buildFrankenphp(): void { GlobalEnvManager::addPathIfNotExists(GoXcaddy::getPath()); + $this->processFrankenphpApp(); $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); - // make it possible to build from a different frankenphp directory! - if (!str_contains($xcaddyModules, '--with github.com/dunglas/frankenphp')) { - $xcaddyModules = '--with github.com/dunglas/frankenphp ' . $xcaddyModules; - } + $frankenphpSourceDir = SOURCE_PATH . '/frankenphp'; + + $xcaddyModules = preg_replace('#--with github.com/dunglas/frankenphp(=\S+)?#', '', $xcaddyModules); + $xcaddyModules = preg_replace('#--with github.com/dunglas/frankenphp/caddy(=\S+)?#', '', $xcaddyModules); + $xcaddyModules = "--with github.com/dunglas/frankenphp={$frankenphpSourceDir} " . + "--with github.com/dunglas/frankenphp/caddy={$frankenphpSourceDir}/caddy {$xcaddyModules}"; if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { logger()->warning('caddy-cbrotli module is enabled, but brotli library is not built. Disabling caddy-cbrotli.'); $xcaddyModules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddyModules); } - [, $out] = shell()->execWithResult('go list -m github.com/dunglas/frankenphp@latest'); - $frankenPhpVersion = str_replace('github.com/dunglas/frankenphp v', '', $out[0]); + + $frankenPhpVersion = $this->getFrankenPHPVersion(); $libphpVersion = $this->getPHPVersion(); $dynamic_exports = ''; if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index b608b8f06..ad884b3ba 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -48,6 +48,7 @@ public function configure(): void $this->addOption('with-upx-pack', null, null, 'Compress / pack binary using UPX tool (linux/windows only)'); $this->addOption('with-micro-logo', null, InputOption::VALUE_REQUIRED, 'Use custom .ico for micro.sfx (windows only)'); $this->addOption('enable-micro-win32', null, null, 'Enable win32 mode for phpmicro (Windows only)'); + $this->addOption('with-frankenphp-app', null, InputOption::VALUE_REQUIRED, 'Path to a folder to be embedded in FrankenPHP'); } public function handle(): int From 081e2d284609f81821c825650394e2af6e429452 Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 28 Oct 2025 18:50:16 +0100 Subject: [PATCH 12/22] fix debugflags being backwards --- src/SPC/builder/unix/UnixBuilderBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 0692d4a37..0888f66e9 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -418,7 +418,7 @@ protected function buildFrankenphp(): void $dynamic_exports = ' ' . $dynamicSymbolsArgument; } } - $debugFlags = $this->getOption('no-strip') ? '-w -s ' : ''; + $debugFlags = $this->getOption('no-strip') ? '' : '-w -s '; $extLdFlags = "-extldflags '-pie{$dynamic_exports} {$this->arch_ld_flags}'"; $muslTags = ''; $staticFlags = ''; From fd2b7af1dcb24939dd203de37dd1487fa9031318 Mon Sep 17 00:00:00 2001 From: henderkes Date: Fri, 7 Nov 2025 11:06:50 +0100 Subject: [PATCH 13/22] make --with-frankenphp-app=dir work with docker scripts --- bin/spc-alpine-docker | 43 ++++++++++++++++++++++++++++++++++++++++++- bin/spc-gnu-docker | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/bin/spc-alpine-docker b/bin/spc-alpine-docker index 6365233c5..0d618743e 100755 --- a/bin/spc-alpine-docker +++ b/bin/spc-alpine-docker @@ -162,6 +162,47 @@ if [ ! -z "$GITHUB_TOKEN" ]; then ENV_LIST="$ENV_LIST -e GITHUB_TOKEN=$GITHUB_TOKEN" fi +# Intercept and rewrite --with-frankenphp-app option, and mount host path to /app/app +FRANKENPHP_APP_PATH="" +NEW_ARGS=() +while [ $# -gt 0 ]; do + case "$1" in + --with-frankenphp-app=*) + FRANKENPHP_APP_PATH="${1#*=}" + NEW_ARGS+=("--with-frankenphp-app=/app/app") + shift + ;; + --with-frankenphp-app) + if [ -n "${2:-}" ]; then + FRANKENPHP_APP_PATH="$2" + NEW_ARGS+=("--with-frankenphp-app=/app/app") + shift 2 + else + NEW_ARGS+=("$1") + shift + fi + ;; + *) + NEW_ARGS+=("$1") + shift + ;; + esac +done + +# Normalize the path and add mount if provided +if [ -n "$FRANKENPHP_APP_PATH" ]; then + # expand ~ to $HOME + if [ "${FRANKENPHP_APP_PATH#~}" != "$FRANKENPHP_APP_PATH" ]; then + FRANKENPHP_APP_PATH="$HOME${FRANKENPHP_APP_PATH#~}" + fi + # make absolute if relative + case "$FRANKENPHP_APP_PATH" in + /*) ABS_APP_PATH="$FRANKENPHP_APP_PATH" ;; + *) ABS_APP_PATH="$(pwd)/$FRANKENPHP_APP_PATH" ;; + esac + MOUNT_LIST="$MOUNT_LIST -v \"$ABS_APP_PATH\":/app/app" +fi + # Run docker # shellcheck disable=SC2068 # shellcheck disable=SC2086 @@ -183,5 +224,5 @@ if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then set -ex $DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION /bin/bash else - $DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION bin/spc $@ + $DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST $MOUNT_LIST cwcc-spc-$SPC_USE_ARCH-$SPC_DOCKER_VERSION bin/spc "${NEW_ARGS[@]}" fi diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index 7fcf5d410..29c68adef 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -174,6 +174,47 @@ if [ ! -z "$GITHUB_TOKEN" ]; then ENV_LIST="$ENV_LIST -e GITHUB_TOKEN=$GITHUB_TOKEN" fi +# Intercept and rewrite --with-frankenphp-app option, and mount host path to /app/app +FRANKENPHP_APP_PATH="" +NEW_ARGS=() +while [ $# -gt 0 ]; do + case "$1" in + --with-frankenphp-app=*) + FRANKENPHP_APP_PATH="${1#*=}" + NEW_ARGS+=("--with-frankenphp-app=/app/app") + shift + ;; + --with-frankenphp-app) + if [ -n "${2:-}" ]; then + FRANKENPHP_APP_PATH="$2" + NEW_ARGS+=("--with-frankenphp-app=/app/app") + shift 2 + else + NEW_ARGS+=("$1") + shift + fi + ;; + *) + NEW_ARGS+=("$1") + shift + ;; + esac +done + +# Normalize the path and add mount if provided +if [ -n "$FRANKENPHP_APP_PATH" ]; then + # expand ~ to $HOME + if [ "${FRANKENPHP_APP_PATH#~}" != "$FRANKENPHP_APP_PATH" ]; then + FRANKENPHP_APP_PATH="$HOME${FRANKENPHP_APP_PATH#~}" + fi + # make absolute if relative + case "$FRANKENPHP_APP_PATH" in + /*) ABS_APP_PATH="$FRANKENPHP_APP_PATH" ;; + *) ABS_APP_PATH="$(pwd)/$FRANKENPHP_APP_PATH" ;; + esac + MOUNT_LIST="$MOUNT_LIST -v \"$ABS_APP_PATH\":/app/app" +fi + # Run docker # shellcheck disable=SC2068 # shellcheck disable=SC2086 @@ -196,5 +237,5 @@ if [ "$SPC_DOCKER_DEBUG" = "yes" ]; then set -ex $DOCKER_EXECUTABLE run $PLATFORM_ARG --privileged --rm -it $INTERACT $ENV_LIST --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION /bin/bash else - $DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION bin/spc $@ + $DOCKER_EXECUTABLE run $PLATFORM_ARG --rm $INTERACT $ENV_LIST --env-file /tmp/spc-gnu-docker.env $MOUNT_LIST cwcc-spc-gnu-$SPC_USE_ARCH-$SPC_DOCKER_VERSION bin/spc "${NEW_ARGS[@]}" fi From c91128995d63e5533c2ad40b5b203c5f656b15bb Mon Sep 17 00:00:00 2001 From: henderkes Date: Sat, 8 Nov 2025 09:40:16 +0100 Subject: [PATCH 14/22] update freetype download to get latest version --- config/source.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/source.json b/config/source.json index 4a1990815..ca1edfb02 100644 --- a/config/source.json +++ b/config/source.json @@ -302,9 +302,9 @@ } }, "freetype": { - "type": "git", - "rev": "VER-2-13-2", - "url": "https://github.com/freetype/freetype", + "type": "ghtagtar", + "repo": "freetype/freetype", + "match": "VER-2-\\d+-\\d+", "license": { "type": "file", "path": "LICENSE.TXT" From 541889d17b2147508f0a947aa7215772c23c1b1f Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 9 Nov 2025 10:01:19 +0100 Subject: [PATCH 15/22] update to rc4 --- src/SPC/store/source/PhpSource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index 1c0be85fb..e0b3ba279 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -16,7 +16,7 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = { $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; if ($major === '8.5') { - Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~daniels/php-8.5.0RC3.tar.xz'], $force); + Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~edorian/php-8.5.0RC4.tar.xz'], $force); } elseif ($major === 'git') { Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); } else { From 64079d93311d7cddf5a84f82b6ef46389a53955c Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 9 Nov 2025 23:35:49 +0100 Subject: [PATCH 16/22] simplify regex --- src/SPC/builder/unix/UnixBuilderBase.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 0888f66e9..429dace51 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -399,8 +399,7 @@ protected function buildFrankenphp(): void $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); $frankenphpSourceDir = SOURCE_PATH . '/frankenphp'; - $xcaddyModules = preg_replace('#--with github.com/dunglas/frankenphp(=\S+)?#', '', $xcaddyModules); - $xcaddyModules = preg_replace('#--with github.com/dunglas/frankenphp/caddy(=\S+)?#', '', $xcaddyModules); + $xcaddyModules = preg_replace('#--with github.com/dunglas/frankenphp\S*#', '', $xcaddyModules); $xcaddyModules = "--with github.com/dunglas/frankenphp={$frankenphpSourceDir} " . "--with github.com/dunglas/frankenphp/caddy={$frankenphpSourceDir}/caddy {$xcaddyModules}"; if ($this->getLib('brotli') === null && str_contains($xcaddyModules, '--with github.com/dunglas/caddy-cbrotli')) { From cff6ec17ea4dfa6de6dbfb4464855d67ff13d094 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 10 Nov 2025 13:26:17 +0800 Subject: [PATCH 17/22] Remove escape backslashes --- bin/spc-alpine-docker | 2 +- bin/spc-gnu-docker | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/spc-alpine-docker b/bin/spc-alpine-docker index 0d618743e..2790a5c34 100755 --- a/bin/spc-alpine-docker +++ b/bin/spc-alpine-docker @@ -200,7 +200,7 @@ if [ -n "$FRANKENPHP_APP_PATH" ]; then /*) ABS_APP_PATH="$FRANKENPHP_APP_PATH" ;; *) ABS_APP_PATH="$(pwd)/$FRANKENPHP_APP_PATH" ;; esac - MOUNT_LIST="$MOUNT_LIST -v \"$ABS_APP_PATH\":/app/app" + MOUNT_LIST="$MOUNT_LIST -v $ABS_APP_PATH:/app/app" fi # Run docker diff --git a/bin/spc-gnu-docker b/bin/spc-gnu-docker index 29c68adef..68f85109f 100755 --- a/bin/spc-gnu-docker +++ b/bin/spc-gnu-docker @@ -212,7 +212,7 @@ if [ -n "$FRANKENPHP_APP_PATH" ]; then /*) ABS_APP_PATH="$FRANKENPHP_APP_PATH" ;; *) ABS_APP_PATH="$(pwd)/$FRANKENPHP_APP_PATH" ;; esac - MOUNT_LIST="$MOUNT_LIST -v \"$ABS_APP_PATH\":/app/app" + MOUNT_LIST="$MOUNT_LIST -v $ABS_APP_PATH:/app/app" fi # Run docker From 09073c5517dbabd71bdec5e132201339d5f84e18 Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 11 Nov 2025 15:51:28 +0100 Subject: [PATCH 18/22] sort config and remove lonesome configure cflags --- config/env.ini | 10 +++------- config/lib.json | 8 ++++---- config/source.json | 20 ++++++++++---------- src/SPC/builder/linux/LinuxBuilder.php | 6 +++--- src/SPC/builder/macos/MacOSBuilder.php | 2 +- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/config/env.ini b/config/env.ini index cfda622e7..7448cc373 100644 --- a/config/env.ini +++ b/config/env.ini @@ -110,9 +110,7 @@ SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --disable ; *** default build vars for building php *** ; embed type for php, static (libphp.a) or shared (libphp.so) SPC_CMD_VAR_PHP_EMBED_TYPE="static" -; CFLAGS for configuring php -SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -fPIE" -; EXTRA_CFLAGS for `make` php +; EXTRA_CFLAGS for `configure` and `make` php SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fno-ident -fPIE ${SPC_DEFAULT_C_FLAGS}" ; EXTRA_LDFLAGS for `make` php, can use -release to set a soname for libphp.so SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS="" @@ -142,10 +140,8 @@ SPC_CMD_PREFIX_PHP_CONFIGURE="./configure --prefix= --with-valgrind=no --enable- ; *** default build vars for building php *** ; embed type for php, static (libphp.a) or shared (libphp.dylib) SPC_CMD_VAR_PHP_EMBED_TYPE="static" -; CFLAGS for configuring php -SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS="${SPC_DEFAULT_C_FLAGS} -Werror=unknown-warning-option" -; EXTRA_CFLAGS for `make` php -SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie ${SPC_DEFAULT_C_FLAGS}" +; EXTRA_CFLAGS for `configure` and `make` php +SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="-g -fstack-protector-strong -fpic -fpie -Werror=unknown-warning-option ${SPC_DEFAULT_C_FLAGS}" [freebsd] ; compiler environments diff --git a/config/lib.json b/config/lib.json index 640d4ea92..aa7eb4996 100644 --- a/config/lib.json +++ b/config/lib.json @@ -26,6 +26,10 @@ "watcher" ] }, + "frankenphp": { + "source": "frankenphp", + "type": "target" + }, "micro": { "type": "target", "source": "micro" @@ -970,9 +974,5 @@ "zstd.h", "zstd_errors.h" ] - }, - "frankenphp": { - "source": "frankenphp", - "type": "target" } } diff --git a/config/source.json b/config/source.json index ca1edfb02..fa16651cd 100644 --- a/config/source.json +++ b/config/source.json @@ -301,6 +301,16 @@ "path": "LICENSE.MIT" } }, + "frankenphp": { + "type": "ghtar", + "repo": "php/frankenphp", + "prefer-stable": true, + "provide-pre-built": false, + "license": { + "type": "file", + "path": "LICENSE" + } + }, "freetype": { "type": "ghtagtar", "repo": "freetype/freetype", @@ -363,16 +373,6 @@ "path": "LICENSE" } }, - "frankenphp": { - "type": "ghtar", - "repo": "php/frankenphp", - "prefer-stable": true, - "provide-pre-build": false, - "license": { - "type": "file", - "path": "LICENSE" - } - }, "icu-static-win": { "type": "url", "url": "https://dl.static-php.dev/static-php-cli/deps/icu-static-windows-x64/icu-static-windows-x64.zip", diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index 68db5688c..0d6f77fba 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -91,7 +91,7 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void // prepare build php envs // $musl_flag = SPCTarget::getLibc() === 'musl' ? ' -D__MUSL__' : ' -U__MUSL__'; $php_configure_env = SystemUtil::makeEnvVarString([ - 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'), + 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'CPPFLAGS' => '-I' . BUILD_INCLUDE_PATH, // . ' -Dsomethinghere', // . $musl_flag, 'LDFLAGS' => '-L' . BUILD_LIB_PATH, // 'LIBS' => SPCTarget::getRuntimeLibs(), // do not pass static libraries here yet, they may contain polyfills for libc functions! @@ -284,10 +284,10 @@ protected function buildEmbed(): void // process libphp.so for shared embed $libphpSo = BUILD_LIB_PATH . '/libphp.so'; if (file_exists($libphpSo)) { - // deploy libphp.so - $this->deployBinary($libphpSo, $libphpSo, false); // post actions: rename libphp.so to libphp-.so if -release is set in LDFLAGS $this->processLibphpSoFile($libphpSo); + // deploy libphp.so + $this->deployBinary($libphpSo, $libphpSo, false); } // process shared extensions build-with-php diff --git a/src/SPC/builder/macos/MacOSBuilder.php b/src/SPC/builder/macos/MacOSBuilder.php index 849b350f9..07ef275d6 100644 --- a/src/SPC/builder/macos/MacOSBuilder.php +++ b/src/SPC/builder/macos/MacOSBuilder.php @@ -106,7 +106,7 @@ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void // prepare build php envs $envs_build_php = SystemUtil::makeEnvVarString([ - 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS'), + 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), 'CPPFLAGS' => '-I' . BUILD_INCLUDE_PATH, 'LDFLAGS' => '-L' . BUILD_LIB_PATH, ]); From e2b80e7f03439a8f2eb180133bc671019f56812c Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 11 Nov 2025 15:53:44 +0100 Subject: [PATCH 19/22] update building of frankenphp --- src/SPC/builder/unix/UnixBuilderBase.php | 30 +++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index 429dace51..fac3ca40e 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -105,9 +105,16 @@ public function extractDebugInfo(string $binary_path): string if (PHP_OS_FAMILY === 'Darwin') { shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}"); } elseif (PHP_OS_FAMILY === 'Linux') { - shell() - ->exec("objcopy --only-keep-debug {$binary_path} {$debug_file}") - ->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}"); + $has_eu_strip = shell()->execWithResult('which eu-strip')[0] === 0; + if ($has_eu_strip) { + shell() + ->exec("eu-strip -f {$debug_file} {$binary_path}") + ->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}"); + } else { + shell() + ->exec("objcopy --only-keep-debug {$binary_path} {$debug_file}") + ->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}"); + } } else { throw new SPCInternalException('extractDebugInfo is only supported on Linux and macOS'); } @@ -351,8 +358,10 @@ protected function patchPhpScripts(): void */ protected function processFrankenphpApp(): void { - $frankenphpSourceDir = SOURCE_PATH . '/frankenphp'; - SourceManager::initSource(['frankenphp'], ['frankenphp']); + $frankenphpSourceDir = getenv('FRANKENPHP_SOURCE_PATH') ?: SOURCE_PATH . '/frankenphp'; + if (!is_dir($frankenphpSourceDir)) { + SourceManager::initSource(['frankenphp'], ['frankenphp']); + } $frankenphpAppPath = $this->getOption('with-frankenphp-app'); if ($frankenphpAppPath) { @@ -376,7 +385,11 @@ protected function processFrankenphpApp(): void protected function getFrankenPHPVersion(): string { - $goModPath = SOURCE_PATH . '/frankenphp/caddy/go.mod'; + if ($version = getenv('FRANKENPHP_VERSION')) { + return $version; + } + $frankenphpSourceDir = getenv('FRANKENPHP_SOURCE_PATH') ?: SOURCE_PATH . '/frankenphp'; + $goModPath = $frankenphpSourceDir . '/caddy/go.mod'; if (!file_exists($goModPath)) { throw new SPCInternalException("FrankenPHP caddy/go.mod file not found at {$goModPath}, why did we not download FrankenPHP?"); @@ -397,7 +410,7 @@ protected function buildFrankenphp(): void $nobrotli = $this->getLib('brotli') === null ? ',nobrotli' : ''; $nowatcher = $this->getLib('watcher') === null ? ',nowatcher' : ''; $xcaddyModules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); - $frankenphpSourceDir = SOURCE_PATH . '/frankenphp'; + $frankenphpSourceDir = getenv('FRANKENPHP_SOURCE_PATH') ?: SOURCE_PATH . '/frankenphp'; $xcaddyModules = preg_replace('#--with github.com/dunglas/frankenphp\S*#', '', $xcaddyModules); $xcaddyModules = "--with github.com/dunglas/frankenphp={$frankenphpSourceDir} " . @@ -417,7 +430,6 @@ protected function buildFrankenphp(): void $dynamic_exports = ' ' . $dynamicSymbolsArgument; } } - $debugFlags = $this->getOption('no-strip') ? '' : '-w -s '; $extLdFlags = "-extldflags '-pie{$dynamic_exports} {$this->arch_ld_flags}'"; $muslTags = ''; $staticFlags = ''; @@ -442,7 +454,7 @@ protected function buildFrankenphp(): void 'CGO_CFLAGS' => clean_spaces($cflags), 'CGO_LDFLAGS' => "{$this->arch_ld_flags} {$staticFlags} {$config['ldflags']} {$libs}", 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . - '-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . $debugFlags . + '-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . '-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' . "v{$frankenPhpVersion} PHP {$libphpVersion} Caddy'\\\" " . "-tags={$muslTags}nobadger,nomysql,nopgx{$nobrotli}{$nowatcher}", From 8e4d4b7be53adcf942ed1a034f7f8d1502252886 Mon Sep 17 00:00:00 2001 From: henderkes Date: Tue, 11 Nov 2025 16:51:30 +0100 Subject: [PATCH 20/22] suggestion --- src/SPC/builder/unix/UnixBuilderBase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index fac3ca40e..d8f3ae2b1 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -5,6 +5,7 @@ namespace SPC\builder\unix; use SPC\builder\BuilderBase; +use SPC\builder\linux\SystemUtil; use SPC\builder\linux\SystemUtil as LinuxSystemUtil; use SPC\exception\SPCException; use SPC\exception\SPCInternalException; @@ -105,10 +106,9 @@ public function extractDebugInfo(string $binary_path): string if (PHP_OS_FAMILY === 'Darwin') { shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}"); } elseif (PHP_OS_FAMILY === 'Linux') { - $has_eu_strip = shell()->execWithResult('which eu-strip')[0] === 0; - if ($has_eu_strip) { + if ($eu_strip = SystemUtil::findCommand('eu-strip')) { shell() - ->exec("eu-strip -f {$debug_file} {$binary_path}") + ->exec("{$eu_strip} -f {$debug_file} {$binary_path}") ->exec("objcopy --add-gnu-debuglink={$debug_file} {$binary_path}"); } else { shell() From 23c0d6f4aa88034286c5eef51dc16b4b39a607f1 Mon Sep 17 00:00:00 2001 From: henderkes Date: Wed, 12 Nov 2025 10:11:29 +0100 Subject: [PATCH 21/22] simplify deployBinary a little bit --- src/SPC/builder/unix/UnixBuilderBase.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/SPC/builder/unix/UnixBuilderBase.php b/src/SPC/builder/unix/UnixBuilderBase.php index d8f3ae2b1..d98059cb0 100644 --- a/src/SPC/builder/unix/UnixBuilderBase.php +++ b/src/SPC/builder/unix/UnixBuilderBase.php @@ -128,9 +128,6 @@ public function deployBinary(string $src, string $dst, bool $executable = true): { logger()->debug('Deploying binary from ' . $src . ' to ' . $dst); - // UPX for linux - $upx_option = (bool) $this->getOption('with-upx-pack', false); - // file must exists if (!file_exists($src)) { throw new SPCInternalException("Deploy failed. Cannot find file: {$src}"); @@ -152,13 +149,14 @@ public function deployBinary(string $src, string $dst, bool $executable = true): $this->extractDebugInfo($dst); // strip - if (!$this->getOption('no-strip', false)) { + if (!$this->getOption('no-strip')) { $this->stripBinary($dst); } - // Compress binary with UPX if needed (only for Linux) + // UPX for linux + $upx_option = $this->getOption('with-upx-pack'); if ($upx_option && PHP_OS_FAMILY === 'Linux' && $executable) { - if ($this->getOption('no-strip', false)) { + if ($this->getOption('no-strip')) { logger()->warning('UPX compression is not recommended when --no-strip is enabled.'); } logger()->info("Compressing {$dst} with UPX"); From d69826eb4a19170662fc0cde02bfce4f938e07e6 Mon Sep 17 00:00:00 2001 From: henderkes Date: Sun, 16 Nov 2025 11:30:40 +0100 Subject: [PATCH 22/22] update php 8.5 to RC5 --- src/SPC/store/source/PhpSource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SPC/store/source/PhpSource.php b/src/SPC/store/source/PhpSource.php index e0b3ba279..80d99c9b9 100644 --- a/src/SPC/store/source/PhpSource.php +++ b/src/SPC/store/source/PhpSource.php @@ -16,7 +16,7 @@ public function fetch(bool $force = false, ?array $config = null, int $lock_as = { $major = defined('SPC_BUILD_PHP_VERSION') ? SPC_BUILD_PHP_VERSION : '8.4'; if ($major === '8.5') { - Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~edorian/php-8.5.0RC4.tar.xz'], $force); + Downloader::downloadSource('php-src', ['type' => 'url', 'url' => 'https://downloads.php.net/~daniels/php-8.5.0RC5.tar.xz'], $force); } elseif ($major === 'git') { Downloader::downloadSource('php-src', ['type' => 'git', 'url' => 'https://github.com/php/php-src.git', 'rev' => 'master'], $force); } else {