From 1dce50622283b3eaeb998dcc75e6e4afceddddfa Mon Sep 17 00:00:00 2001 From: m-this <52073029+m-this@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:11:27 +0200 Subject: [PATCH 1/9] fix(postgresql): provision meson, ninja and winflexbison on windows - the meson build assumed meson/ninja/win_flex/win_bison/perl on PATH, which only held on machines that had them installed - add meson, ninja and winflexbison tool packages and declare them via tools@windows on postgresql, together with the existing strawberry-perl - meson no longer ships a Windows MSI, so the wheel is installed offline into a venv: postgres re-invokes meson through find_program, which needs the real meson.exe launcher pip generates --- config/pkg/lib/postgresql.yml | 5 ++++ config/pkg/tool/meson.yml | 11 ++++++++ config/pkg/tool/ninja.yml | 9 ++++++ config/pkg/tool/winflexbison.yml | 12 ++++++++ src/Package/Artifact/meson_win.php | 45 ++++++++++++++++++++++++++++++ src/Package/Library/postgresql.php | 3 +- 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 config/pkg/tool/meson.yml create mode 100644 config/pkg/tool/ninja.yml create mode 100644 config/pkg/tool/winflexbison.yml create mode 100644 src/Package/Artifact/meson_win.php diff --git a/config/pkg/lib/postgresql.yml b/config/pkg/lib/postgresql.yml index 4be33190..290650df 100644 --- a/config/pkg/lib/postgresql.yml +++ b/config/pkg/lib/postgresql.yml @@ -17,6 +17,11 @@ postgresql: depends@windows: - openssl - zlib + tools@windows: + - meson + - ninja + - winflexbison + - strawberry-perl suggests@unix: - icu - libxslt diff --git a/config/pkg/tool/meson.yml b/config/pkg/tool/meson.yml new file mode 100644 index 00000000..0021bf93 --- /dev/null +++ b/config/pkg/tool/meson.yml @@ -0,0 +1,11 @@ +meson: + type: tool + artifact: + binary: + windows-x86_64: { type: url, url: 'https://github.com/mesonbuild/meson/releases/download/1.11.1/meson-1.11.1-py3-none-any.whl', extract: '{pkg_root_path}/meson/meson-1.11.1-py3-none-any.whl' } + path@windows: + - '{pkg_root_path}\meson' + tool: + provides: + - meson.bat + binary-subdir: meson diff --git a/config/pkg/tool/ninja.yml b/config/pkg/tool/ninja.yml new file mode 100644 index 00000000..820e154d --- /dev/null +++ b/config/pkg/tool/ninja.yml @@ -0,0 +1,9 @@ +ninja: + type: tool + artifact: + binary: + windows-x86_64: { type: url, url: 'https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-win.zip', extract: { ninja.exe: '{pkg_root_path}/bin/ninja.exe' } } + tool: + provides: + - ninja.exe + binary-subdir: bin diff --git a/config/pkg/tool/winflexbison.yml b/config/pkg/tool/winflexbison.yml new file mode 100644 index 00000000..f117b5b9 --- /dev/null +++ b/config/pkg/tool/winflexbison.yml @@ -0,0 +1,12 @@ +winflexbison: + type: tool + artifact: + binary: + windows-x86_64: { type: url, url: 'https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip', extract: '{pkg_root_path}/winflexbison' } + path@windows: + - '{pkg_root_path}\winflexbison' + tool: + provides: + - win_flex.exe + - win_bison.exe + binary-subdir: winflexbison diff --git a/src/Package/Artifact/meson_win.php b/src/Package/Artifact/meson_win.php new file mode 100644 index 00000000..d3a911be --- /dev/null +++ b/src/Package/Artifact/meson_win.php @@ -0,0 +1,45 @@ +execWithResult("{$candidate} --version", false); + if ($code === 0) { + $python = $candidate; + break; + } + } + if ($python === null) { + throw new EnvironmentException('meson needs Python 3 on PATH. Install it from https://www.python.org or with `winget install Python.Python.3.13`.'); + } + + if (is_dir($venv)) { + FileSystem::removeDir($venv); + } + cmd()->exec("{$python} -m venv \"{$venv}\"") + ->exec("\"{$venv}\\Scripts\\python.exe\" -m pip install --no-index --no-deps \"{$target_path}\""); + + FileSystem::writeFile("{$dir}\\meson.bat", "@echo off\r\n\"%~dp0venv\\Scripts\\meson.exe\" %*\r\n"); + } +} diff --git a/src/Package/Library/postgresql.php b/src/Package/Library/postgresql.php index d0fcc2eb..25d600e2 100644 --- a/src/Package/Library/postgresql.php +++ b/src/Package/Library/postgresql.php @@ -98,7 +98,8 @@ class postgresql extends LibraryPackage . ' -Dextra_lib_dirs=' . escapeshellarg($lib_dir); // Build only the three frontend static libs (not the server) — keeps it fast and avoids - // needing every backend dependency. meson/ninja/win_bison/win_flex/perl come from PATH. + // needing every backend dependency. meson/ninja/win_bison/win_flex/perl come from the + // tool packages declared in the package config (tools@windows). $targets = 'src/interfaces/libpq/libpq.a src/common/libpgcommon.a src/port/libpgport.a'; cmd()->cd($src) From f3afd81c81f2550faeafcb52394a49ff9aef4c4a Mon Sep 17 00:00:00 2001 From: m-this <52073029+m-this@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:11:27 +0200 Subject: [PATCH 2/9] fix(php): pass /FORCE:MULTIPLE when linking php-cgi.exe and micro.sfx - php.exe already links with /FORCE:MULTIPLE for extension-bundled duplicate symbols, cgi and micro did not - imagick's MagickCore defines gettimeofday, also defined in php's time.obj, and both links failed with LNK2005 --- src/Package/Target/php/windows.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Package/Target/php/windows.php b/src/Package/Target/php/windows.php index d8cc7293..dfc09882 100644 --- a/src/Package/Target/php/windows.php +++ b/src/Package/Target/php/windows.php @@ -201,7 +201,10 @@ trait windows throw new PatchException('Windows Makefile patching for php-cgi.exe target', 'Cannot patch windows CGI Makefile, Makefile does not contain "$(BUILD_DIR)\php-cgi.exe:" line'); } $lines[$line_num] = '$(BUILD_DIR)\php-cgi.exe: $(DEPS_CGI) $(CGI_GLOBAL_OBJS) $(PHP_GLOBAL_OBJS) $(STATIC_EXT_OBJS) $(ASM_OBJS) $(BUILD_DIR)\php-cgi.exe.res $(BUILD_DIR)\php-cgi.exe.manifest'; - $lines[$line_num + 1] = "\t" . '@"$(LINK)" /nologo $(PHP_GLOBAL_OBJS_RESP) $(CGI_GLOBAL_OBJS_RESP) $(STATIC_EXT_OBJS_RESP) $(STATIC_EXT_LIBS) $(ASM_OBJS) $(LIBS) $(LIBS_CGI) $(BUILD_DIR)\php-cgi.exe.res /out:$(BUILD_DIR)\php-cgi.exe $(LDFLAGS) $(LDFLAGS_CGI) /ltcg /nodefaultlib:msvcrt /nodefaultlib:msvcrtd /ignore:4286'; + // /FORCE:MULTIPLE /ignore:4006: same reason as the php.exe target above. Without it the + // CGI link fails with LNK2005 (e.g. imagick's MagickCore defines gettimeofday, which + // php's time.obj also defines). + $lines[$line_num + 1] = "\t" . '@"$(LINK)" /nologo $(PHP_GLOBAL_OBJS_RESP) $(CGI_GLOBAL_OBJS_RESP) $(STATIC_EXT_OBJS_RESP) $(STATIC_EXT_LIBS) $(ASM_OBJS) $(LIBS) $(LIBS_CGI) $(BUILD_DIR)\php-cgi.exe.res /out:$(BUILD_DIR)\php-cgi.exe $(LDFLAGS) $(LDFLAGS_CGI) /ltcg /nodefaultlib:msvcrt /nodefaultlib:msvcrtd /ignore:4286 /FORCE:MULTIPLE /ignore:4006'; FileSystem::writeFile("{$package->getSourceDir()}\\Makefile", implode("\r\n", $lines)); // Patch cgi-static, comment ZEND_TSRMLS_CACHE_DEFINE() @@ -268,6 +271,19 @@ trait windows } } + #[BeforeStage('php', [self::class, 'makeMicroForWindows'])] + #[PatchDescription('Add /FORCE:MULTIPLE to the micro.sfx link, matching the CLI and CGI targets')] + public function patchMicroTarget(TargetPackage $package): void + { + $makefile = "{$package->getSourceDir()}\\Makefile"; + if (str_contains(FileSystem::readFile($makefile), 'LDFLAGS_MICRO=/FORCE:MULTIPLE')) { + return; + } + // Same reason as the php.exe and php-cgi.exe targets: extension-bundled libs duplicate + // symbols from php's own objects and the link fails with LNK2005. + FileSystem::replaceFileStr($makefile, "\r\nLDFLAGS_MICRO=", "\r\nLDFLAGS_MICRO=/FORCE:MULTIPLE /ignore:4006 "); + } + #[Stage] public function makeMicroForWindows(TargetPackage $package, PackageBuilder $builder, PackageInstaller $installer): void { From 6dc5c5b7709d48430af10d9f0de7370b40469a4e Mon Sep 17 00:00:00 2001 From: m-this <52073029+m-this@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:11:27 +0200 Subject: [PATCH 3/9] fix(pgsql): link secur32 and crypt32 on windows - libpq authenticates through SSPI and the static libcrypto behind it reads the system cert store; php.exe failed to link with unresolved Cert* and SSPI symbols when the openssl extension is not in the build --- src/Package/Extension/pgsql.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Package/Extension/pgsql.php b/src/Package/Extension/pgsql.php index b5b22a00..5b7f50a0 100644 --- a/src/Package/Extension/pgsql.php +++ b/src/Package/Extension/pgsql.php @@ -5,11 +5,15 @@ declare(strict_types=1); namespace Package\Extension; use Package\Target\php; +use StaticPHP\Attribute\Package\BeforeStage; use StaticPHP\Attribute\Package\CustomPhpConfigureArg; use StaticPHP\Attribute\Package\Extension; +use StaticPHP\Attribute\PatchDescription; use StaticPHP\Package\PackageBuilder; use StaticPHP\Package\PackageInstaller; use StaticPHP\Package\PhpExtensionPackage; +use StaticPHP\Package\TargetPackage; +use StaticPHP\Util\FileSystem; use StaticPHP\Util\SPCConfigUtil; #[Extension('pgsql')] @@ -39,6 +43,27 @@ class pgsql extends PhpExtensionPackage return "--with-pgsql={$builder->getBuildRootPath()}"; } + #[BeforeStage('php', [php::class, 'buildconfForWindows'], 'ext-pgsql')] + #[PatchDescription('Link the Win32 system libraries the static libpq needs')] + public function patchConfigW32ForWindows(TargetPackage $package): void + { + $config = "{$package->getSourceDir()}\\ext\\pgsql\\config.w32"; + + if (str_contains(FileSystem::readFile($config), 'LIBS_PGSQL')) { + return; + } + + // libpq uses SSPI for Windows auth (secur32) and the static libcrypto behind it + // reads the system cert store (crypt32). Nothing else puts these on the link line + // when the openssl extension itself is not part of the build. + FileSystem::replaceFileStr( + $config, + 'EXTENSION("pgsql", "pgsql.c", PHP_PGSQL_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");', + 'EXTENSION("pgsql", "pgsql.c", PHP_PGSQL_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");' . "\n\t\t" . + 'ADD_FLAG("LIBS_PGSQL", "secur32.lib crypt32.lib");' + ); + } + public function getSharedExtensionEnv(): array { $parent = parent::getSharedExtensionEnv(); From 43b2da05115571c51aa964a7b538af0e51c90202 Mon Sep 17 00:00:00 2001 From: m-this <52073029+m-this@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:11:27 +0200 Subject: [PATCH 4/9] fix(libiconv): only put the static lib on the windows link line - static-libs@windows listed both libiconv.lib (the dll import lib) and libiconv_a.lib; the import lib came first, so php.exe ended up importing libiconv.dll and the cli smoke test died with STATUS_DLL_NOT_FOUND on machines without it --- config/pkg/lib/libiconv-win.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/pkg/lib/libiconv-win.yml b/config/pkg/lib/libiconv-win.yml index 103acf2e..91327149 100644 --- a/config/pkg/lib/libiconv-win.yml +++ b/config/pkg/lib/libiconv-win.yml @@ -9,5 +9,4 @@ libiconv-win: license-files: [source/COPYING] license: GPL-3.0-or-later static-libs@windows: - - libiconv.lib - libiconv_a.lib From d7902b9931ae520b2d2485647191832d384c3a8c Mon Sep 17 00:00:00 2001 From: m-this <52073029+m-this@users.noreply.github.com> Date: Wed, 8 Jul 2026 14:17:36 +0200 Subject: [PATCH 5/9] feat(tool): bundle python for the meson build - add a python-win tool package that unzips the official python.org nuget package into the pkgroot (venv and ensurepip included) - meson uses it to build its venv and only falls back to a system python, so no manual environment setup is needed --- config/pkg/lib/postgresql.yml | 1 + config/pkg/tool/python-win.yml | 8 ++++ src/Package/Artifact/meson_win.php | 7 ++- src/Package/Artifact/python_win.php | 66 +++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 config/pkg/tool/python-win.yml create mode 100644 src/Package/Artifact/python_win.php diff --git a/config/pkg/lib/postgresql.yml b/config/pkg/lib/postgresql.yml index 290650df..b010be33 100644 --- a/config/pkg/lib/postgresql.yml +++ b/config/pkg/lib/postgresql.yml @@ -18,6 +18,7 @@ postgresql: - openssl - zlib tools@windows: + - python-win - meson - ninja - winflexbison diff --git a/config/pkg/tool/python-win.yml b/config/pkg/tool/python-win.yml new file mode 100644 index 00000000..388c1ace --- /dev/null +++ b/config/pkg/tool/python-win.yml @@ -0,0 +1,8 @@ +python-win: + type: tool + artifact: + binary: custom + tool: + provides: + - python.exe + binary-subdir: python-win/tools diff --git a/src/Package/Artifact/meson_win.php b/src/Package/Artifact/meson_win.php index d3a911be..1594579b 100644 --- a/src/Package/Artifact/meson_win.php +++ b/src/Package/Artifact/meson_win.php @@ -22,8 +22,11 @@ class meson_win $dir = dirname($target_path); $venv = "{$dir}\\venv"; + // Prefer the python-win tool package (installed alongside via tools@windows), + // fall back to whatever Python the machine already has. + $candidates = ['"' . PKG_ROOT_PATH . '\python-win\tools\python.exe"', 'python', 'py -3']; $python = null; - foreach (['python', 'py -3'] as $candidate) { + foreach ($candidates as $candidate) { [$code] = cmd()->execWithResult("{$candidate} --version", false); if ($code === 0) { $python = $candidate; @@ -31,7 +34,7 @@ class meson_win } } if ($python === null) { - throw new EnvironmentException('meson needs Python 3 on PATH. Install it from https://www.python.org or with `winget install Python.Python.3.13`.'); + throw new EnvironmentException('meson needs Python 3; the python-win tool package did not install and no system Python was found.'); } if (is_dir($venv)) { diff --git a/src/Package/Artifact/python_win.php b/src/Package/Artifact/python_win.php new file mode 100644 index 00000000..f3aa7135 --- /dev/null +++ b/src/Package/Artifact/python_win.php @@ -0,0 +1,66 @@ +getLatestVersion($downloader->getRetry()); + + $url = "https://api.nuget.org/v3-flatcontainer/python/{$version}/python.{$version}.nupkg"; + // .nupkg is a zip; name the cache file .zip so the extractor treats it as one + $path = DOWNLOAD_PATH . DIRECTORY_SEPARATOR . "python-win-{$version}.zip"; + default_shell()->executeCurlDownload($url, $path, retries: $downloader->getRetry()); + + return DownloadResult::archive(basename($path), ['url' => $url, 'version' => $version], extract: '{pkg_root_path}/python-win', version: $version); + } + + #[CustomBinaryCheckUpdate('python-win', ['windows-x86_64'])] + public function checkUpdateBinary(?string $old_version): CheckUpdateResult + { + $version = $this->getLatestVersion(); + return new CheckUpdateResult( + old: $old_version, + new: $version, + needUpdate: $old_version === null || $version !== $old_version, + ); + } + + #[AfterBinaryExtract('python-win', ['windows-x86_64'])] + public function afterExtract(string $target_path): void + { + if (!file_exists("{$target_path}\\tools\\python.exe")) { + throw new DownloaderException("Python installation appears incomplete: python.exe not found at {$target_path}\\tools\\python.exe"); + } + } + + private function getLatestVersion(int $retries = 0): string + { + $index = default_shell()->executeCurl('https://api.nuget.org/v3-flatcontainer/python/index.json', retries: $retries); + $versions = $index ? (json_decode($index, true)['versions'] ?? []) : []; + $stable = array_filter($versions, fn ($v) => preg_match('/^\d+\.\d+\.\d+$/', $v)); + if ($stable === []) { + throw new DownloaderException('Failed to get Python versions from the nuget index'); + } + return end($stable); + } +} From 303dca5f5944231502bbda227e8707efe16af690 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 9 Jul 2026 11:03:15 +0900 Subject: [PATCH 6/9] Remove doctor item --- config/pkg/lib/openssl.yml | 1 + .../Doctor/Item/WindowsToolCheck.php | 41 ------------------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/config/pkg/lib/openssl.yml b/config/pkg/lib/openssl.yml index 42672183..aa3aa42c 100644 --- a/config/pkg/lib/openssl.yml +++ b/config/pkg/lib/openssl.yml @@ -27,4 +27,5 @@ openssl: - libcrypto.lib tools@windows: - jom + - nasm - strawberry-perl diff --git a/src/StaticPHP/Doctor/Item/WindowsToolCheck.php b/src/StaticPHP/Doctor/Item/WindowsToolCheck.php index 3fe1d312..b802790d 100644 --- a/src/StaticPHP/Doctor/Item/WindowsToolCheck.php +++ b/src/StaticPHP/Doctor/Item/WindowsToolCheck.php @@ -12,7 +12,6 @@ use StaticPHP\Package\PackageInstaller; use StaticPHP\Runtime\SystemTarget; use StaticPHP\Toolchain\ToolchainManager; use StaticPHP\Util\FileSystem; -use StaticPHP\Util\GlobalEnvManager; use StaticPHP\Util\System\WindowsUtil; #[OptionalCheck([self::class, 'optional'])] @@ -74,27 +73,6 @@ class WindowsToolCheck return CheckResult::ok($path); } - #[CheckItem('if nasm installed', level: 995)] - public function checkNasm(): ?CheckResult - { - if (($a = WindowsUtil::findCommand('nasm.exe')) === null) { - return CheckResult::fail('nasm.exe not found in path.', 'install-nasm'); - } - return CheckResult::ok($a); - } - - #[CheckItem('if perl(strawberry) installed', limit_os: 'Windows', level: 994)] - public function checkPerl(): ?CheckResult - { - if (($path = WindowsUtil::findCommand('perl.exe')) === null) { - return CheckResult::fail('perl not found in path.', 'install-perl'); - } - if (!str_contains(implode('', cmd()->execWithResult(quote($path) . ' -v', false)[1]), 'MSWin32')) { - return CheckResult::fail($path . ' is not built for msvc.', 'install-perl'); - } - return CheckResult::ok($path); - } - #[CheckItem('if environment is properly set up', level: 1)] public function checkenv(): ?CheckResult { @@ -113,16 +91,6 @@ class WindowsToolCheck return CheckResult::ok(); } - #[FixItem('install-perl')] - public function installPerl(): bool - { - $installer = new PackageInstaller(); - $installer->addInstallPackage('strawberry-perl'); - $installer->run(true); - GlobalEnvManager::addPathIfNotExists(PKG_ROOT_PATH . '\strawberry-perl'); - return true; - } - #[FixItem('install-msys2-build-essentials')] public function installMsys2(): bool { @@ -141,15 +109,6 @@ class WindowsToolCheck return true; } - #[FixItem('install-nasm')] - public function installNasm(): bool - { - $installer = new PackageInstaller(interactive: false); - $installer->addInstallPackage('nasm'); - $installer->run(true); - return true; - } - #[FixItem('install-vswhere')] public function installVSWhere(): bool { From 150998723da39b9812facee0b2efa60ba630dd87 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 9 Jul 2026 11:03:32 +0900 Subject: [PATCH 7/9] Fix windows captainhook --- captainhook.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/captainhook.json b/captainhook.json index 1057cb29..cd718dbe 100644 --- a/captainhook.json +++ b/captainhook.json @@ -20,7 +20,7 @@ ] }, { - "action": "bin/spc dev:lint-config --check", + "action": "php bin/spc dev:lint-config --check", "conditions": [ { "exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\InDirectory", From 0bd94b0ebd8bedf7dc7fb2b1c2342896e743a510 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 9 Jul 2026 11:03:39 +0900 Subject: [PATCH 8/9] Lint --- config/pkg/lib/postgresql.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config/pkg/lib/postgresql.yml b/config/pkg/lib/postgresql.yml index b010be33..439e4e30 100644 --- a/config/pkg/lib/postgresql.yml +++ b/config/pkg/lib/postgresql.yml @@ -17,12 +17,6 @@ postgresql: depends@windows: - openssl - zlib - tools@windows: - - python-win - - meson - - ninja - - winflexbison - - strawberry-perl suggests@unix: - icu - libxslt @@ -34,3 +28,9 @@ postgresql: - libpq.lib - libpgport.lib - libpgcommon.lib + tools@windows: + - python-win + - meson + - ninja + - winflexbison + - strawberry-perl From 274706084ab1d9986be8cce73779d996b3875c11 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 9 Jul 2026 11:04:00 +0900 Subject: [PATCH 9/9] Fix frankenphp package path bug --- src/Package/Target/php/unix.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Package/Target/php/unix.php b/src/Package/Target/php/unix.php index 7d4d0063..dd2ae526 100644 --- a/src/Package/Target/php/unix.php +++ b/src/Package/Target/php/unix.php @@ -59,7 +59,7 @@ trait unix } if (self::getPHPVersionID() >= 80300 && self::getPHPVersionID() < 80400) { - SourcePatcher::patchFile('spc_fix_avx512_cache_before_80400.patch', $this->getSourceDir()); + SourcePatcher::patchFile('spc_fix_avx512_cache_before_80400.patch', $package->getSourceDir()); } }