From 020666ccedadab4f63c67914bebf298e8f1cd285 Mon Sep 17 00:00:00 2001 From: Tianfeng Han Date: Fri, 31 Jul 2026 11:57:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=20Windows=20=E6=89=93?= =?UTF-8?q?=E5=8C=85=E5=92=8C=E5=89=8D=E7=BD=AE=E6=A3=80=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/tpc.php | 1 + package-on-windows.php | 408 ++++++++++++++++++-------- phpunit/src/Platform/PlatformTest.php | 17 +- src/Build/SourcePipelineTrait.php | 7 +- src/Platform/PlatformBase.php | 7 +- src/Platform/Windows.php | 24 +- version.txt | 2 +- 7 files changed, 319 insertions(+), 147 deletions(-) diff --git a/bin/tpc.php b/bin/tpc.php index 867a119e..f0fd0bff 100755 --- a/bin/tpc.php +++ b/bin/tpc.php @@ -5,4 +5,5 @@ require __DIR__ . '/../src/polyfills.php'; require __DIR__ . '/../src/gen_stub.php'; require __DIR__ . '/../src/compiler.php'; +define('TYPEPHP_PHP_SCRIPT_ENTRY', true); main($argc, $argv); diff --git a/package-on-windows.php b/package-on-windows.php index 5aabf623..63ba0f70 100644 --- a/package-on-windows.php +++ b/package-on-windows.php @@ -52,19 +52,24 @@ if (file_exists($versionFile)) { } $versionId++; -file_put_contents($versionFile, (string)$versionId); echo "当前版本: {$versionId}\n\n"; // ==================== 2. 检测系统架构 ==================== echo "[2/7] 检测系统架构...\n"; -$arch = match (getenv('PROCESSOR_ARCHITECTURE')) { +$processorArchitecture = getenv('PROCESSOR_ARCHITEW6432') ?: getenv('PROCESSOR_ARCHITECTURE'); +$arch = match (strtoupper((string)$processorArchitecture)) { 'AMD64' => 'x86_64', 'ARM64' => 'arm64', - 'x86' => 'i386', - default => getenv('PROCESSOR_ARCHITECTURE') ?: 'unknown', + default => null, }; +if ($arch === null || PHP_INT_SIZE !== 8) { + $detectedArchitecture = $processorArchitecture ?: 'unknown'; + echo "错误: TypePHP 不支持 32 位或未知 Windows 架构 - {$detectedArchitecture}\n"; + echo "仅支持 Windows x86_64 和 ARM64\n"; + exit(1); +} $osType = 'windows'; $outputFile = "tpc_v{$versionId}_{$osType}_{$arch}.zip"; @@ -91,13 +96,135 @@ foreach ($requiredFiles as $file) { } } +$phpEmbedLibCandidates = [ + 'SDK/lib/php8embed.lib' => "{$phpDir}/SDK/lib/php8embed.lib", + 'lib/php8embed.lib' => "{$phpDir}/lib/php8embed.lib", + 'php8embed.lib' => "{$phpDir}/php8embed.lib", +]; +$phpEmbedLib = null; +$phpEmbedLibRelativePath = null; +foreach ($phpEmbedLibCandidates as $relativePath => $candidate) { + if (is_file($candidate)) { + $phpEmbedLib = $candidate; + $phpEmbedLibRelativePath = $relativePath; + break; + } +} +if ($phpEmbedLib === null) { + echo "错误: 未找到 Windows 嵌入式 PHP 导入库 php8embed.lib\n"; + echo "请先构建或安装包含 php8embed.lib 的 PHP SDK\n"; + exit(1); +} + +$phpCoreLibCandidates = [ + 'SDK/lib/php8ts.lib' => "{$phpDir}/SDK/lib/php8ts.lib", + 'SDK/lib/php8.lib' => "{$phpDir}/SDK/lib/php8.lib", + 'lib/php8ts.lib' => "{$phpDir}/lib/php8ts.lib", + 'lib/php8.lib' => "{$phpDir}/lib/php8.lib", + 'php8ts.lib' => "{$phpDir}/php8ts.lib", + 'php8.lib' => "{$phpDir}/php8.lib", +]; +$phpCoreLibRelativePath = null; +foreach ($phpCoreLibCandidates as $relativePath => $candidate) { + if (is_file($candidate)) { + $phpCoreLibRelativePath = $relativePath; + break; + } +} +if ($phpCoreLibRelativePath === null) { + echo "错误: 未找到 php8ts.lib 或 php8.lib\n"; + exit(1); +} + +$phpRuntimeDllRelativePath = is_file("{$phpDir}/php8ts.dll") + ? 'php8ts.dll' + : (is_file("{$phpDir}/php8.dll") ? 'php8.dll' : null); +if ($phpRuntimeDllRelativePath === null) { + echo "错误: 未找到 php8ts.dll 或 php8.dll\n"; + exit(1); +} + +$phpxIncludeDir = "{$phpxDir}/include"; +$phpxLibFile = "{$phpxDir}/lib/phpx.lib"; +$windowsLinkLibraryFiles = [ + 'gmp.lib', + 'gmpxx.lib', + 'mpfr.lib', + 'libmpdec-4.0.1.dll.lib', + 'libmpdec++-4.0.1.dll.lib', +]; +foreach ($windowsLinkLibraryFiles as $libraryFile) { + $libraryPath = "{$phpDir}/SDK/lib/{$libraryFile}"; + if (!is_file($libraryPath)) { + echo "错误: Windows 链接依赖库不存在 - {$libraryPath}\n"; + exit(1); + } +} +$phpxMiscDir = "{$phpxDir}/src/misc"; +$phpxDllFile = "{$phpxDir}/build/phpx.dll"; +$requiredPhpxPaths = [ + 'PHPX include directory' => $phpxIncludeDir, + 'PHPX misc source directory' => $phpxMiscDir, + 'PHPX import library' => $phpxLibFile, + 'PHPX runtime library' => $phpxDllFile, +]; +foreach ($requiredPhpxPaths as $description => $path) { + if (!file_exists($path)) { + echo "错误: {$description} 不存在 - {$path}\n"; + echo "请先在 {$phpxDir}/build 中执行 nmake phpx\n"; + exit(1); + } +} + echo "所有文件检查通过\n\n"; +$topLevelDir = "tpc_v{$versionId}_{$osType}_{$arch}"; +if (is_dir($topLevelDir)) { + echo "清理旧的临时目录...\n"; + removeDirectory($topLevelDir); +} +mustCreateDirectory($topLevelDir); + +$cleanupStage = true; +register_shutdown_function(static function () use (&$cleanupStage, $topLevelDir): void { + if ($cleanupStage && is_dir($topLevelDir)) { + try { + removeDirectory($topLevelDir); + } catch (Throwable $error) { + fwrite(STDERR, "警告: 无法清理临时目录 {$topLevelDir}: {$error->getMessage()}\n"); + } + } +}); + +$packagedCompilerExe = "{$topLevelDir}/{$compilerExe}"; +mustCopy($compilerExe, $packagedCompilerExe); +$windowsSetupFile = "{$topLevelDir}/WINDOWS-SETUP.txt"; +$windowsSetup = <<<'TEXT' +TypePHP Windows setup +===================== + +TypePHP for Windows uses the prebuilt PHPX DLL and import library included in +this package. The Composer path vendor\swoole\phpx is not used on Windows. + +Open a command prompt in this directory and run: + + set PHP_HOME=%CD% + set PHPX_HOME=%CD%\phpx + set PATH=%CD%;%PATH% + +PHPX_HOME must point to the bundled phpx directory. Its required files include: + + phpx\include + phpx\lib\phpx.lib + phpx\src\misc + +TEXT; +mustWriteFile($windowsSetupFile, $windowsSetup); + // ==================== 4. UPX 压缩(可选) ==================== echo "[4/7] UPX 压缩优化...\n"; $useUpx = false; -$backupExe = "{$compilerExe}.backup"; // 检查 UPX 是否可用 exec('where upx 2>nul', $upxOutput, $upxReturn); @@ -107,20 +234,16 @@ if ($upxReturn === 0) { $upxVersion = $versionOutput[0] ?? 'unknown'; echo "检测到 UPX: {$upxVersion}\n"; - // 备份原始文件 - copy($compilerExe, $backupExe); - echo "备份原始文件: {$backupExe}\n"; - - // 使用 UPX 压缩 - echo "使用 UPX 压缩 {$compilerExe} ...\n"; - exec("upx --best \"{$compilerExe}\" 2>&1", $upxOutput, $upxResult); + // 只压缩暂存目录中的副本,不修改工作区内的原始 tpc.exe。 + echo "使用 UPX 压缩 {$packagedCompilerExe} ...\n"; + $originalSize = filesize($packagedCompilerExe); + exec('upx --best ' . escapeshellarg($packagedCompilerExe) . ' 2>&1', $upxOutput, $upxResult); if ($upxResult === 0) { echo "✓ UPX 压缩完成\n"; // 显示压缩效果 - $originalSize = filesize($backupExe); - $compressedSize = filesize($compilerExe); + $compressedSize = filesize($packagedCompilerExe); $originalSizeMB = round($originalSize / 1024 / 1024, 2); $compressedSizeMB = round($compressedSize / 1024 / 1024, 2); $compressionRatio = round((1 - $compressedSize / $originalSize) * 100, 2); @@ -131,8 +254,8 @@ if ($upxReturn === 0) { $useUpx = true; } else { - echo "✗ UPX 压缩失败,恢复原始文件\n"; - rename($backupExe, $compilerExe); + echo "✗ UPX 压缩失败,重新复制未压缩的 tpc.exe\n"; + mustCopy($compilerExe, $packagedCompilerExe); $useUpx = false; } } else { @@ -145,21 +268,7 @@ echo "\n"; // ==================== 5. 准备 PHP 目录结构 ==================== echo "[5/7] 准备 PHP 目录结构...\n"; -// 创建顶层目录(压缩包根目录) -$topLevelDir = "tpc_v{$versionId}_{$osType}_{$arch}"; - -// 清理旧的临时目录 -if (is_dir($topLevelDir)) { - echo "清理旧的临时目录...\n"; - removeDirectory($topLevelDir); -} - -// 创建顶层目录 -mkdir($topLevelDir, 0755, true); echo "创建顶层目录: {$topLevelDir}\n"; - -// 复制编译器可执行文件到顶层目录 -copy($compilerExe, "{$topLevelDir}/{$compilerExe}"); echo "复制 {$compilerExe} -> {$topLevelDir}/\n"; // 递归复制 PHP_HOME 下的所有文件到顶层目录 @@ -168,8 +277,16 @@ if (!is_dir($phpDir)) { echo "跳过 PHP 文件复制\n\n"; } else { echo "复制 PHP 运行时文件...\n"; - // 排除 dev/php8ts.lib(SDK 目录下已有)和根目录的 php8embed.lib(SDK/lib/ 目录下已有) - copyDirectory($phpDir, $topLevelDir, ['dev/php8ts.lib', 'php8embed.lib']); + // dev/php8ts.lib 与 SDK/lib/php8ts.lib 重复,可以排除。 + // php8embed.lib 必须保留;部分 PHP SDK 仅在根目录提供该导入库。 + copyDirectory($phpDir, $topLevelDir, ['dev/php8ts.lib']); + + $packagedEmbedLib = "{$topLevelDir}/{$phpEmbedLibRelativePath}"; + if (!is_file($packagedEmbedLib)) { + echo "错误: php8embed.lib 未复制到打包目录 - {$packagedEmbedLib}\n"; + exit(1); + } + echo " 已包含: php8embed.lib\n"; // 统计复制的文件数量 $iterator = new RecursiveIteratorIterator( @@ -186,7 +303,7 @@ echo "\n"; echo "[6/7] 复制项目文件到 PHP 目录...\n"; // 在顶层目录下创建子目录 -mkdir("{$topLevelDir}/examples", 0755, true); +mustCreateDirectory("{$topLevelDir}/examples"); // 复制项目文件 $projectFiles = [ @@ -199,9 +316,9 @@ $projectFiles = [ foreach ($projectFiles as $src => $destDir) { $destPath = "{$topLevelDir}/{$destDir}"; if (!is_dir($destPath)) { - mkdir($destPath, 0755, true); + mustCreateDirectory($destPath); } - copy($src, "{$destPath}/" . basename($src)); + mustCopy($src, "{$destPath}/" . basename($src)); echo " 复制: {$src} -> {$destPath}/\n"; } @@ -232,8 +349,8 @@ echo "[6.3/7] 复制 vendor 目录...\n"; $vendorDir = 'vendor'; if (!is_dir($vendorDir)) { - echo "警告: vendor 目录不存在\n"; - echo "请先运行 composer install\n\n"; + echo "错误: vendor 目录不存在,请先运行 composer install\n"; + exit(1); } else { echo "复制 vendor 目录...\n"; // 排除 vendor/swoole/phpx 目录(Windows 下不需要 composer 安装的 phpx) @@ -255,63 +372,30 @@ echo "[6.5/7] 复制 phpx 相关文件...\n"; // 检查 phpx 目录是否存在 if (!is_dir($phpxDir)) { - echo "警告: phpx 目录不存在 - {$phpxDir}\n"; - echo "跳过 phpx 文件复制\n\n"; + echo "错误: phpx 目录不存在 - {$phpxDir}\n"; + exit(1); } else { // 创建 phpx 子目录 - mkdir("{$topLevelDir}/phpx/include", 0755, true); - mkdir("{$topLevelDir}/phpx/lib", 0755, true); + mustCreateDirectory("{$topLevelDir}/phpx/include"); + mustCreateDirectory("{$topLevelDir}/phpx/lib"); // 复制 phpx include 头文件 - $phpxIncludeDir = "{$phpxDir}/include"; - if (is_dir($phpxIncludeDir)) { - copyDirectory($phpxIncludeDir, "{$topLevelDir}/phpx/include"); - echo " 复制: phpx/include -> phpx/include/\n"; - } else { - echo " 警告: phpx/include 目录不存在\n"; - } + copyDirectory($phpxIncludeDir, "{$topLevelDir}/phpx/include"); + echo " 复制: phpx/include -> phpx/include/\n"; - // 复制 phpx lib 库文件(只保留 phpx.lib) - $phpxLibDir = "{$phpxDir}/lib"; - $phpxLibFile = "{$phpxLibDir}/phpx.lib"; - if (file_exists($phpxLibFile)) { - copy($phpxLibFile, "{$topLevelDir}/phpx/lib/phpx.lib"); - echo " 复制: phpx.lib -> phpx/lib/\n"; - } else { - echo " 警告: phpx.lib 文件不存在\n"; - } + // PHPX 目录只保留自身导入库;gmp/mpfr/mpdecimal 位于 PHP SDK/lib。 + mustCopy($phpxLibFile, "{$topLevelDir}/phpx/lib/phpx.lib"); + echo " 复制: phpx.lib -> phpx/lib/\n"; // 复制 phpx/src/misc 目录(辅助工具和头文件) - $phpxMiscDir = "{$phpxDir}/src/misc"; - if (is_dir($phpxMiscDir)) { - mkdir("{$topLevelDir}/phpx/src/misc", 0755, true); - // 排除 .obj 文件(MSVC 目标文件)和 .d 文件(依赖文件) - copyDirectory($phpxMiscDir, "{$topLevelDir}/phpx/src/misc", [], null, ['obj', 'd']); - echo " 复制: phpx/src/misc -> phpx/src/misc/\n"; - } else { - echo " 警告: phpx/src/misc 目录不存在\n"; - } + mustCreateDirectory("{$topLevelDir}/phpx/src/misc"); + // 排除 .obj 文件(MSVC 目标文件)和 .d 文件(依赖文件) + copyDirectory($phpxMiscDir, "{$topLevelDir}/phpx/src/misc", [], null, ['obj', 'd']); + echo " 复制: phpx/src/misc -> phpx/src/misc/\n"; // 复制 phpx.dll 到顶层目录 - $phpxDllCandidates = [ - "{$phpxDir}/build/phpx.dll", - "{$phpxDir}/bin/phpx.dll", - "{$phpxDir}/phpx.dll", - ]; - - $dllCopied = false; - foreach ($phpxDllCandidates as $dllPath) { - if (file_exists($dllPath)) { - copy($dllPath, "{$topLevelDir}/phpx.dll"); - echo " 复制: phpx.dll -> {$topLevelDir}/\n"; - $dllCopied = true; - break; - } - } - - if (!$dllCopied) { - echo " 警告: 未找到 phpx.dll 文件\n"; - } + mustCopy($phpxDllFile, "{$topLevelDir}/phpx.dll"); + echo " 复制: phpx.dll -> {$topLevelDir}/\n"; echo "phpx 文件复制完成\n\n"; } @@ -321,7 +405,9 @@ echo "[7/7] 创建压缩包...\n"; // 删除旧的压缩包 if (file_exists($outputFile)) { - unlink($outputFile); + if (!unlink($outputFile)) { + throw new RuntimeException("无法删除旧的压缩包: {$outputFile}"); + } echo "已删除旧的压缩包\n"; } @@ -330,16 +416,6 @@ if (!class_exists('ZipArchive')) { echo "错误: ZipArchive 扩展未启用\n"; echo "请在 php.ini 中启用 extension=zip\n"; - // 恢复原始文件 - if ($useUpx && file_exists($backupExe)) { - rename($backupExe, $compilerExe); - } - - // 清理临时目录 - if (is_dir($tempPhpDir)) { - removeDirectory($tempPhpDir); - } - exit(1); } @@ -347,16 +423,6 @@ $zip = new ZipArchive(); if ($zip->open($outputFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { echo "错误: 无法创建压缩包\n"; - // 恢复原始文件 - if ($useUpx && file_exists($backupExe)) { - rename($backupExe, $compilerExe); - } - - // 清理临时目录 - if (is_dir($tempPhpDir)) { - removeDirectory($tempPhpDir); - } - exit(1); } @@ -375,11 +441,41 @@ foreach ($files as $file) { $relativePath = substr($filePathNormalized, strlen($topLevelDirNormalized) + 1); // 在相对路径前加上顶层目录名 $zipPath = "{$topLevelDir}/{$relativePath}"; - $zip->addFile($filePath, $zipPath); + if (!$zip->addFile($filePath, $zipPath)) { + throw new RuntimeException("无法将文件加入压缩包: {$filePath}"); + } } } -$zip->close(); +if (!$zip->close()) { + throw new RuntimeException("无法完成压缩包写入: {$outputFile}"); +} + +$requiredArchiveEntries = [ + "{$topLevelDir}/{$compilerExe}", + "{$topLevelDir}/WINDOWS-SETUP.txt", + "{$topLevelDir}/phpx.dll", + "{$topLevelDir}/{$phpRuntimeDllRelativePath}", + "{$topLevelDir}/{$phpEmbedLibRelativePath}", + "{$topLevelDir}/{$phpCoreLibRelativePath}", + "{$topLevelDir}/phpx/include/phpx.h", + "{$topLevelDir}/phpx/lib/phpx.lib", + "{$topLevelDir}/phpx/src/misc/typephp_helper.cc", +]; +foreach ($windowsLinkLibraryFiles as $libraryFile) { + $requiredArchiveEntries[] = "{$topLevelDir}/SDK/lib/{$libraryFile}"; +} +$verificationZip = new ZipArchive(); +if ($verificationZip->open($outputFile) !== true) { + throw new RuntimeException("无法重新打开压缩包进行验证: {$outputFile}"); +} +foreach ($requiredArchiveEntries as $entry) { + if ($verificationZip->locateName($entry) === false) { + $verificationZip->close(); + throw new RuntimeException("压缩包缺少必需文件: {$entry}"); + } +} +$verificationZip->close(); echo "✓ 压缩包创建成功\n\n"; // ==================== 8. 清理和恢复 ==================== @@ -391,14 +487,19 @@ $packageSizeMB = round($packageSize / 1024 / 1024, 2); echo "临时目录: {$topLevelDir}\n"; removeDirectory($topLevelDir); +$cleanupStage = false; echo "临时目录已清理\n"; -// 如果使用了 UPX,恢复原始文件 -if ($useUpx && file_exists($backupExe)) { - echo "恢复原始二进制文件...\n"; - rename($backupExe, $compilerExe); - @unlink($backupExe); - echo "原始文件已恢复\n"; +try { + mustWriteFile($versionFile, (string)$versionId); +} catch (Throwable $error) { + if (is_file($outputFile) && !unlink($outputFile)) { + throw new RuntimeException( + "无法提交版本号,且无法删除未提交的压缩包 {$outputFile}: {$error->getMessage()}", + previous: $error, + ); + } + throw $error; } echo "\n"; @@ -412,6 +513,7 @@ echo "输出文件: {$outputFile}\n"; echo "文件大小: {$packageSizeMB} MB\n\n"; echo "包含内容:\n"; echo " - {$compilerExe} (编译器可执行文件)\n"; +echo " - WINDOWS-SETUP.txt (Windows 环境变量与 PHPX_HOME 配置说明)\n"; echo " - phpx.dll (PHPX 运行时库)\n"; echo " - phpx/include/ (PHPX 头文件)\n"; echo " - phpx/lib/ (PHPX 库文件)\n"; @@ -425,8 +527,10 @@ echo " - examples/win32-hello/ (Windows GUI 编程实例)\n"; echo " - examples/tetris-win32/ (俄罗斯方块游戏实例)\n\n"; echo "使用说明:\n"; echo " 1. 解压到任意目录\n"; -echo " 2. 确保 PHP 8.4+ 已安装并添加到 PATH\n"; -echo " 3. 运行: tpc \n\n"; +echo " 2. set PHP_HOME=%CD%\n"; +echo " 3. set PHPX_HOME=%CD%\\phpx\n"; +echo " 4. set PATH=%CD%;%PATH%\n"; +echo " 5. 运行: tpc \n\n"; /** * 递归复制目录 @@ -439,7 +543,7 @@ echo " 3. 运行: tpc \n\n"; function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?string $baseSrc = null, array $excludeExtensions = []): void { if (!is_dir($src)) { - return; + throw new RuntimeException("源目录不存在: {$src}"); } if ($baseSrc === null) { @@ -447,10 +551,14 @@ function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?stri } if (!is_dir($dest)) { - mkdir($dest, 0755, true); + mustCreateDirectory($dest); } - $files = array_diff(scandir($src), ['.', '..']); + $scannedFiles = scandir($src); + if ($scannedFiles === false) { + throw new RuntimeException("无法读取目录: {$src}"); + } + $files = array_diff($scannedFiles, ['.', '..']); foreach ($files as $file) { $srcPath = "{$src}/{$file}"; $destPath = "{$dest}/{$file}"; @@ -469,7 +577,8 @@ function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?stri // 检查当前路径是否在排除列表中 $shouldExclude = false; foreach ($excludeDirs as $excludeDir) { - if (strpos($relativePath, $excludeDir) === 0 || $relativePath === $excludeDir) { + $excludeDir = rtrim(str_replace('\\', '/', $excludeDir), '/'); + if ($relativePath === $excludeDir || str_starts_with($relativePath, $excludeDir . '/')) { $shouldExclude = true; break; } @@ -482,8 +591,49 @@ function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?stri if (is_dir($srcPath)) { copyDirectory($srcPath, $destPath, $excludeDirs, $baseSrc, $excludeExtensions); } else { - copy($srcPath, $destPath); + mustCopy($srcPath, $destPath); + } + } +} + +function mustCreateDirectory(string $dir): void +{ + if (!is_dir($dir) && !mkdir($dir, 0755, true) && !is_dir($dir)) { + throw new RuntimeException("无法创建目录: {$dir}"); + } +} + +function mustCopy(string $src, string $dest): void +{ + if (!is_file($src)) { + throw new RuntimeException("源文件不存在: {$src}"); + } + if (!copy($src, $dest)) { + throw new RuntimeException("无法复制文件: {$src} -> {$dest}"); + } +} + +function mustWriteFile(string $path, string $contents): void +{ + $handle = fopen($path, 'c+b'); + if ($handle === false) { + throw new RuntimeException("无法打开文件进行写入: {$path}"); + } + + try { + if (!flock($handle, LOCK_EX)) { + throw new RuntimeException("无法锁定文件: {$path}"); + } + if (!ftruncate($handle, 0) || rewind($handle) === false) { + throw new RuntimeException("无法清空文件: {$path}"); + } + $length = strlen($contents); + if (fwrite($handle, $contents) !== $length || !fflush($handle)) { + throw new RuntimeException("无法写入文件: {$path}"); } + flock($handle, LOCK_UN); + } finally { + fclose($handle); } } @@ -504,16 +654,24 @@ function removeDirectory(string $dir): void foreach ($iterator as $file) { if ($file->isDir()) { - @rmdir($file->getRealPath()); + if (!rmdir($file->getRealPath())) { + throw new RuntimeException("无法删除目录: {$file->getRealPath()}"); + } } else { // 移除只读属性(Windows) if (PHP_OS_FAMILY === 'Windows' && !is_writable($file->getRealPath())) { - @chmod($file->getRealPath(), 0666); + if (!chmod($file->getRealPath(), 0666)) { + throw new RuntimeException("无法修改文件属性: {$file->getRealPath()}"); + } + } + if (!unlink($file->getRealPath())) { + throw new RuntimeException("无法删除文件: {$file->getRealPath()}"); } - @unlink($file->getRealPath()); } } // 最后删除根目录 - @rmdir($dir); + if (!rmdir($dir)) { + throw new RuntimeException("无法删除目录: {$dir}"); + } } diff --git a/phpunit/src/Platform/PlatformTest.php b/phpunit/src/Platform/PlatformTest.php index 76ee9688..ab1f2d84 100644 --- a/phpunit/src/Platform/PlatformTest.php +++ b/phpunit/src/Platform/PlatformTest.php @@ -78,15 +78,24 @@ class PlatformTest extends TestCase 'The PHPX import library was not found at: ' . $root . '\lib\phpx.lib', $errors, ); - $this->assertContains( - 'The PHPX runtime library `phpx.dll` was not found under: ' . $root, - $errors, - ); + $runtimeError = 'The PHPX runtime library was not found at: ' . $root . '\build\phpx.dll'; + $this->assertContains($runtimeError, $errors); foreach ($diagnostics as $diagnostic) { if (isset($diagnostic['error'])) { $this->assertStringContainsString('Build PHPX first', $diagnostic['info']); } } + + $nativeExecutableDiagnostics = (new Windows())->getBuildLibraryWarnings( + $root, + $root, + 'bin', + checkPhpxRuntime: false, + ); + $this->assertNotContains( + $runtimeError, + array_column($nativeExecutableDiagnostics, 'error'), + ); } finally { rmdir($root); } diff --git a/src/Build/SourcePipelineTrait.php b/src/Build/SourcePipelineTrait.php index 88b7f340..afff4720 100644 --- a/src/Build/SourcePipelineTrait.php +++ b/src/Build/SourcePipelineTrait.php @@ -102,7 +102,12 @@ trait SourcePipelineTrait // Windows 的所有构建模式都依赖 PHPX 导入库和运行库。 // 其他平台仅在嵌入式构建模式下执行现有检查。 if ($this->isBuildModeEmbed() || $this->getPlatform() instanceof Windows) { - foreach ($this->getPlatform()->getBuildLibraryWarnings($this->getPhpDir(), $this->getPhpxDir(), $this->buildMode) as $message) { + foreach ($this->getPlatform()->getBuildLibraryWarnings( + $this->getPhpDir(), + $this->getPhpxDir(), + $this->buildMode, + defined('TYPEPHP_PHP_SCRIPT_ENTRY'), + ) as $message) { if (!empty($message['error'])) { $detail = $message['error']; if (!empty($message['info'])) { diff --git a/src/Platform/PlatformBase.php b/src/Platform/PlatformBase.php index b4dd6ad7..dc015368 100644 --- a/src/Platform/PlatformBase.php +++ b/src/Platform/PlatformBase.php @@ -91,7 +91,12 @@ abstract class PlatformBase /** * 获取构建前的运行库检查告警 */ - public function getBuildLibraryWarnings(string $phpDir, string $phpxDir, string $buildMode): array + public function getBuildLibraryWarnings( + string $phpDir, + string $phpxDir, + string $buildMode, + bool $checkPhpxRuntime = true, + ): array { if ($buildMode !== 'bin' && $buildMode !== 'lib') { return []; diff --git a/src/Platform/Windows.php b/src/Platform/Windows.php index 22d69a80..84db42d5 100644 --- a/src/Platform/Windows.php +++ b/src/Platform/Windows.php @@ -175,7 +175,12 @@ class Windows extends PlatformBase return '/NODEFAULTLIB:LIBCMT'; } - public function getBuildLibraryWarnings(string $phpDir, string $phpxDir, string $buildMode): array + public function getBuildLibraryWarnings( + string $phpDir, + string $phpxDir, + string $buildMode, + bool $checkPhpxRuntime = true, + ): array { $warnings = []; $phpDirs = [ @@ -205,21 +210,10 @@ class Windows extends PlatformBase ]; } - $phpxDllPaths = [ - $phpxDir . '\build\phpx.dll', - $phpxDir . '\lib\phpx.dll', - $phpxDir . '\phpx.dll', - ]; - $hasPhpxDll = false; - foreach ($phpxDllPaths as $phpxDllPath) { - if (is_file($phpxDllPath)) { - $hasPhpxDll = true; - break; - } - } - if (!$hasPhpxDll) { + $phpxDllPath = $phpxDir . '\build\phpx.dll'; + if ($checkPhpxRuntime && !is_file($phpxDllPath)) { $warnings[] = [ - 'error' => 'The PHPX runtime library `phpx.dll` was not found under: ' . $phpxDir, + 'error' => 'The PHPX runtime library was not found at: ' . $phpxDllPath, 'info' => 'Build PHPX first (for example, run `nmake phpx` in ' . $phpxDir . '\build)', ]; } diff --git a/version.txt b/version.txt index d673a7a7..87e831c3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1092 \ No newline at end of file +1094 \ No newline at end of file