fix(build): make Windows PHP package portable with relative extension path

- Update bundled php.ini to use extension_dir = "./ext/" instead of absolute CI runner path
- Add makeWindowsPhpIniPortable function to rewrite extension_dir during packaging
- Preserve enabled extensions and line endings while removing CI-specific path settings
- Include php.exe, php.ini, and ext/php_zip.dll in required archive entries
- Add verification step to ensure packaged PHP runtime works correctly after relocation
- Test
master
韩天峰 4 weeks ago
parent 222911cbde
commit 1b8826338f
  1. 44
      .github/workflows/windows-build.yml
  2. 41
      package.php

@ -408,6 +408,50 @@ jobs:
throw "Expected one Windows release package, found $($packages.Count)"
}
- name: Verify portable Windows PHP configuration
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$package = Get-ChildItem 'tpc_v*_windows_*.zip' -File | Select-Object -First 1
$destination = Join-Path $env:RUNNER_TEMP ('typephp-release-check-' + [guid]::NewGuid())
Expand-Archive -LiteralPath $package.FullName -DestinationPath $destination
$roots = @(Get-ChildItem $destination -Directory)
if ($roots.Count -ne 1) {
throw "Expected one release package directory, found $($roots.Count)"
}
$root = $roots[0].FullName
$phpExe = Join-Path $root 'php.exe'
$phpIni = Join-Path $root 'php.ini'
$checkFile = Join-Path $destination 'check-php-ini.php'
$check = @'
<?php
if (ini_get('extension_dir') !== './ext/' || !extension_loaded('zip')) {
fwrite(STDERR, "Invalid packaged extension configuration\n");
exit(1);
}
echo 'portable-php-ini-ok';
'@
[IO.File]::WriteAllText($checkFile, $check, [Text.UTF8Encoding]::new($false))
$previousScanDir = $env:PHP_INI_SCAN_DIR
try {
# Ignore the runner's extra ini files; test the relocated package.
$env:PHP_INI_SCAN_DIR = ''
foreach ($workingDirectory in @($root, $destination)) {
Push-Location $workingDirectory
try {
$output = (& $phpExe -c $phpIni $checkFile 2>&1 | Out-String).Trim()
if ($LASTEXITCODE -ne 0 -or $output -ne 'portable-php-ini-ok') {
throw "Relocated PHP runtime validation failed in ${workingDirectory}: $output"
}
} finally {
Pop-Location
}
}
} finally {
$env:PHP_INI_SCAN_DIR = $previousScanDir
}
- name: Upload Windows release package
if: startsWith(github.ref, 'refs/tags/') && matrix.php == '8.5'
uses: actions/upload-artifact@v4

@ -228,6 +228,9 @@ PHPX_HOME must point to the bundled phpx directory. Its required files include:
phpx\lib\phpx.lib
phpx\src\misc
The bundled php.ini uses extension_dir = "./ext/" instead of the CI runner's
absolute extension path.
TEXT;
mustWriteFile($windowsSetupFile, $windowsSetup);
@ -254,6 +257,18 @@ if (!is_dir($phpDir)) {
// php8embed.lib 必须保留;部分 PHP SDK 仅在根目录提供该导入库。
copyDirectory($phpDir, $topLevelDir, ['dev/php8ts.lib'], null, ['zip']);
// Do not ship setup-php's runner-specific C:\tools\php\ext setting.
// Rewrite only the staged copy; the CI installation remains untouched.
$packagedPhpIni = "{$topLevelDir}/php.ini";
if (!is_file($packagedPhpIni)) {
throw new RuntimeException("打包目录缺少 php.ini: {$packagedPhpIni}");
}
$phpIni = file_get_contents($packagedPhpIni);
if (!is_string($phpIni)) {
throw new RuntimeException("无法读取 php.ini: {$packagedPhpIni}");
}
mustWriteFile($packagedPhpIni, makeWindowsPhpIniPortable($phpIni));
$packagedEmbedLib = "{$topLevelDir}/{$phpEmbedLibRelativePath}";
if (!is_file($packagedEmbedLib)) {
echo "错误: php8embed.lib 未复制到打包目录 - {$packagedEmbedLib}\n";
@ -426,6 +441,9 @@ if (!$zip->close()) {
$requiredArchiveEntries = [
"{$topLevelDir}/{$compilerExe}",
"{$topLevelDir}/php.exe",
"{$topLevelDir}/php.ini",
"{$topLevelDir}/ext/php_zip.dll",
"{$topLevelDir}/WINDOWS-SETUP.txt",
"{$topLevelDir}/phpx.dll",
"{$topLevelDir}/{$phpRuntimeDllRelativePath}",
@ -780,6 +798,29 @@ function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?stri
}
}
/** Preserve enabled extensions and line endings while removing the CI path. */
function makeWindowsPhpIniPortable(string $ini): string
{
$bom = str_starts_with($ini, "\xEF\xBB\xBF") ? "\xEF\xBB\xBF" : '';
$ini = substr($ini, strlen($bom));
$portable = preg_replace(
'/^[\t ]*extension_dir[\t ]*=[^\r\n]*/mi',
'extension_dir = "./ext/"',
$ini,
-1,
$replacements,
);
if (!is_string($portable)) {
throw new RuntimeException('Unable to rewrite extension_dir in php.ini');
}
if ($replacements === 0) {
$newline = str_contains($ini, "\r\n") ? "\r\n" : "\n";
// Prepend to the global section, not a trailing [PATH=...] section.
$portable = 'extension_dir = "./ext/"' . $newline . $ini;
}
return $bom . $portable;
}
function mustCreateDirectory(string $dir): void
{
if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) {

Loading…
Cancel
Save