改进 Windows 打包和前置检测

pull/43/head
韩天峰 4 weeks ago
parent 9cbe9a0483
commit 020666cced
  1. 1
      bin/tpc.php
  2. 394
      package-on-windows.php
  3. 17
      phpunit/src/Platform/PlatformTest.php
  4. 7
      src/Build/SourcePipelineTrait.php
  5. 7
      src/Platform/PlatformBase.php
  6. 24
      src/Platform/Windows.php
  7. 2
      version.txt

@ -5,4 +5,5 @@ require __DIR__ . '/../src/polyfills.php';
require __DIR__ . '/../src/gen_stub.php'; require __DIR__ . '/../src/gen_stub.php';
require __DIR__ . '/../src/compiler.php'; require __DIR__ . '/../src/compiler.php';
define('TYPEPHP_PHP_SCRIPT_ENTRY', true);
main($argc, $argv); main($argc, $argv);

@ -52,19 +52,24 @@ if (file_exists($versionFile)) {
} }
$versionId++; $versionId++;
file_put_contents($versionFile, (string)$versionId);
echo "当前版本: {$versionId}\n\n"; echo "当前版本: {$versionId}\n\n";
// ==================== 2. 检测系统架构 ==================== // ==================== 2. 检测系统架构 ====================
echo "[2/7] 检测系统架构...\n"; echo "[2/7] 检测系统架构...\n";
$arch = match (getenv('PROCESSOR_ARCHITECTURE')) { $processorArchitecture = getenv('PROCESSOR_ARCHITEW6432') ?: getenv('PROCESSOR_ARCHITECTURE');
$arch = match (strtoupper((string)$processorArchitecture)) {
'AMD64' => 'x86_64', 'AMD64' => 'x86_64',
'ARM64' => 'arm64', 'ARM64' => 'arm64',
'x86' => 'i386', default => null,
default => getenv('PROCESSOR_ARCHITECTURE') ?: 'unknown',
}; };
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'; $osType = 'windows';
$outputFile = "tpc_v{$versionId}_{$osType}_{$arch}.zip"; $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"; 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 压缩(可选) ==================== // ==================== 4. UPX 压缩(可选) ====================
echo "[4/7] UPX 压缩优化...\n"; echo "[4/7] UPX 压缩优化...\n";
$useUpx = false; $useUpx = false;
$backupExe = "{$compilerExe}.backup";
// 检查 UPX 是否可用 // 检查 UPX 是否可用
exec('where upx 2>nul', $upxOutput, $upxReturn); exec('where upx 2>nul', $upxOutput, $upxReturn);
@ -107,20 +234,16 @@ if ($upxReturn === 0) {
$upxVersion = $versionOutput[0] ?? 'unknown'; $upxVersion = $versionOutput[0] ?? 'unknown';
echo "检测到 UPX: {$upxVersion}\n"; echo "检测到 UPX: {$upxVersion}\n";
// 备份原始文件 // 只压缩暂存目录中的副本,不修改工作区内的原始 tpc.exe。
copy($compilerExe, $backupExe); echo "使用 UPX 压缩 {$packagedCompilerExe} ...\n";
echo "备份原始文件: {$backupExe}\n"; $originalSize = filesize($packagedCompilerExe);
exec('upx --best ' . escapeshellarg($packagedCompilerExe) . ' 2>&1', $upxOutput, $upxResult);
// 使用 UPX 压缩
echo "使用 UPX 压缩 {$compilerExe} ...\n";
exec("upx --best \"{$compilerExe}\" 2>&1", $upxOutput, $upxResult);
if ($upxResult === 0) { if ($upxResult === 0) {
echo "✓ UPX 压缩完成\n"; echo "✓ UPX 压缩完成\n";
// 显示压缩效果 // 显示压缩效果
$originalSize = filesize($backupExe); $compressedSize = filesize($packagedCompilerExe);
$compressedSize = filesize($compilerExe);
$originalSizeMB = round($originalSize / 1024 / 1024, 2); $originalSizeMB = round($originalSize / 1024 / 1024, 2);
$compressedSizeMB = round($compressedSize / 1024 / 1024, 2); $compressedSizeMB = round($compressedSize / 1024 / 1024, 2);
$compressionRatio = round((1 - $compressedSize / $originalSize) * 100, 2); $compressionRatio = round((1 - $compressedSize / $originalSize) * 100, 2);
@ -131,8 +254,8 @@ if ($upxReturn === 0) {
$useUpx = true; $useUpx = true;
} else { } else {
echo "✗ UPX 压缩失败,恢复原始文件\n"; echo "✗ UPX 压缩失败,重新复制未压缩的 tpc.exe\n";
rename($backupExe, $compilerExe); mustCopy($compilerExe, $packagedCompilerExe);
$useUpx = false; $useUpx = false;
} }
} else { } else {
@ -145,21 +268,7 @@ echo "\n";
// ==================== 5. 准备 PHP 目录结构 ==================== // ==================== 5. 准备 PHP 目录结构 ====================
echo "[5/7] 准备 PHP 目录结构...\n"; 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"; echo "创建顶层目录: {$topLevelDir}\n";
// 复制编译器可执行文件到顶层目录
copy($compilerExe, "{$topLevelDir}/{$compilerExe}");
echo "复制 {$compilerExe} -> {$topLevelDir}/\n"; echo "复制 {$compilerExe} -> {$topLevelDir}/\n";
// 递归复制 PHP_HOME 下的所有文件到顶层目录 // 递归复制 PHP_HOME 下的所有文件到顶层目录
@ -168,8 +277,16 @@ if (!is_dir($phpDir)) {
echo "跳过 PHP 文件复制\n\n"; echo "跳过 PHP 文件复制\n\n";
} else { } else {
echo "复制 PHP 运行时文件...\n"; echo "复制 PHP 运行时文件...\n";
// 排除 dev/php8ts.lib(SDK 目录下已有)和根目录的 php8embed.lib(SDK/lib/ 目录下已有) // dev/php8ts.lib 与 SDK/lib/php8ts.lib 重复,可以排除。
copyDirectory($phpDir, $topLevelDir, ['dev/php8ts.lib', 'php8embed.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( $iterator = new RecursiveIteratorIterator(
@ -186,7 +303,7 @@ echo "\n";
echo "[6/7] 复制项目文件到 PHP 目录...\n"; echo "[6/7] 复制项目文件到 PHP 目录...\n";
// 在顶层目录下创建子目录 // 在顶层目录下创建子目录
mkdir("{$topLevelDir}/examples", 0755, true); mustCreateDirectory("{$topLevelDir}/examples");
// 复制项目文件 // 复制项目文件
$projectFiles = [ $projectFiles = [
@ -199,9 +316,9 @@ $projectFiles = [
foreach ($projectFiles as $src => $destDir) { foreach ($projectFiles as $src => $destDir) {
$destPath = "{$topLevelDir}/{$destDir}"; $destPath = "{$topLevelDir}/{$destDir}";
if (!is_dir($destPath)) { if (!is_dir($destPath)) {
mkdir($destPath, 0755, true); mustCreateDirectory($destPath);
} }
copy($src, "{$destPath}/" . basename($src)); mustCopy($src, "{$destPath}/" . basename($src));
echo " 复制: {$src} -> {$destPath}/\n"; echo " 复制: {$src} -> {$destPath}/\n";
} }
@ -232,8 +349,8 @@ echo "[6.3/7] 复制 vendor 目录...\n";
$vendorDir = 'vendor'; $vendorDir = 'vendor';
if (!is_dir($vendorDir)) { if (!is_dir($vendorDir)) {
echo "警告: vendor 目录不存在\n"; echo "错误: vendor 目录不存在,请先运行 composer install\n";
echo "请先运行 composer install\n\n"; exit(1);
} else { } else {
echo "复制 vendor 目录...\n"; echo "复制 vendor 目录...\n";
// 排除 vendor/swoole/phpx 目录(Windows 下不需要 composer 安装的 phpx) // 排除 vendor/swoole/phpx 目录(Windows 下不需要 composer 安装的 phpx)
@ -255,63 +372,30 @@ echo "[6.5/7] 复制 phpx 相关文件...\n";
// 检查 phpx 目录是否存在 // 检查 phpx 目录是否存在
if (!is_dir($phpxDir)) { if (!is_dir($phpxDir)) {
echo "警告: phpx 目录不存在 - {$phpxDir}\n"; echo "错误: phpx 目录不存在 - {$phpxDir}\n";
echo "跳过 phpx 文件复制\n\n"; exit(1);
} else { } else {
// 创建 phpx 子目录 // 创建 phpx 子目录
mkdir("{$topLevelDir}/phpx/include", 0755, true); mustCreateDirectory("{$topLevelDir}/phpx/include");
mkdir("{$topLevelDir}/phpx/lib", 0755, true); mustCreateDirectory("{$topLevelDir}/phpx/lib");
// 复制 phpx include 头文件 // 复制 phpx include 头文件
$phpxIncludeDir = "{$phpxDir}/include";
if (is_dir($phpxIncludeDir)) {
copyDirectory($phpxIncludeDir, "{$topLevelDir}/phpx/include"); copyDirectory($phpxIncludeDir, "{$topLevelDir}/phpx/include");
echo " 复制: phpx/include -> phpx/include/\n"; echo " 复制: phpx/include -> phpx/include/\n";
} else {
echo " 警告: phpx/include 目录不存在\n";
}
// 复制 phpx lib 库文件(只保留 phpx.lib) // PHPX 目录只保留自身导入库;gmp/mpfr/mpdecimal 位于 PHP SDK/lib。
$phpxLibDir = "{$phpxDir}/lib"; mustCopy($phpxLibFile, "{$topLevelDir}/phpx/lib/phpx.lib");
$phpxLibFile = "{$phpxLibDir}/phpx.lib";
if (file_exists($phpxLibFile)) {
copy($phpxLibFile, "{$topLevelDir}/phpx/lib/phpx.lib");
echo " 复制: phpx.lib -> phpx/lib/\n"; echo " 复制: phpx.lib -> phpx/lib/\n";
} else {
echo " 警告: phpx.lib 文件不存在\n";
}
// 复制 phpx/src/misc 目录(辅助工具和头文件) // 复制 phpx/src/misc 目录(辅助工具和头文件)
$phpxMiscDir = "{$phpxDir}/src/misc"; mustCreateDirectory("{$topLevelDir}/phpx/src/misc");
if (is_dir($phpxMiscDir)) {
mkdir("{$topLevelDir}/phpx/src/misc", 0755, true);
// 排除 .obj 文件(MSVC 目标文件)和 .d 文件(依赖文件) // 排除 .obj 文件(MSVC 目标文件)和 .d 文件(依赖文件)
copyDirectory($phpxMiscDir, "{$topLevelDir}/phpx/src/misc", [], null, ['obj', 'd']); copyDirectory($phpxMiscDir, "{$topLevelDir}/phpx/src/misc", [], null, ['obj', 'd']);
echo " 复制: phpx/src/misc -> phpx/src/misc/\n"; echo " 复制: phpx/src/misc -> phpx/src/misc/\n";
} else {
echo " 警告: phpx/src/misc 目录不存在\n";
}
// 复制 phpx.dll 到顶层目录 // 复制 phpx.dll 到顶层目录
$phpxDllCandidates = [ mustCopy($phpxDllFile, "{$topLevelDir}/phpx.dll");
"{$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"; echo " 复制: phpx.dll -> {$topLevelDir}/\n";
$dllCopied = true;
break;
}
}
if (!$dllCopied) {
echo " 警告: 未找到 phpx.dll 文件\n";
}
echo "phpx 文件复制完成\n\n"; echo "phpx 文件复制完成\n\n";
} }
@ -321,7 +405,9 @@ echo "[7/7] 创建压缩包...\n";
// 删除旧的压缩包 // 删除旧的压缩包
if (file_exists($outputFile)) { if (file_exists($outputFile)) {
unlink($outputFile); if (!unlink($outputFile)) {
throw new RuntimeException("无法删除旧的压缩包: {$outputFile}");
}
echo "已删除旧的压缩包\n"; echo "已删除旧的压缩包\n";
} }
@ -330,16 +416,6 @@ if (!class_exists('ZipArchive')) {
echo "错误: ZipArchive 扩展未启用\n"; echo "错误: ZipArchive 扩展未启用\n";
echo "请在 php.ini 中启用 extension=zip\n"; echo "请在 php.ini 中启用 extension=zip\n";
// 恢复原始文件
if ($useUpx && file_exists($backupExe)) {
rename($backupExe, $compilerExe);
}
// 清理临时目录
if (is_dir($tempPhpDir)) {
removeDirectory($tempPhpDir);
}
exit(1); exit(1);
} }
@ -347,16 +423,6 @@ $zip = new ZipArchive();
if ($zip->open($outputFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { if ($zip->open($outputFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
echo "错误: 无法创建压缩包\n"; echo "错误: 无法创建压缩包\n";
// 恢复原始文件
if ($useUpx && file_exists($backupExe)) {
rename($backupExe, $compilerExe);
}
// 清理临时目录
if (is_dir($tempPhpDir)) {
removeDirectory($tempPhpDir);
}
exit(1); exit(1);
} }
@ -375,11 +441,41 @@ foreach ($files as $file) {
$relativePath = substr($filePathNormalized, strlen($topLevelDirNormalized) + 1); $relativePath = substr($filePathNormalized, strlen($topLevelDirNormalized) + 1);
// 在相对路径前加上顶层目录名 // 在相对路径前加上顶层目录名
$zipPath = "{$topLevelDir}/{$relativePath}"; $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"; echo "✓ 压缩包创建成功\n\n";
// ==================== 8. 清理和恢复 ==================== // ==================== 8. 清理和恢复 ====================
@ -391,14 +487,19 @@ $packageSizeMB = round($packageSize / 1024 / 1024, 2);
echo "临时目录: {$topLevelDir}\n"; echo "临时目录: {$topLevelDir}\n";
removeDirectory($topLevelDir); removeDirectory($topLevelDir);
$cleanupStage = false;
echo "临时目录已清理\n"; echo "临时目录已清理\n";
// 如果使用了 UPX,恢复原始文件 try {
if ($useUpx && file_exists($backupExe)) { mustWriteFile($versionFile, (string)$versionId);
echo "恢复原始二进制文件...\n"; } catch (Throwable $error) {
rename($backupExe, $compilerExe); if (is_file($outputFile) && !unlink($outputFile)) {
@unlink($backupExe); throw new RuntimeException(
echo "原始文件已恢复\n"; "无法提交版本号,且无法删除未提交的压缩包 {$outputFile}: {$error->getMessage()}",
previous: $error,
);
}
throw $error;
} }
echo "\n"; echo "\n";
@ -412,6 +513,7 @@ echo "输出文件: {$outputFile}\n";
echo "文件大小: {$packageSizeMB} MB\n\n"; echo "文件大小: {$packageSizeMB} MB\n\n";
echo "包含内容:\n"; echo "包含内容:\n";
echo " - {$compilerExe} (编译器可执行文件)\n"; echo " - {$compilerExe} (编译器可执行文件)\n";
echo " - WINDOWS-SETUP.txt (Windows 环境变量与 PHPX_HOME 配置说明)\n";
echo " - phpx.dll (PHPX 运行时库)\n"; echo " - phpx.dll (PHPX 运行时库)\n";
echo " - phpx/include/ (PHPX 头文件)\n"; echo " - phpx/include/ (PHPX 头文件)\n";
echo " - phpx/lib/ (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 " - examples/tetris-win32/ (俄罗斯方块游戏实例)\n\n";
echo "使用说明:\n"; echo "使用说明:\n";
echo " 1. 解压到任意目录\n"; echo " 1. 解压到任意目录\n";
echo " 2. 确保 PHP 8.4+ 已安装并添加到 PATH\n"; echo " 2. set PHP_HOME=%CD%\n";
echo " 3. 运行: tpc <your_script.php>\n\n"; echo " 3. set PHPX_HOME=%CD%\\phpx\n";
echo " 4. set PATH=%CD%;%PATH%\n";
echo " 5. 运行: tpc <your_script.php>\n\n";
/** /**
* 递归复制目录 * 递归复制目录
@ -439,7 +543,7 @@ echo " 3. 运行: tpc <your_script.php>\n\n";
function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?string $baseSrc = null, array $excludeExtensions = []): void function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?string $baseSrc = null, array $excludeExtensions = []): void
{ {
if (!is_dir($src)) { if (!is_dir($src)) {
return; throw new RuntimeException("源目录不存在: {$src}");
} }
if ($baseSrc === null) { if ($baseSrc === null) {
@ -447,10 +551,14 @@ function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?stri
} }
if (!is_dir($dest)) { 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) { foreach ($files as $file) {
$srcPath = "{$src}/{$file}"; $srcPath = "{$src}/{$file}";
$destPath = "{$dest}/{$file}"; $destPath = "{$dest}/{$file}";
@ -469,7 +577,8 @@ function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?stri
// 检查当前路径是否在排除列表中 // 检查当前路径是否在排除列表中
$shouldExclude = false; $shouldExclude = false;
foreach ($excludeDirs as $excludeDir) { 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; $shouldExclude = true;
break; break;
} }
@ -482,8 +591,49 @@ function copyDirectory(string $src, string $dest, array $excludeDirs = [], ?stri
if (is_dir($srcPath)) { if (is_dir($srcPath)) {
copyDirectory($srcPath, $destPath, $excludeDirs, $baseSrc, $excludeExtensions); copyDirectory($srcPath, $destPath, $excludeDirs, $baseSrc, $excludeExtensions);
} else { } 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) { foreach ($iterator as $file) {
if ($file->isDir()) { if ($file->isDir()) {
@rmdir($file->getRealPath()); if (!rmdir($file->getRealPath())) {
throw new RuntimeException("无法删除目录: {$file->getRealPath()}");
}
} else { } else {
// 移除只读属性(Windows) // 移除只读属性(Windows)
if (PHP_OS_FAMILY === 'Windows' && !is_writable($file->getRealPath())) { 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}");
}
} }

@ -78,15 +78,24 @@ class PlatformTest extends TestCase
'The PHPX import library was not found at: ' . $root . '\lib\phpx.lib', 'The PHPX import library was not found at: ' . $root . '\lib\phpx.lib',
$errors, $errors,
); );
$this->assertContains( $runtimeError = 'The PHPX runtime library was not found at: ' . $root . '\build\phpx.dll';
'The PHPX runtime library `phpx.dll` was not found under: ' . $root, $this->assertContains($runtimeError, $errors);
$errors,
);
foreach ($diagnostics as $diagnostic) { foreach ($diagnostics as $diagnostic) {
if (isset($diagnostic['error'])) { if (isset($diagnostic['error'])) {
$this->assertStringContainsString('Build PHPX first', $diagnostic['info']); $this->assertStringContainsString('Build PHPX first', $diagnostic['info']);
} }
} }
$nativeExecutableDiagnostics = (new Windows())->getBuildLibraryWarnings(
$root,
$root,
'bin',
checkPhpxRuntime: false,
);
$this->assertNotContains(
$runtimeError,
array_column($nativeExecutableDiagnostics, 'error'),
);
} finally { } finally {
rmdir($root); rmdir($root);
} }

@ -102,7 +102,12 @@ trait SourcePipelineTrait
// Windows 的所有构建模式都依赖 PHPX 导入库和运行库。 // Windows 的所有构建模式都依赖 PHPX 导入库和运行库。
// 其他平台仅在嵌入式构建模式下执行现有检查。 // 其他平台仅在嵌入式构建模式下执行现有检查。
if ($this->isBuildModeEmbed() || $this->getPlatform() instanceof Windows) { 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'])) { if (!empty($message['error'])) {
$detail = $message['error']; $detail = $message['error'];
if (!empty($message['info'])) { if (!empty($message['info'])) {

@ -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') { if ($buildMode !== 'bin' && $buildMode !== 'lib') {
return []; return [];

@ -175,7 +175,12 @@ class Windows extends PlatformBase
return '/NODEFAULTLIB:LIBCMT'; 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 = []; $warnings = [];
$phpDirs = [ $phpDirs = [
@ -205,21 +210,10 @@ class Windows extends PlatformBase
]; ];
} }
$phpxDllPaths = [ $phpxDllPath = $phpxDir . '\build\phpx.dll';
$phpxDir . '\build\phpx.dll', if ($checkPhpxRuntime && !is_file($phpxDllPath)) {
$phpxDir . '\lib\phpx.dll',
$phpxDir . '\phpx.dll',
];
$hasPhpxDll = false;
foreach ($phpxDllPaths as $phpxDllPath) {
if (is_file($phpxDllPath)) {
$hasPhpxDll = true;
break;
}
}
if (!$hasPhpxDll) {
$warnings[] = [ $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)', 'info' => 'Build PHPX first (for example, run `nmake phpx` in ' . $phpxDir . '\build)',
]; ];
} }

@ -1 +1 @@
1092 1094
Loading…
Cancel
Save