diff --git a/composer.lock b/composer.lock index 40489a06..eded86ef 100644 --- a/composer.lock +++ b/composer.lock @@ -8,30 +8,30 @@ "packages": [ { "name": "laravel/prompts", - "version": "v0.3.11", + "version": "v0.3.12", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "dd2a2ed95acacbcccd32fd98dee4c946ae7a7217" + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/dd2a2ed95acacbcccd32fd98dee4c946ae7a7217", - "reference": "dd2a2ed95acacbcccd32fd98dee4c946ae7a7217", + "url": "https://api.github.com/repos/laravel/prompts/zipball/4861ded9003b7f8a158176a0b7666f74ee761be8", + "reference": "4861ded9003b7f8a158176a0b7666f74ee761be8", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "ext-mbstring": "*", "php": "^8.1", - "symfony/console": "^6.2|^7.0" + "symfony/console": "^6.2|^7.0|^8.0" }, "conflict": { "illuminate/console": ">=10.17.0 <10.25.0", "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "illuminate/collections": "^10.0|^11.0|^12.0", + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3|^3.4|^4.0", "phpstan/phpstan": "^1.12.28", @@ -61,33 +61,33 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.11" + "source": "https://github.com/laravel/prompts/tree/v0.3.12" }, - "time": "2026-01-27T02:55:06+00:00" + "time": "2026-02-03T06:57:26+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.8", + "version": "v2.0.9", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b" + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7581a4407012f5f53365e11bafc520fd7f36bc9b", - "reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/8f631589ab07b7b52fead814965f5a800459cb3e", + "reference": "8f631589ab07b7b52fead814965f5a800459cb3e", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", "nesbot/carbon": "^2.67|^3.0", "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/phpstan": "^2.0", - "symfony/var-dumper": "^6.2.0|^7.0.0" + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" }, "type": "library", "extra": { @@ -124,20 +124,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-01-08T16:22:46+00:00" + "time": "2026-02-03T06:55:34+00:00" }, { "name": "nette/php-generator", - "version": "v4.2.0", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/nette/php-generator.git", - "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac" + "reference": "52aff4d9b12f20ca9f3e31a559b646d2fd21dd61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/php-generator/zipball/4707546a1f11badd72f5d82af4f8a6bc64bd56ac", - "reference": "4707546a1f11badd72f5d82af4f8a6bc64bd56ac", + "url": "https://api.github.com/repos/nette/php-generator/zipball/52aff4d9b12f20ca9f3e31a559b646d2fd21dd61", + "reference": "52aff4d9b12f20ca9f3e31a559b646d2fd21dd61", "shasum": "" }, "require": { @@ -146,9 +146,9 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.2", - "nette/tester": "^2.4", + "nette/tester": "^2.6", "nikic/php-parser": "^5.0", - "phpstan/phpstan-nette": "^2.0@stable", + "phpstan/phpstan": "^2.0@stable", "tracy/tracy": "^2.8" }, "suggest": { @@ -194,22 +194,22 @@ ], "support": { "issues": "https://github.com/nette/php-generator/issues", - "source": "https://github.com/nette/php-generator/tree/v4.2.0" + "source": "https://github.com/nette/php-generator/tree/v4.2.1" }, - "time": "2025-08-06T18:24:31+00:00" + "time": "2026-02-09T05:43:31+00:00" }, { "name": "nette/utils", - "version": "v4.1.1", + "version": "v4.1.2", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72" + "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72", - "reference": "c99059c0315591f1a0db7ad6002000288ab8dc72", + "url": "https://api.github.com/repos/nette/utils/zipball/f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", + "reference": "f76b5dc3d6c6d3043c8d937df2698515b99cbaf5", "shasum": "" }, "require": { @@ -222,7 +222,7 @@ "require-dev": { "jetbrains/phpstorm-attributes": "^1.2", "nette/tester": "^2.5", - "phpstan/phpstan-nette": "^2.0@stable", + "phpstan/phpstan": "^2.0@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -283,9 +283,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.1" + "source": "https://github.com/nette/utils/tree/v4.1.2" }, - "time": "2025-12-22T12:14:32+00:00" + "time": "2026-02-03T17:21:09+00:00" }, { "name": "php-di/invoker", @@ -2217,7 +2217,7 @@ }, { "name": "captainhook/captainhook-phar", - "version": "5.27.5", + "version": "5.28.0", "source": { "type": "git", "url": "https://github.com/captainhook-git/captainhook-phar.git", @@ -2271,7 +2271,7 @@ ], "support": { "issues": "https://github.com/captainhook-git/captainhook/issues", - "source": "https://github.com/captainhook-git/captainhook-phar/tree/5.27.5" + "source": "https://github.com/captainhook-git/captainhook-phar/tree/5.28.0" }, "funding": [ { @@ -2660,29 +2660,29 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.5", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", - "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "conflict": { - "phpunit/phpunit": "<=7.5 || >=13" + "phpunit/phpunit": "<=7.5 || >=14" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12 || ^13", - "phpstan/phpstan": "1.4.10 || 2.1.11", + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", "phpstan/phpstan-phpunit": "^1.0 || ^2", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", "psr/log": "^1 || ^2 || ^3" }, "suggest": { @@ -2702,9 +2702,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" }, - "time": "2025-04-07T20:06:18+00:00" + "time": "2026-02-07T07:09:04+00:00" }, { "name": "evenement/evenement", diff --git a/config/pkg/ext/ext-phar.yml b/config/pkg/ext/ext-phar.yml new file mode 100644 index 00000000..3625d2c0 --- /dev/null +++ b/config/pkg/ext/ext-phar.yml @@ -0,0 +1,4 @@ +ext-phar: + type: php-extension + depends: + - zlib diff --git a/config/pkg/target/frankenphp.yml b/config/pkg/target/frankenphp.yml new file mode 100644 index 00000000..96f9c082 --- /dev/null +++ b/config/pkg/target/frankenphp.yml @@ -0,0 +1,18 @@ +frankenphp: + type: target + artifact: + source: + type: ghtar + repo: php/frankenphp + prefer-stable: true + metadata: + license-files: [LICENSE] + license: MIT + depends: + - php-embed + - go-xcaddy + suggests@unix: + - brotli + - watcher + static-bins@unix: + - frankenphp diff --git a/config/pkg/target/php.yml b/config/pkg/target/php.yml index 8f88cb58..d45e509e 100644 --- a/config/pkg/target/php.yml +++ b/config/pkg/target/php.yml @@ -1,16 +1,3 @@ -frankenphp: - type: virtual-target - artifact: - source: - type: ghtar - repo: php/frankenphp - prefer-stable: true - metadata: - license-files: [LICENSE] - license: MIT - depends: - - php-embed - - go-xcaddy php: type: target artifact: php-src @@ -32,6 +19,8 @@ php-fpm: type: virtual-target depends: - php + suggests@linux: + - libacl php-micro: type: virtual-target artifact: diff --git a/src/Package/Extension/phar.php b/src/Package/Extension/phar.php new file mode 100644 index 00000000..dd9b1dff --- /dev/null +++ b/src/Package/Extension/phar.php @@ -0,0 +1,29 @@ +getSourceDir()}/Makefile", 'OVERALL_TARGET =', 'OVERALL_TARGET = libphp.la'); } + + #[BeforeStage('php', [php::class, 'makeForUnix'], 'php-micro')] + #[PatchDescription('Patch Makefile to skip installing micro binary')] + public function patchMakefileBeforeUnixMake(TargetPackage $package): void + { + FileSystem::replaceFileStr("{$package->getSourceDir()}/Makefile", 'install-micro', ''); + } } diff --git a/src/Package/Target/php.php b/src/Package/Target/php.php index 239fcf43..c21ae590 100644 --- a/src/Package/Target/php.php +++ b/src/Package/Target/php.php @@ -74,6 +74,34 @@ class php extends TargetPackage throw new WrongUsageException('PHP version file format is malformed, please remove "./source/php-src" dir and download/extract again'); } + /** + * Get PHP version from php_version.h + * + * @param null|string $from_custom_source Where to read php_version.h from custom source + * @param bool $return_null_if_failed Whether to return null if failed to get version + * @return null|string PHP version (e.g., "8.4.0") or null if failed + */ + public static function getPHPVersion(?string $from_custom_source = null, bool $return_null_if_failed = false): ?string + { + $source_dir = $from_custom_source ?? ArtifactLoader::getArtifactInstance('php-src')->getSourceDir(); + if (!file_exists("{$source_dir}/main/php_version.h")) { + if ($return_null_if_failed) { + return null; + } + throw new WrongUsageException('PHP source files are not available, you need to download them first'); + } + + $file = file_get_contents("{$source_dir}/main/php_version.h"); + if (preg_match('/PHP_VERSION "(.*)"/', $file, $match) !== 0) { + return $match[1]; + } + + if ($return_null_if_failed) { + return null; + } + throw new WrongUsageException('PHP version file format is malformed, please remove "./source/php-src" dir and download/extract again'); + } + #[InitPackage] public function init(TargetPackage $package): void { @@ -222,6 +250,8 @@ class php extends TargetPackage 'Static Extensions (' . count($static_extensions) . ')' => implode(',', array_map(fn ($x) => substr($x->getName(), 4), $static_extensions)), 'Shared Extensions (' . count($shared_extensions) . ')' => implode(',', $shared_extensions), 'Install Packages (' . count($install_packages) . ')' => implode(',', array_map(fn ($x) => $x->getName(), $install_packages)), + 'Strip Binaries' => $package->getBuildOption('no-strip') ? 'No' : 'Yes', + 'Enable ZTS' => $package->getBuildOption('enable-zts') ? 'Yes' : 'No', ]; } @@ -236,7 +266,7 @@ class php extends TargetPackage logger()->info("Adding hardcoded INI [{$source_name} = {$ini_value}]"); } if (!empty($custom_ini)) { - SourcePatcher::patchHardcodedINI($package->getSourceDir(), $custom_ini); + ApplicationContext::invoke([SourcePatcher::class, 'patchHardcodedINI'], [$package->getSourceDir(), $custom_ini]); } // Patch StaticPHP version diff --git a/src/Package/Target/php/frankenphp.php b/src/Package/Target/php/frankenphp.php new file mode 100644 index 00000000..06687090 --- /dev/null +++ b/src/Package/Target/php/frankenphp.php @@ -0,0 +1,155 @@ +runStage([$this, 'processFrankenphpApp']); + + // modules + $no_brotli = $installer->isPackageResolved('brotli') ? '' : ',nobrotli'; + $no_watcher = $installer->isPackageResolved('watcher') ? '' : ',nowatcher'; + $xcaddy_modules = getenv('SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES'); // from env.ini + $source_dir = $package->getSourceDir(); + + $xcaddy_modules = preg_replace('#--with github.com/dunglas/frankenphp\S*#', '', $xcaddy_modules); + $xcaddy_modules = "--with github.com/dunglas/frankenphp={$source_dir} " . + "--with github.com/dunglas/frankenphp/caddy={$source_dir}/caddy {$xcaddy_modules}"; + + // disable caddy-cbrotli if brotli is not built + if (!$installer->isPackageResolved('brotli') && str_contains($xcaddy_modules, '--with github.com/dunglas/caddy-cbrotli')) { + logger()->warning('caddy-cbrotli module is enabled, but brotli library is not built. Disabling caddy-cbrotli.'); + $xcaddy_modules = str_replace('--with github.com/dunglas/caddy-cbrotli', '', $xcaddy_modules); + } + + $frankenphp_version = $this->getFrankenPHPVersion($package); + $libphp_version = php::getPHPVersion(); + $dynamic_exports = ''; + if (getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') === 'shared') { + $libphp_version = preg_replace('/\.\d+$/', '', $libphp_version); + } elseif ($dynamicSymbolsArgument = LinuxUtil::getDynamicExportedSymbols(BUILD_LIB_PATH . '/libphp.a')) { + $dynamic_exports = ' ' . $dynamicSymbolsArgument; + } + + // full-static build flags + if ($toolchain->isStatic()) { + $extLdFlags = "-extldflags '-static-pie -Wl,-z,stack-size=0x80000{$dynamic_exports} {$package->getLibExtraLdFlags()}'"; + $muslTags = 'static_build,'; + $staticFlags = '-static-pie'; + } else { + $extLdFlags = "-extldflags '-pie{$dynamic_exports} {$package->getLibExtraLdFlags()}'"; + $muslTags = ''; + $staticFlags = ''; + } + + $resolved = array_keys($installer->getResolvedPackages()); + // remove self from deps + $resolved = array_filter($resolved, fn ($pkg_name) => $pkg_name !== $package->getName()); + $config = new SPCConfigUtil()->config($resolved); + $cflags = "{$package->getLibExtraCFlags()} {$config['cflags']} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS') . " -DFRANKENPHP_VERSION={$frankenphp_version}"; + $libs = $config['libs']; + + // Go's gcc driver doesn't automatically link against -lgcov or -lrt. Ugly, but necessary fix. + if ((str_contains((string) getenv('SPC_DEFAULT_C_FLAGS'), '-fprofile') || + str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), '-fprofile')) && + $toolchain instanceof GccNativeToolchain) { + $cflags .= ' -Wno-error=missing-profile'; + $libs .= ' -lgcov'; + } + + $env = [ + 'CGO_ENABLED' => '1', + 'CGO_CFLAGS' => clean_spaces($cflags), + 'CGO_LDFLAGS' => "{$package->getLibExtraLdFlags()} {$staticFlags} {$config['ldflags']} {$libs}", + 'XCADDY_GO_BUILD_FLAGS' => '-buildmode=pie ' . + '-ldflags \"-linkmode=external ' . $extLdFlags . ' ' . + '-X \'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ' . + "v{$frankenphp_version} PHP {$libphp_version} Caddy'\\\" " . + "-tags={$muslTags}nobadger,nomysql,nopgx{$no_brotli}{$no_watcher}", + 'LD_LIBRARY_PATH' => BUILD_LIB_PATH, + ]; + InteractiveTerm::setMessage('Building frankenphp: ' . ConsoleColor::yellow('building with xcaddy')); + shell()->cd(BUILD_LIB_PATH) + ->setEnv($env) + ->exec("xcaddy build --output frankenphp {$xcaddy_modules}"); + + $builder->deployBinary(BUILD_LIB_PATH . '/frankenphp', BUILD_BIN_PATH . '/frankenphp'); + $package->setOutput('Binary path for FrankenPHP SAPI', BUILD_BIN_PATH . '/frankenphp'); + } + + /** + * Process the --with-frankenphp-app option + * Creates app.tar and app.checksum in source/frankenphp directory + */ + #[Stage] + public function processFrankenphpApp(TargetPackage $package): void + { + $frankenphpSourceDir = $package->getSourceDir(); + + $frankenphpAppPath = $package->getBuildOption('with-frankenphp-app'); + + if ($frankenphpAppPath) { + InteractiveTerm::setMessage('Building frankenphp: ' . ConsoleColor::yellow('processing --with-frankenphp-app option')); + 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(TargetPackage $package): string + { + if ($version = getenv('FRANKENPHP_VERSION')) { + return $version; + } + $frankenphpSourceDir = $package->getSourceDir(); + $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?"); + } + + $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'); + } +} diff --git a/src/Package/Target/php/unix.php b/src/Package/Target/php/unix.php index 18946ad4..13c89780 100644 --- a/src/Package/Target/php/unix.php +++ b/src/Package/Target/php/unix.php @@ -4,11 +4,13 @@ declare(strict_types=1); namespace Package\Target\php; +use Package\Target\php; use StaticPHP\Attribute\Package\BeforeStage; use StaticPHP\Attribute\Package\BuildFor; use StaticPHP\Attribute\Package\Stage; use StaticPHP\Attribute\PatchDescription; use StaticPHP\DI\ApplicationContext; +use StaticPHP\Exception\PatchException; use StaticPHP\Exception\SPCException; use StaticPHP\Exception\WrongUsageException; use StaticPHP\Package\PackageBuilder; @@ -20,7 +22,6 @@ use StaticPHP\Toolchain\Interface\ToolchainInterface; use StaticPHP\Util\DirDiff; use StaticPHP\Util\FileSystem; use StaticPHP\Util\InteractiveTerm; -use StaticPHP\Util\SourcePatcher; use StaticPHP\Util\SPCConfigUtil; use StaticPHP\Util\System\UnixUtil; use StaticPHP\Util\V2CompatLayer; @@ -28,6 +29,8 @@ use ZM\Logger\ConsoleColor; trait unix { + use frankenphp; + #[BeforeStage('php', [self::class, 'buildconfForUnix'], 'php')] #[PatchDescription('Patch configure.ac for musl and musl-toolchain')] #[PatchDescription('Let php m4 tools use static pkg-config')] @@ -46,11 +49,47 @@ trait unix FileSystem::replaceFileStr("{$package->getSourceDir()}/build/php.m4", 'PKG_CHECK_MODULES(', 'PKG_CHECK_MODULES_STATIC('); } + #[BeforeStage('php', [php::class, 'makeForUnix'], 'php')] + #[PatchDescription('Patch TSRM for musl TLS symbol visibility issue')] + #[PatchDescription('Patch ext/standard/info.c for configure command info')] + public function patchTSRMBeforeUnixMake(ToolchainInterface $toolchain): void + { + if (!$toolchain->isStatic() && SystemTarget::getLibc() === 'musl') { + // we need to patch the symbol to global visibility, otherwise extensions with `initial-exec` TLS model will fail to load + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/TSRM/TSRM.h', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS __attribute__((visibility("default"))) void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + ); + } else { + FileSystem::replaceFileStr( + SOURCE_PATH . '/php-src/TSRM/TSRM.h', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS __attribute__((visibility("default"))) void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + '#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;', + ); + } + + if (str_contains((string) getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), '-release')) { + FileSystem::replaceFileLineContainsString( + SOURCE_PATH . '/php-src/ext/standard/info.c', + '#ifdef CONFIGURE_COMMAND', + '#ifdef NO_CONFIGURE_COMMAND', + ); + } else { + FileSystem::replaceFileLineContainsString( + SOURCE_PATH . '/php-src/ext/standard/info.c', + '#ifdef NO_CONFIGURE_COMMAND', + '#ifdef CONFIGURE_COMMAND', + ); + } + } + #[Stage] public function buildconfForUnix(TargetPackage $package): void { InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf')); V2CompatLayer::emitPatchPoint('before-php-buildconf'); + // run ./buildconf shell()->cd($package->getSourceDir())->exec(getenv('SPC_CMD_PREFIX_PHP_BUILDCONF')); } @@ -112,18 +151,23 @@ trait unix logger()->info('cleaning up php-src build files'); shell()->cd($package->getSourceDir())->exec('make clean'); + // cli if ($installer->isPackageResolved('php-cli')) { $package->runStage([self::class, 'makeCliForUnix']); } + // cgi if ($installer->isPackageResolved('php-cgi')) { $package->runStage([self::class, 'makeCgiForUnix']); } + // fpm if ($installer->isPackageResolved('php-fpm')) { $package->runStage([self::class, 'makeFpmForUnix']); } + // micro if ($installer->isPackageResolved('php-micro')) { $package->runStage([self::class, 'makeMicroForUnix']); } + // embed if ($installer->isPackageResolved('php-embed')) { $package->runStage([self::class, 'makeEmbedForUnix']); } @@ -175,32 +219,44 @@ trait unix } #[Stage] - #[PatchDescription('Patch phar extension for micro SAPI to support compressed phar')] + #[PatchDescription('Patch micro.sfx after UPX compression')] public function makeMicroForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void { - $phar_patched = false; - try { - if ($installer->isPackageResolved('ext-phar')) { - $phar_patched = true; - SourcePatcher::patchMicroPhar(self::getPHPVersionID()); - } - InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro')); - // apply --with-micro-fake-cli option - $vars = $this->makeVars($installer); - $vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; - $makeArgs = $this->makeVarsToArgs($vars); - // build - shell()->cd($package->getSourceDir()) - ->setEnv($vars) - ->exec("make -j{$builder->concurrency} {$makeArgs} micro"); + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make micro')); + // apply --with-micro-fake-cli option + $vars = $this->makeVars($installer); + $vars['EXTRA_CFLAGS'] .= $package->getBuildOption('with-micro-fake-cli', false) ? ' -DPHP_MICRO_FAKE_CLI' : ''; + $makeArgs = $this->makeVarsToArgs($vars); + // build + shell()->cd($package->getSourceDir()) + ->setEnv($vars) + ->exec("make -j{$builder->concurrency} {$makeArgs} micro"); - $builder->deployBinary($package->getSourceDir() . '/sapi/micro/micro.sfx', BUILD_BIN_PATH . '/micro.sfx'); - $package->setOutput('Binary path for micro SAPI', BUILD_BIN_PATH . '/micro.sfx'); - } finally { - if ($phar_patched) { - SourcePatcher::unpatchMicroPhar(); + $dst = BUILD_BIN_PATH . '/micro.sfx'; + $builder->deployBinary("{$package->getSourceDir()}/sapi/micro/micro.sfx", $dst); + + /* + * 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 + */ + if ($package->getBuildOption('with-upx-pack') && SystemTarget::getTargetOS() === 'Linux') { + // 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)); } + + $package->setOutput('Binary path for micro SAPI', BUILD_BIN_PATH . '/micro.sfx'); } #[Stage] @@ -229,13 +285,18 @@ trait unix // process libphp.so for shared embed $suffix = SystemTarget::getTargetOS() === 'Darwin' ? 'dylib' : 'so'; $libphp_so = "{$package->getLibDir()}/libphp.{$suffix}"; + $libphp_so_dst = $libphp_so; if (file_exists($libphp_so)) { // rename libphp.so if -release is set if (SystemTarget::getTargetOS() === 'Linux') { - $this->processLibphpSoFile($libphp_so, $installer); + // deploy libphp.so + preg_match('/-release\s+(\S*)/', getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), $matches); + if (!empty($matches[1])) { + $libphp_so_dst = str_replace('.so', '-' . $matches[1] . '.so', $libphp_so); + } } // deploy - $builder->deployBinary($libphp_so, $libphp_so, false); + $builder->deployBinary($libphp_so, $libphp_so_dst, false); $package->setOutput('Library path for embed SAPI', $libphp_so); } @@ -312,7 +373,11 @@ trait unix public function build(TargetPackage $package): void { // virtual target, do nothing - if ($package->getName() !== 'php') { + if (in_array($package->getName(), ['php-cli', 'php-fpm', 'php-cgi', 'php-micro', 'php-embed'], true)) { + return; + } + if ($package->getName() === 'frankenphp') { + $package->runStage([$this, 'buildFrankenphpUnix']); return; } diff --git a/src/StaticPHP/Package/PackageBuilder.php b/src/StaticPHP/Package/PackageBuilder.php index c9b1051f..d782b3e2 100644 --- a/src/StaticPHP/Package/PackageBuilder.php +++ b/src/StaticPHP/Package/PackageBuilder.php @@ -113,11 +113,10 @@ class PackageBuilder throw new SPCInternalException("Deploy failed. Cannot find file after copy: {$dst}"); } - // extract debug info - $this->extractDebugInfo($dst); - - // strip if (!$this->getOption('no-strip') && SystemTarget::isUnix()) { + // extract debug info + $this->extractDebugInfo($dst); + // strip binary $this->stripBinary($dst); } @@ -145,13 +144,12 @@ class PackageBuilder 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}" . (SystemTarget::getTargetOS() === 'Darwin' ? '.dwarf' : '.debug'); if (SystemTarget::getTargetOS() === 'Darwin') { - FileSystem::createDir($target_dir); shell()->exec("dsymutil -f {$binary_path} -o {$debug_file}"); } elseif (SystemTarget::getTargetOS() === 'Linux') { - FileSystem::createDir($target_dir); if ($eu_strip = LinuxUtil::findCommand('eu-strip')) { shell() ->exec("{$eu_strip} -f {$debug_file} {$binary_path}") @@ -176,6 +174,7 @@ class PackageBuilder shell()->exec(match (SystemTarget::getTargetOS()) { 'Darwin' => "strip -S {$binary_path}", 'Linux' => "strip --strip-unneeded {$binary_path}", + 'Windows' => 'echo "Skip strip on Windows"', // Windows strip is not available for now default => throw new SPCInternalException('stripBinary is only supported on Linux and macOS'), }); } diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 169630f9..d156eb61 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -614,8 +614,13 @@ class PackageInstaller } } else { // process specific php sapi targets - $this->build_packages['php'] = PackageLoader::getPackage('php'); - $this->install_packages[$package->getName()] = $package; + if ($package->getName() === 'frankenphp') { + $this->build_packages['php'] = PackageLoader::getPackage('php'); + $this->build_packages['frankenphp'] = PackageLoader::getPackage('frankenphp'); + } else { + $this->install_packages[$package->getName()] = $package; + $this->build_packages['php'] = PackageLoader::getPackage('php'); + } } }