diff --git a/src/Package/Target/php/unix.php b/src/Package/Target/php/unix.php new file mode 100644 index 00000000..23d465df --- /dev/null +++ b/src/Package/Target/php/unix.php @@ -0,0 +1,450 @@ +/dev/null && ldd --version 2>&1 | grep ^musl >/dev/null 2>&1', + 'if ' . ($musl ? 'true' : 'false') + ); + + // let php m4 tools use static pkg-config + FileSystem::replaceFileStr("{$package->getSourceDir()}/build/php.m4", 'PKG_CHECK_MODULES(', 'PKG_CHECK_MODULES_STATIC('); + } + + #[Stage] + public function buildconfForUnix(TargetPackage $package): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf')); + V2CompatLayer::emitPatchPoint('before-php-buildconf'); + shell()->cd($package->getSourceDir())->exec(getenv('SPC_CMD_PREFIX_PHP_BUILDCONF')); + } + + #[Stage] + public function configureForUnix(TargetPackage $package, PackageInstaller $installer): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./configure')); + V2CompatLayer::emitPatchPoint('before-php-configure'); + $cmd = getenv('SPC_CMD_PREFIX_PHP_CONFIGURE'); + + $args = []; + $version_id = self::getPHPVersionID(); + // PHP JSON extension is built-in since PHP 8.0 + if ($version_id < 80000) { + $args[] = '--enable-json'; + } + // zts + if ($package->getBuildOption('enable-zts', false)) { + $args[] = '--enable-zts --disable-zend-signals'; + if ($version_id >= 80100 && SystemTarget::getTargetOS() === 'Linux') { + $args[] = '--enable-zend-max-execution-timers'; + } + } + // config-file-path and config-file-scan-dir + if ($option = $package->getBuildOption('with-config-file-path', false)) { + $args[] = "--with-config-file-path={$option}"; + } + if ($option = $package->getBuildOption('with-config-file-scan-dir', false)) { + $args[] = "--with-config-file-scan-dir={$option}"; + } + // perform enable cli options + $args[] = $installer->isPackageResolved('php-cli') ? '--enable-cli' : '--disable-cli'; + $args[] = $installer->isPackageResolved('php-fpm') ? '--enable-fpm' : '--disable-fpm'; + $args[] = $installer->isPackageResolved('php-micro') ? match (SystemTarget::getTargetOS()) { + 'Linux' => '--enable-micro=all-static', + default => '--enable-micro', + } : null; + $args[] = $installer->isPackageResolved('php-cgi') ? '--enable-cgi' : '--disable-cgi'; + $embed_type = getenv('SPC_CMD_VAR_PHP_EMBED_TYPE') ?: 'static'; + $args[] = $installer->isPackageResolved('php-embed') ? "--enable-embed={$embed_type}" : '--disable-embed'; + $args[] = getenv('SPC_EXTRA_PHP_VARS') ?: null; + $args = implode(' ', array_filter($args)); + + $static_extension_str = $this->makeStaticExtensionString($installer); + + // run ./configure with args + $this->seekPhpSrcLogFileOnException(fn () => shell()->cd($package->getSourceDir())->setEnv([ + 'CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), + 'CPPFLAGS' => "-I{$package->getIncludeDir()}", + 'LDFLAGS' => "-L{$package->getLibDir()} " . getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS'), + ])->exec("{$cmd} {$args} {$static_extension_str}"), $package->getSourceDir()); + } + + #[Stage] + public function makeForUnix(TargetPackage $package, PackageInstaller $installer): void + { + V2CompatLayer::emitPatchPoint('before-php-make'); + + logger()->info('cleaning up php-src build files'); + shell()->cd($package->getSourceDir())->exec('make clean'); + + if ($installer->isPackageResolved('php-cli')) { + $package->runStage([self::class, 'makeCliForUnix']); + } + if ($installer->isPackageResolved('php-cgi')) { + $package->runStage([self::class, 'makeCgiForUnix']); + } + if ($installer->isPackageResolved('php-fpm')) { + $package->runStage([self::class, 'makeFpmForUnix']); + } + if ($installer->isPackageResolved('php-micro')) { + $package->runStage([self::class, 'makeMicroForUnix']); + } + if ($installer->isPackageResolved('php-embed')) { + $package->runStage([self::class, 'makeEmbedForUnix']); + } + } + + #[Stage] + public function makeCliForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cli')); + $concurrency = $builder->concurrency; + shell()->cd($package->getSourceDir()) + ->setEnv($this->makeVars($installer)) + ->exec("make -j{$concurrency} cli"); + + $builder->deployBinary("{$package->getSourceDir()}/sapi/cli/php", BUILD_BIN_PATH . '/php'); + $package->setOutput('Binary path for cli SAPI', BUILD_BIN_PATH . '/php'); + } + + #[Stage] + public function makeCgiForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make cgi')); + $concurrency = $builder->concurrency; + shell()->cd($package->getSourceDir()) + ->setEnv($this->makeVars($installer)) + ->exec("make -j{$concurrency} cgi"); + + $builder->deployBinary("{$package->getSourceDir()}/sapi/cgi/php-cgi", BUILD_BIN_PATH . '/php-cgi'); + $package->setOutput('Binary path for cgi SAPI', BUILD_BIN_PATH . '/php-cgi'); + } + + #[Stage] + public function makeFpmForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make fpm')); + $concurrency = $builder->concurrency; + shell()->cd($package->getSourceDir()) + ->setEnv($this->makeVars($installer)) + ->exec("make -j{$concurrency} fpm"); + + $builder->deployBinary("{$package->getSourceDir()}/sapi/fpm/php-fpm", BUILD_BIN_PATH . '/php-fpm'); + $package->setOutput('Binary path for fpm SAPI', BUILD_BIN_PATH . '/php-fpm'); + } + + #[Stage] + #[PatchDescription('Patch phar extension for micro SAPI to support compressed phar')] + 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' : ''; + // build + shell()->cd($package->getSourceDir()) + ->setEnv($vars) + ->exec("make -j{$builder->concurrency} 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(); + } + } + } + + #[Stage] + public function makeEmbedForUnix(TargetPackage $package, PackageInstaller $installer, PackageBuilder $builder): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('make embed')); + $shared_exts = array_filter( + $installer->getResolvedPackages(), + static fn ($x) => $x instanceof PhpExtensionPackage && $x->isBuildShared() && $x->isBuildWithPhp() + ); + $install_modules = $shared_exts ? 'install-modules' : ''; + + // detect changes in module path + $diff = new DirDiff(BUILD_MODULES_PATH, true); + + $root = BUILD_ROOT_PATH; + $sed_prefix = SystemTarget::getTargetOS() === 'Darwin' ? 'sed -i ""' : 'sed -i'; + + shell()->cd($package->getSourceDir()) + ->setEnv($this->makeVars($installer)) + ->exec("{$sed_prefix} \"s|^EXTENSION_DIR = .*|EXTENSION_DIR = /" . basename(BUILD_MODULES_PATH) . '|" Makefile') + ->exec("make -j{$builder->concurrency} INSTALL_ROOT={$root} install-sapi {$install_modules} install-build install-headers install-programs"); + + // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=shared ------------- + + // process libphp.so for shared embed + $suffix = SystemTarget::getTargetOS() === 'Darwin' ? 'dylib' : 'so'; + $libphp_so = "{$package->getLibDir()}/libphp.{$suffix}"; + if (file_exists($libphp_so)) { + // rename libphp.so if -release is set + if (SystemTarget::getTargetOS() === 'Linux') { + $this->processLibphpSoFile($libphp_so, $installer); + } + // deploy + $builder->deployBinary($libphp_so, $libphp_so, false); + $package->setOutput('Library path for embed SAPI', $libphp_so); + } + + // process shared extensions that built-with-php + $increment_files = $diff->getChangedFiles(); + $files = []; + foreach ($increment_files as $increment_file) { + $builder->deployBinary($increment_file, $increment_file, false); + $files[] = basename($increment_file); + } + if (!empty($files)) { + $package->setOutput('Built shared extensions', implode(', ', $files)); + } + + // ------------- SPC_CMD_VAR_PHP_EMBED_TYPE=static ------------- + + // process libphp.a for static embed + if (!file_exists("{$package->getLibDir()}/libphp.a")) { + return; + } + $ar = getenv('AR') ?: 'ar'; + $libphp_a = "{$package->getLibDir()}/libphp.a"; + shell()->exec("{$ar} -t {$libphp_a} | grep '\\.a$' | xargs -n1 {$ar} d {$libphp_a}"); + UnixUtil::exportDynamicSymbols($libphp_a); + + // deploy embed php scripts + $package->runStage([$this, 'patchEmbedScripts']); + } + + #[Stage] + public function unixBuildSharedExt(PackageInstaller $installer, ToolchainInterface $toolchain): void + { + // collect shared extensions + /** @var PhpExtensionPackage[] $shared_extensions */ + $shared_extensions = array_filter( + $installer->getResolvedPackages(PhpExtensionPackage::class), + fn ($x) => $x->isBuildShared() && !$x->isBuildWithPhp() + ); + if (!empty($shared_extensions)) { + if ($toolchain->isStatic()) { + throw new WrongUsageException( + "You're building against musl libc statically (the default on Linux), but you're trying to build shared extensions.\n" . + 'Static musl libc does not implement `dlopen`, so your php binary is not able to load shared extensions.' . "\n" . + 'Either use SPC_LIBC=glibc to link against glibc on a glibc OS, or use SPC_TARGET="native-native-musl -dynamic" to link against musl libc dynamically using `zig cc`.' + ); + } + FileSystem::createDir(BUILD_MODULES_PATH); + + // backup + FileSystem::backupFile(BUILD_BIN_PATH . '/php-config'); + FileSystem::backupFile(BUILD_LIB_PATH . '/php/build/phpize.m4'); + + FileSystem::replaceFileLineContainsString(BUILD_BIN_PATH . '/php-config', 'extension_dir=', 'extension_dir="' . BUILD_MODULES_PATH . '"'); + FileSystem::replaceFileStr(BUILD_LIB_PATH . '/php/build/phpize.m4', 'test "[$]$1" = "no" && $1=yes', '# test "[$]$1" = "no" && $1=yes'); + } + + try { + logger()->debug('Building shared extensions...'); + foreach ($shared_extensions as $extension) { + InteractiveTerm::setMessage('Building shared PHP extension: ' . ConsoleColor::yellow($extension->getName())); + $extension->buildShared(); + } + } finally { + // restore php-config + if (!empty($shared_extensions)) { + FileSystem::restoreBackupFile(BUILD_BIN_PATH . '/php-config'); + FileSystem::restoreBackupFile(BUILD_LIB_PATH . '/php/build/phpize.m4'); + } + } + } + + #[BuildFor('Darwin')] + #[BuildFor('Linux')] + public function build(TargetPackage $package): void + { + // virtual target, do nothing + if ($package->getName() !== 'php') { + return; + } + + $package->runStage([$this, 'buildconfForUnix']); + $package->runStage([$this, 'configureForUnix']); + $package->runStage([$this, 'makeForUnix']); + + $package->runStage([$this, 'unixBuildSharedExt']); + } + + /** + * Patch phpize and php-config if needed + */ + #[Stage] + public function patchUnixEmbedScripts(): void + { + // patch phpize + if (file_exists(BUILD_BIN_PATH . '/phpize')) { + logger()->debug('Patching phpize prefix'); + FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', "prefix=''", "prefix='" . BUILD_ROOT_PATH . "'"); + FileSystem::replaceFileStr(BUILD_BIN_PATH . '/phpize', 's##', 's#/usr/local#'); + $this->setOutput('phpize script path for embed SAPI', BUILD_BIN_PATH . '/phpize'); + } + // patch php-config + if (file_exists(BUILD_BIN_PATH . '/php-config')) { + logger()->debug('Patching php-config prefix and libs order'); + $php_config_str = FileSystem::readFile(BUILD_BIN_PATH . '/php-config'); + $php_config_str = str_replace('prefix=""', 'prefix="' . BUILD_ROOT_PATH . '"', $php_config_str); + // move mimalloc to the beginning of libs + $php_config_str = preg_replace('/(libs=")(.*?)\s*(' . preg_quote(BUILD_LIB_PATH, '/') . '\/mimalloc\.o)\s*(.*?)"/', '$1$3 $2 $4"', $php_config_str); + // move lstdc++ to the end of libs + $php_config_str = preg_replace('/(libs=")(.*?)\s*(-lstdc\+\+)\s*(.*?)"/', '$1$2 $4 $3"', $php_config_str); + FileSystem::writeFile(BUILD_BIN_PATH . '/php-config', $php_config_str); + $this->setOutput('php-config script path for embed SAPI', BUILD_BIN_PATH . '/php-config'); + } + } + + /** + * Seek php-src/config.log when building PHP, add it to exception. + */ + protected function seekPhpSrcLogFileOnException(callable $callback, string $source_dir): void + { + try { + $callback(); + } catch (SPCException $e) { + if (file_exists("{$source_dir}/config.log")) { + $e->addExtraLogFile('php-src config.log', 'php-src.config.log'); + copy("{$source_dir}/config.log", SPC_LOGS_DIR . '/php-src.config.log'); + } + throw $e; + } + } + + /** + * Rename libphp.so to libphp-.so if -release is set in LDFLAGS. + */ + private function processLibphpSoFile(string $libphpSo, PackageInstaller $installer): void + { + $ldflags = getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') ?: ''; + $libDir = BUILD_LIB_PATH; + $modulesDir = BUILD_MODULES_PATH; + $realLibName = 'libphp.so'; + $cwd = getcwd(); + + if (preg_match('/-release\s+(\S+)/', $ldflags, $matches)) { + $release = $matches[1]; + $realLibName = "libphp-{$release}.so"; + $libphpRelease = "{$libDir}/{$realLibName}"; + if (!file_exists($libphpRelease) && file_exists($libphpSo)) { + rename($libphpSo, $libphpRelease); + } + if (file_exists($libphpRelease)) { + chdir($libDir); + if (file_exists($libphpSo)) { + unlink($libphpSo); + } + symlink($realLibName, 'libphp.so'); + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg($realLibName), + escapeshellarg($libphpRelease) + )); + } + if (is_dir($modulesDir)) { + chdir($modulesDir); + foreach ($installer->getResolvedPackages(PhpExtensionPackage::class) as $ext) { + if (!$ext->isBuildShared()) { + continue; + } + $name = $ext->getName(); + $versioned = "{$name}-{$release}.so"; + $unversioned = "{$name}.so"; + $src = "{$modulesDir}/{$versioned}"; + $dst = "{$modulesDir}/{$unversioned}"; + if (is_file($src)) { + rename($src, $dst); + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg($unversioned), + escapeshellarg($dst) + )); + } + } + } + chdir($cwd); + } + + $target = "{$libDir}/{$realLibName}"; + if (file_exists($target)) { + [, $output] = shell()->execWithResult('readelf -d ' . escapeshellarg($target)); + $output = implode("\n", $output); + if (preg_match('/SONAME.*\[(.+)]/', $output, $sonameMatch)) { + $currentSoname = $sonameMatch[1]; + if ($currentSoname !== basename($target)) { + shell()->exec(sprintf( + 'patchelf --set-soname %s %s', + escapeshellarg(basename($target)), + escapeshellarg($target) + )); + } + } + } + } + + /** + * Make environment variables for php make. + * This will call SPCConfigUtil to generate proper LDFLAGS and LIBS for static linking. + */ + private function makeVars(PackageInstaller $installer): array + { + $config = (new SPCConfigUtil(['libs_only_deps' => true]))->config(array_map(fn ($x) => $x->getName(), $installer->getResolvedPackages())); + $static = ApplicationContext::get(ToolchainInterface::class)->isStatic() ? '-all-static' : ''; + $pie = SystemTarget::getTargetOS() === 'Linux' ? '-pie' : ''; + + return array_filter([ + 'EXTRA_CFLAGS' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS'), + 'EXTRA_LDFLAGS_PROGRAM' => getenv('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS') . "{$config['ldflags']} {$static} {$pie}", + 'EXTRA_LDFLAGS' => $config['ldflags'], + 'EXTRA_LIBS' => $config['libs'], + ]); + } +} diff --git a/src/Package/Target/php/windows.php b/src/Package/Target/php/windows.php new file mode 100644 index 00000000..28952637 --- /dev/null +++ b/src/Package/Target/php/windows.php @@ -0,0 +1,228 @@ +cd($package->getSourceDir())->exec('.\buildconf.bat'); + } + + #[Stage] + public function configureForWindows(TargetPackage $package, PackageInstaller $installer): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./configure.bat')); + V2CompatLayer::emitPatchPoint('before-php-configure'); + $args = [ + '--disable-all', + "--with-php-build={$package->getBuildRootPath()}", + "--with-extra-includes={$package->getIncludeDir()}", + "--with-extra-libs={$package->getLibDir()}", + ]; + // sapis + $cli = $installer->isPackageResolved('php-cli'); + $cgi = $installer->isPackageResolved('php-cgi'); + $micro = $installer->isPackageResolved('php-micro'); + $args[] = $cli ? '--enable-cli=yes' : '--enable-cli=no'; + $args[] = $cgi ? '--enable-cgi=yes' : '--enable-cgi=no'; + $args[] = $micro ? '--enable-micro=yes' : '--enable-micro=no'; + + // zts + $args[] = $package->getBuildOption('enable-zts', false) ? '--enable-zts=yes' : '--enable-zts=no'; + // opcache-jit + $args[] = !$package->getBuildOption('disable-opcache-jit', false) ? '--enable-opcache-jit=yes' : '--enable-opcache-jit=no'; + // micro win32 + if ($micro && $package->getBuildOption('enable-micro-win32', false)) { + $args[] = '--enable-micro-win32=yes'; + } + // config-file-scan-dir + if ($option = $package->getBuildOption('with-config-file-scan-dir', false)) { + $args[] = "--with-config-file-scan-dir={$option}"; + } + // micro logo + if ($micro && ($logo = $this->getBuildOption('with-micro-logo')) !== null) { + $args[] = "--enable-micro-logo={$logo}"; + copy($logo, SOURCE_PATH . '\php-src\\' . $logo); + } + $args = implode(' ', $args); + $static_extension_str = $this->makeStaticExtensionString($installer); + cmd()->cd($package->getSourceDir())->exec(".\\configure.bat {$args} {$static_extension_str}"); + } + + #[BeforeStage('php', [self::class, 'makeCliForWindows'])] + #[PatchDescription('Patch Windows Makefile for CLI target')] + public function patchCLITarget(TargetPackage $package): void + { + // search Makefile code line contains "$(BUILD_DIR)\php.exe:" + $content = FileSystem::readFile("{$package->getSourceDir()}\\Makefile"); + $lines = explode("\r\n", $content); + $line_num = 0; + $found = false; + foreach ($lines as $v) { + if (str_contains($v, '$(BUILD_DIR)\php.exe:')) { + $found = $line_num; + break; + } + ++$line_num; + } + if ($found === false) { + throw new PatchException('Windows Makefile patching for php.exe target', 'Cannot patch windows CLI Makefile, Makefile does not contain "$(BUILD_DIR)\php.exe:" line'); + } + $lines[$line_num] = '$(BUILD_DIR)\php.exe: generated_files $(DEPS_CLI) $(PHP_GLOBAL_OBJS) $(CLI_GLOBAL_OBJS) $(STATIC_EXT_OBJS) $(ASM_OBJS) $(BUILD_DIR)\php.exe.res $(BUILD_DIR)\php.exe.manifest'; + $lines[$line_num + 1] = "\t" . '"$(LINK)" /nologo $(PHP_GLOBAL_OBJS_RESP) $(CLI_GLOBAL_OBJS_RESP) $(STATIC_EXT_OBJS_RESP) $(STATIC_EXT_LIBS) $(ASM_OBJS) $(LIBS) $(LIBS_CLI) $(BUILD_DIR)\php.exe.res /out:$(BUILD_DIR)\php.exe $(LDFLAGS) $(LDFLAGS_CLI) /ltcg /nodefaultlib:msvcrt /nodefaultlib:msvcrtd /ignore:4286'; + FileSystem::writeFile("{$package->getSourceDir()}\\Makefile", implode("\r\n", $lines)); + } + + #[Stage] + public function makeCliForWindows(TargetPackage $package, PackageBuilder $builder): void + { + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('nmake php-cli')); + + // extra lib + $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 ($package->getBuildOption('no-strip', false)) { + // Read current CFLAGS from Makefile and replace optimization flags + $makefile_content = file_get_contents("{$package->getSourceDir()}\\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" '; + } + } + + cmd()->cd($package->getSourceDir()) + ->exec("nmake /nologo {$debug_overrides}LIBS_CLI=\"ws2_32.lib shell32.lib {$extra_libs}\" EXTRA_LD_FLAGS_PROGRAM= php.exe"); + + $this->deployWindowsBinary($builder, $package, 'php-cli'); + } + + #[Stage] + public function makeForWindows(TargetPackage $package, PackageInstaller $installer): void + { + V2CompatLayer::emitPatchPoint('before-php-make'); + InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('nmake clean')); + cmd()->cd($package->getSourceDir())->exec('nmake clean'); + + if ($installer->isPackageResolved('php-cli')) { + $package->runStage([$this, 'makeCliForWindows']); + } + if ($installer->isPackageResolved('php-cgi')) { + $package->runStage([$this, 'makeCgiForWindows']); + } + if ($installer->isPackageResolved('php-micro')) { + $package->runStage([$this, 'makeMicroForWindows']); + } + } + + #[BuildFor('Windows')] + public function buildWin(TargetPackage $package): void + { + if ($package->getName() !== 'php') { + return; + } + + $package->runStage([$this, 'buildconfForWindows']); + $package->runStage([$this, 'configureForWindows']); + $package->runStage([$this, 'makeForWindows']); + } + + #[BeforeStage('php', [self::class, 'buildconfForWindows'])] + #[PatchDescription('Patch SPC_MICRO_PATCHES defined patches')] + #[PatchDescription('Fix PHP 8.1 static build bug on Windows')] + #[PatchDescription('Fix PHP Visual Studio version detection')] + public function patchBeforeBuildconfForWindows(TargetPackage $package): void + { + // php-src patches from micro + SourcePatcher::patchPhpSrc(); + + // php 8.1 bug + if ($this->getPHPVersionID() >= 80100 && $this->getPHPVersionID() < 80200) { + logger()->info('Patching PHP 8.1 windows Fiber bug'); + FileSystem::replaceFileStr( + "{$package->getSourceDir()}\\win32\\build\\config.w32", + "ADD_FLAG('LDFLAGS', '$(BUILD_DIR)\\\\Zend\\\\jump_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');", + "ADD_FLAG('ASM_OBJS', '$(BUILD_DIR)\\\\Zend\\\\jump_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj $(BUILD_DIR)\\\\Zend\\\\make_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');" + ); + FileSystem::replaceFileStr( + "{$package->getSourceDir()}\\win32\\build\\config.w32", + "ADD_FLAG('LDFLAGS', '$(BUILD_DIR)\\\\Zend\\\\make_' + FIBER_ASM_ARCH + '_ms_pe_masm.obj');", + '' + ); + } + + // Fix PHP VS version + // get vs version + $vc = WindowsUtil::findVisualStudio(); + $vc_matches = match ($vc['major_version']) { + '17' => ['VS17', 'Visual C++ 2022'], + '16' => ['VS16', 'Visual C++ 2019'], + default => ['unknown', 'unknown'], + }; + // patch php-src/win32/build/confutils.js + FileSystem::replaceFileStr( + "{$package->getSourceDir()}\\win32\\build\\confutils.js", + 'var name = "unknown";', + "var name = short ? \"{$vc_matches[0]}\" : \"{$vc_matches[1]}\";return name;" + ); + + // patch micro win32 + if ($package->getBuildOption('enable-micro-win32') && !file_exists("{$package->getSourceDir()}\\sapi\\micro\\php_micro.c.win32bak")) { + copy("{$package->getSourceDir()}\\sapi\\micro\\php_micro.c", "{$package->getSourceDir()}\\php-src\\sapi\\micro\\php_micro.c.win32bak"); + FileSystem::replaceFileStr("{$package->getSourceDir()}\\sapi\\micro\\php_micro.c", '#include "php_variables.h"', '#include "php_variables.h"' . "\n#define PHP_MICRO_WIN32_NO_CONSOLE 1"); + } else { + if (file_exists("{$package->getSourceDir()}\\sapi\\micro\\php_micro.c.win32bak")) { + rename("{$package->getSourceDir()}\\sapi\\micro\\php_micro.c.win32bak", "{$package->getSourceDir()}\\sapi\\micro\\php_micro.c"); + } + } + } + + protected function deployWindowsBinary(PackageBuilder $builder, TargetPackage $package, string $sapi): void + { + $rel_type = 'Release'; // TODO: Debug build support + $ts = $builder->getOption('enable-zts') ? '_TS' : ''; + $debug_dir = BUILD_ROOT_PATH . '\debug'; + $src = match ($sapi) { + 'php-cli' => ["{$package->getSourceDir()}\\x64\\{$rel_type}{$ts}", 'php.exe', 'php.pdb'], + 'php-micro' => ["{$package->getSourceDir()}\\x64\\{$rel_type}{$ts}", 'micro.sfx', 'micro.pdb'], + 'php-cgi' => ["{$package->getSourceDir()}\\x64\\{$rel_type}{$ts}", 'php-cgi.exe', 'php-cgi.pdb'], + default => throw new SPCInternalException("Deployment does not accept type {$sapi}"), + }; + $src = "{$src[0]}\\{$src[1]}"; + $dst = BUILD_BIN_PATH . '\\' . basename($src); + + $builder->deployBinary($src, $dst); + + // make debug info file path + if ($builder->getOption('no-strip', false) && file_exists("{$src[0]}\\{$src[2]}")) { + cmd()->exec('copy ' . escapeshellarg("{$src[0]}\\{$src[2]}") . ' ' . escapeshellarg($debug_dir)); + } + } +}