2025-12-11 13:50:36 +08:00

229 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace Package\Target\php;
use StaticPHP\Attribute\Package\BeforeStage;
use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\Stage;
use StaticPHP\Attribute\PatchDescription;
use StaticPHP\Exception\PatchException;
use StaticPHP\Exception\SPCInternalException;
use StaticPHP\Package\PackageBuilder;
use StaticPHP\Package\PackageInstaller;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Util\FileSystem;
use StaticPHP\Util\InteractiveTerm;
use StaticPHP\Util\SourcePatcher;
use StaticPHP\Util\System\WindowsUtil;
use StaticPHP\Util\V2CompatLayer;
use ZM\Logger\ConsoleColor;
trait windows
{
#[Stage]
public function buildconfForWindows(TargetPackage $package): void
{
InteractiveTerm::setMessage('Building php: ' . ConsoleColor::yellow('./buildconf.bat'));
V2CompatLayer::emitPatchPoint('before-php-buildconf');
cmd()->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));
}
}
}