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", 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 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/config/pkg/lib/postgresql.yml b/config/pkg/lib/postgresql.yml index 4be33190..439e4e30 100644 --- a/config/pkg/lib/postgresql.yml +++ b/config/pkg/lib/postgresql.yml @@ -28,3 +28,9 @@ postgresql: - libpq.lib - libpgport.lib - libpgcommon.lib + tools@windows: + - python-win + - meson + - ninja + - winflexbison + - strawberry-perl 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/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/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..1594579b --- /dev/null +++ b/src/Package/Artifact/meson_win.php @@ -0,0 +1,48 @@ +execWithResult("{$candidate} --version", false); + if ($code === 0) { + $python = $candidate; + break; + } + } + if ($python === null) { + 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)) { + 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/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); + } +} 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(); 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) 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()); } } 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 { 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 {