From 661ab07cd9ecb5c10065361b41bf5219e62937d9 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 8 May 2026 21:00:45 +0800 Subject: [PATCH] =?UTF-8?q?refactor(backend):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=99=A8=E5=90=8E=E7=AB=AF=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=BB=A5=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E5=99=A8=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 Clang 和 Gcc 类构造函数以接受自定义编译器命令参数 - 引入 CompilerFactory 来统一管理编译器创建和检测逻辑 - 更新 BackendTest 添加对自定义编译器路径的支持测试 - 实现 Windows 平台下 lld-link 检测和回退机制 - 重构 CompilerBase 类中的平台和编译器初始化逻辑 - 移除旧的平台特定检测方法并整合到 PlatformFactory - 更新编译和链接命令构建以使用新的后端抽象层 - 优化 include paths、library paths 和 libraries 的处理流程 - 改进目标文件扩展名和链接选项的跨平台处理 --- phpunit/src/Backend/BackendTest.php | 47 +++ phpunit/src/Platform/PlatformTest.php | 42 +- src/Php/Backend/Clang.php | 141 +++---- src/Php/Backend/CompilerFactory.php | 89 ++++- src/Php/Backend/Gcc.php | 82 ++-- src/Php/Backend/Msvc.php | 87 ++--- src/Php/CompilerBase.php | 526 ++++++++++---------------- src/Php/Platform/Linux.php | 33 ++ src/Php/Platform/Macos.php | 28 ++ src/Php/Platform/PlatformBase.php | 99 +++++ src/Php/Platform/Windows.php | 79 ++++ src/Php/Preprocessor.php | 7 +- src/Php/Translator.php | 116 +----- 13 files changed, 768 insertions(+), 608 deletions(-) diff --git a/phpunit/src/Backend/BackendTest.php b/phpunit/src/Backend/BackendTest.php index 5d64a9d4..339b39b4 100644 --- a/phpunit/src/Backend/BackendTest.php +++ b/phpunit/src/Backend/BackendTest.php @@ -9,6 +9,7 @@ use PhpAot\Php\Platform\Macos; use PhpAot\Php\Backend\Msvc; use PhpAot\Php\Backend\Gcc; use PhpAot\Php\Backend\Clang; +use PhpAot\Php\Backend\CompilerFactory; class BackendTest extends TestCase { @@ -231,6 +232,43 @@ class BackendTest extends TestCase $this->assertStringContainsString('-DZEND_WIN32', $cmd); } + public function testGccBuildCompileCommandUsesCustomCompilerAndIncludes(): void + { + $platform = new Linux(); + $compiler = new Gcc($platform, '/opt/toolchain/bin/g++'); + + $cmd = $compiler->buildCompileCommand('test.cpp', 'test.o', [ + 'include_paths' => ['/usr/include/php'], + 'cpp_std' => 'c++20', + 'cxxflags' => '-fno-rtti', + ]); + + $this->assertStringStartsWith('/opt/toolchain/bin/g++', $cmd); + $this->assertStringContainsString('-I' . escapeshellarg('/usr/include/php'), $cmd); + $this->assertStringContainsString('-std=c++20', $cmd); + $this->assertStringContainsString('-fno-rtti', $cmd); + } + + public function testGccBuildLinkCommandIncludesPlatformPathsOptionsAndLibraries(): void + { + $platform = new Linux(); + $compiler = new Gcc($platform, 'g++'); + + $cmd = $compiler->buildLinkCommand(['a.o', 'b.o'], 'app', [ + 'library_paths' => ['/usr/lib'], + 'libraries' => ['/usr/lib/libphpx.so', 'php'], + 'ldflags' => '-Wl,--as-needed', + 'build_mode' => 'ext', + ]); + + $this->assertStringStartsWith('g++', $cmd); + $this->assertStringContainsString('-L' . escapeshellarg('/usr/lib'), $cmd); + $this->assertStringContainsString('-Wl,--as-needed', $cmd); + $this->assertStringContainsString('-shared', $cmd); + $this->assertStringContainsString('-lphpx', $cmd); + $this->assertStringContainsString('-lphp', $cmd); + } + /** * 测试 GCC 完整编译选项 */ @@ -314,6 +352,15 @@ class BackendTest extends TestCase $this->assertEquals('link', $compiler->getLinkerCommand()); } + public function testCompilerFactoryKeepsConfiguredCompilerCommand(): void + { + $compiler = CompilerFactory::createByName('/opt/llvm/bin/clang++', new Linux()); + + $this->assertInstanceOf(Clang::class, $compiler); + $this->assertSame('/opt/llvm/bin/clang++', $compiler->getCompilerCommand()); + $this->assertSame('/opt/llvm/bin/clang++', $compiler->getLinkerCommand()); + } + /** * 测试 Clang 完整编译选项(Unix) */ diff --git a/phpunit/src/Platform/PlatformTest.php b/phpunit/src/Platform/PlatformTest.php index 45638ca4..35e0ec27 100644 --- a/phpunit/src/Platform/PlatformTest.php +++ b/phpunit/src/Platform/PlatformTest.php @@ -87,6 +87,29 @@ class PlatformTest extends TestCase $this->assertEquals('src\Php\Backend', $path); } + public function testTargetExtensions(): void + { + $windows = new Windows(); + $linux = new Linux(); + $macos = new Macos(); + + $this->assertSame('.exe', $windows->getTargetExtension('bin')); + $this->assertSame('.dll', $windows->getTargetExtension('ext')); + $this->assertSame('', $linux->getTargetExtension('bin')); + $this->assertSame('.so', $linux->getTargetExtension('ext')); + $this->assertSame('', $macos->getTargetExtension('bin')); + $this->assertSame('.so', $macos->getTargetExtension('ext')); + } + + public function testPlatformPathPrefixRemoval(): void + { + $windows = new Windows(); + $linux = new Linux(); + + $this->assertSame('src\app.php', $windows->removeCommonPrefix('C:\project', 'C:/project/src/app.php')); + $this->assertSame('src/app.php', $linux->removeCommonPrefix('/project', '/project/src/app.php')); + } + /** * 测试 Windows 子系统选项 */ @@ -155,8 +178,8 @@ class PlatformTest extends TestCase $paths = ['/usr/include/php', '/usr/local/include']; $flags = $platform->getIncludeFlags($paths); - $this->assertStringContainsString('-I"/usr/include/php"', $flags); - $this->assertStringContainsString('-I"/usr/local/include"', $flags); + $this->assertStringContainsString('-I' . escapeshellarg('/usr/include/php'), $flags); + $this->assertStringContainsString('-I' . escapeshellarg('/usr/local/include'), $flags); } /** @@ -169,8 +192,8 @@ class PlatformTest extends TestCase $paths = ['/usr/lib', '/usr/local/lib']; $flags = $platform->getLibraryPathFlags($paths); - $this->assertStringContainsString('-L"/usr/lib"', $flags); - $this->assertStringContainsString('-L"/usr/local/lib"', $flags); + $this->assertStringContainsString('-L' . escapeshellarg('/usr/lib'), $flags); + $this->assertStringContainsString('-L' . escapeshellarg('/usr/local/lib'), $flags); } /** @@ -197,8 +220,8 @@ class PlatformTest extends TestCase $paths = ['/usr/lib', '/usr/local/lib']; $options = $platform->getRpathOptions($paths); - $this->assertStringContainsString('-Wl,-rpath,"/usr/lib"', $options); - $this->assertStringContainsString('-Wl,-rpath,"/usr/local/lib"', $options); + $this->assertStringContainsString('-Wl,-rpath,' . escapeshellarg('/usr/lib'), $options); + $this->assertStringContainsString('-Wl,-rpath,' . escapeshellarg('/usr/local/lib'), $options); } /** @@ -211,6 +234,13 @@ class PlatformTest extends TestCase $this->assertEquals('-fPIC', $platform->getPicFlag()); } + public function testIntegerLiteralSuffixes(): void + { + $this->assertSame('L', (new Linux())->getIntegerLiteralSuffix()); + $this->assertSame('LL', (new Macos())->getIntegerLiteralSuffix()); + $this->assertSame('LL', (new Windows())->getIntegerLiteralSuffix()); + } + /** * 测试 Linux 共享库链接标志 */ diff --git a/src/Php/Backend/Clang.php b/src/Php/Backend/Clang.php index 7a72ae4f..1d5bb444 100644 --- a/src/Php/Backend/Clang.php +++ b/src/Php/Backend/Clang.php @@ -9,6 +9,16 @@ use PhpAot\Php\Platform\PlatformBase; */ class Clang extends CompilerBackend { + private string $compilerCommand; + private ?string $linkerCommand; + + public function __construct(PlatformBase $platform, string $compilerCommand = 'clang++', ?string $linkerCommand = null) + { + parent::__construct($platform); + $this->compilerCommand = $compilerCommand; + $this->linkerCommand = $linkerCommand; + } + public function getName(): string { return 'Clang'; @@ -16,16 +26,49 @@ class Clang extends CompilerBackend public function getCompilerCommand(): string { - return 'clang++'; + return $this->compilerCommand; } public function getLinkerCommand(): string { + if ($this->linkerCommand !== null) { + return $this->linkerCommand; + } + // Windows 下使用 MSVC 链接器,其他平台使用 clang++ if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { return 'link'; } - return 'clang++'; + return $this->compilerCommand; + } + + /** + * Windows 下优先使用 lld-link,找不到时回退到 link.exe + */ + public static function detectWindowsLinker(): string + { + $output = []; + $returnCode = 0; + exec('lld-link --version 2>&1', $output, $returnCode); + + if ($returnCode === 0) { + return 'lld-link'; + } + + $llvmHome = getenv('LLVM_HOME'); + if ($llvmHome && is_dir($llvmHome)) { + $lldLinkPath = rtrim($llvmHome, '\/') . '\x64\bin\lld-link.exe'; + if (file_exists($lldLinkPath)) { + exec('"' . $lldLinkPath . '" --version 2>&1', $output, $returnCode); + if ($returnCode === 0) { + $lldDir = dirname($lldLinkPath); + putenv("PATH={$lldDir};" . getenv('PATH')); + return 'lld-link'; + } + } + } + + return 'link'; } public function compileFile( @@ -118,34 +161,13 @@ class Clang extends CompilerBackend $cmd .= ' -c'; $cmd .= ' ' . escapeshellarg($sourceFile); $cmd .= ' -o ' . escapeshellarg($outputFile); - - // 优化级别 - $optimizeLevel = $options['optimize'] ?? 2; - - // 调试模式 - if (!empty($options['debug'])) { - $cmd .= ' -O0 -g'; - } else { - $cmd .= ' -O' . $optimizeLevel; - } - - // 警告级别 - $cmd .= ' -Wall'; - - // C++ 标准 - $cppStd = $options['cpp_std'] ?? 'c++17'; - $cmd .= ' -std=' . $cppStd; - - // Sanitizer 支持 - if (!empty($options['sanitize'])) { - $cmd .= ' -fsanitize=' . $options['sanitize']; - } - - // PIC(位置无关代码) - if (!empty($options['pic'])) { - $cmd .= ' -fPIC'; + + if (!empty($options['include_paths'])) { + $cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); } - + + $cmd .= $this->buildCompileOptions($options); + return $cmd; } @@ -167,6 +189,10 @@ class Clang extends CompilerBackend $cmd .= ' -c'; $cmd .= ' ' . escapeshellarg($sourceFile); $cmd .= ' -o ' . escapeshellarg($outputFile); + + if (!empty($options['include_paths'])) { + $cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); + } // 优化级别(C 文件通常使用较低的优化) $optimizeLevel = $options['optimize'] ?? 0; @@ -195,41 +221,22 @@ class Clang extends CompilerBackend if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { $cmd .= ' /OUT:' . escapeshellarg($outputFile); - // 调试信息 - if (!empty($options['debug'])) { - $cmd .= ' /DEBUG'; - } - - // Windows 子系统 - if (!empty($options['no_console'])) { - $cmd .= ' ' . $this->platform->getSubsystemOptions(true); - } - - // CRT 配置 - $cmd .= ' ' . $this->platform->getCrtConfig(); } else { - // Unix/Linux/macOS 使用 GCC 风格语法 $cmd .= ' -o ' . escapeshellarg($outputFile); - - // 共享库 - if (!empty($options['shared'])) { - $cmd .= ' ' . $this->platform->getSharedLinkFlag(); - - // macOS 需要 install_name - if ($this->platform instanceof \PhpAot\Php\Platform\Macos && !empty($options['install_name'])) { - $cmd .= ' ' . $this->platform->getCurrentInstallNameOption($options['install_name']); - } - } - - // RPATH(运行时库搜索路径) - if (!empty($options['rpath'])) { - $cmd .= ' ' . $this->platform->getRpathOptions($options['rpath']); - } - - // Sanitizer 链接选项 - if (!empty($options['sanitize'])) { - $cmd .= ' -fsanitize=' . $options['sanitize']; - } + } + + if (!empty($options['library_paths'])) { + $cmd .= ' ' . $this->formatLibraryPaths($options['library_paths']); + } + + if (!empty($options['ldflags'])) { + $cmd .= ' ' . $options['ldflags']; + } + + $cmd .= $this->buildLinkOptions($options); + + if (!empty($options['libraries'])) { + $cmd .= ' ' . $this->formatLibraries($options['libraries']); } return $cmd; @@ -358,7 +365,7 @@ class Clang extends CompilerBackend } // PIC (Position Independent Code) - if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') { + if ((!empty($config['build_mode']) && $config['build_mode'] === 'ext') || !empty($config['pic'])) { if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { // Windows Clang 不需要特殊处理 } else { @@ -408,8 +415,12 @@ class Clang extends CompilerBackend } else { // Unix/Linux/macOS // 扩展模块选项 - if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') { - $cmd .= ' -shared'; + if ((!empty($config['build_mode']) && $config['build_mode'] === 'ext') || !empty($config['shared'])) { + $cmd .= ' ' . $this->platform->getSharedLinkFlag(); + + if ($this->platform instanceof \PhpAot\Php\Platform\Macos && !empty($config['install_name'])) { + $cmd .= ' ' . $this->platform->getCurrentInstallNameOption($config['install_name']); + } } // RPATH diff --git a/src/Php/Backend/CompilerFactory.php b/src/Php/Backend/CompilerFactory.php index 8b520bf8..f5428ddf 100644 --- a/src/Php/Backend/CompilerFactory.php +++ b/src/Php/Backend/CompilerFactory.php @@ -20,49 +20,106 @@ class CompilerFactory { if ($platform instanceof Windows) { // Windows 默认使用 MSVC - return new Msvc($platform); + return new Msvc($platform, $platform->getDefaultCompiler()); } elseif ($platform instanceof Linux) { // Linux 默认使用 GCC - return new Gcc($platform); + return new Gcc($platform, $platform->getDefaultCompiler()); } elseif ($platform instanceof Macos) { // macOS 默认使用 Clang - return new Clang($platform); + return new Clang($platform, $platform->getDefaultCompiler()); } else { throw new \RuntimeException("Unsupported platform: " . $platform->getName()); } } + /** + * 根据配置、环境变量和平台默认值解析编译器命令 + */ + public static function detectCompilerName(PlatformBase $platform, string $configuredCompiler = ''): string + { + if ($configuredCompiler !== '') { + return $configuredCompiler; + } + + $compilerEnv = getenv('PHPX_CC'); + if ($compilerEnv) { + return $compilerEnv; + } + + if (!$platform instanceof Windows) { + $cxxEnv = getenv('CXX'); + if ($cxxEnv) { + return $cxxEnv; + } + } + + return $platform->getDefaultCompiler(); + } + /** * 创建指定类型的编译器后端 */ public static function createByName(string $compilerName, PlatformBase $platform): CompilerBackend { - return match (strtolower($compilerName)) { - 'msvc', 'cl' => new Msvc($platform), - 'gcc', 'g++' => new Gcc($platform), - 'clang', 'clang++' => new Clang($platform), - default => throw new \RuntimeException("Unsupported compiler: {$compilerName}"), - }; + $normalized = self::normalizeCompilerName($compilerName); + $lowerCommand = strtolower($compilerName); + + if (str_contains($normalized, 'clang') || str_contains($lowerCommand, 'clang')) { + $linker = $platform instanceof Windows ? Clang::detectWindowsLinker() : null; + return new Clang($platform, $compilerName, $linker); + } + + if ( + $normalized === 'gcc' || + $normalized === 'g++' || + $normalized === 'c++' || + str_ends_with($normalized, '-gcc') || + str_ends_with($normalized, '-g++') || + str_contains($lowerCommand, 'g++') || + str_contains($lowerCommand, 'c++') || + str_contains($lowerCommand, 'gcc') + ) { + return new Gcc($platform, $compilerName); + } + + if ($normalized === 'msvc' || $normalized === 'cl') { + if (!$platform instanceof Windows) { + throw new \RuntimeException("MSVC compiler is only supported on Windows"); + } + return new Msvc($platform, $compilerName); + } + + throw new \RuntimeException("Unsupported compiler: {$compilerName}"); } /** * 自动检测并创建编译器和平台 */ - public static function autoDetect(string $compilerName = ''): array + public static function autoDetect(string $compilerName = '', ?PlatformBase $platform = null): array { // 创建平台 - $platform = \PhpAot\Php\Platform\PlatformFactory::create(); + $platform ??= \PhpAot\Php\Platform\PlatformFactory::create(); // 创建编译器 - if (!empty($compilerName)) { - $compiler = self::createByName($compilerName, $platform); - } else { - $compiler = self::create($platform); - } + $compilerName = self::detectCompilerName($platform, $compilerName); + $compiler = self::createByName($compilerName, $platform); return [ 'platform' => $platform, 'compiler' => $compiler, ]; } + + private static function normalizeCompilerName(string $compilerName): string + { + $firstToken = strtok(trim($compilerName), ' '); + if ($firstToken === false || $firstToken === '') { + return ''; + } + + $name = basename(str_replace('\\', '/', $firstToken)); + $name = strtolower($name); + + return preg_replace('/\.exe$/', '', $name); + } } diff --git a/src/Php/Backend/Gcc.php b/src/Php/Backend/Gcc.php index 19d7fc7c..1f43a816 100644 --- a/src/Php/Backend/Gcc.php +++ b/src/Php/Backend/Gcc.php @@ -9,6 +9,16 @@ use PhpAot\Php\Platform\PlatformBase; */ class Gcc extends CompilerBackend { + private string $compilerCommand; + private string $linkerCommand; + + public function __construct(PlatformBase $platform, string $compilerCommand = 'g++', ?string $linkerCommand = null) + { + parent::__construct($platform); + $this->compilerCommand = $compilerCommand; + $this->linkerCommand = $linkerCommand ?? $compilerCommand; + } + public function getName(): string { return 'GCC'; @@ -16,12 +26,12 @@ class Gcc extends CompilerBackend public function getCompilerCommand(): string { - return 'g++'; + return $this->compilerCommand; } public function getLinkerCommand(): string { - return 'g++'; + return $this->linkerCommand; } public function compileFile( @@ -92,28 +102,13 @@ class Gcc extends CompilerBackend $cmd .= ' -c'; $cmd .= ' ' . escapeshellarg($sourceFile); $cmd .= ' -o ' . escapeshellarg($outputFile); - - // 优化级别 - $optimizeLevel = $options['optimize'] ?? 2; - $cmd .= ' -O' . $optimizeLevel; - - // 调试信息 - if (!empty($options['debug'])) { - $cmd .= ' -g'; - } - - // 警告级别 - $cmd .= ' -Wall'; - - // C++ 标准 - $cppStd = $options['cpp_std'] ?? 'c++17'; - $cmd .= ' -std=' . $cppStd; - - // PIC(位置无关代码) - if (!empty($options['pic'])) { - $cmd .= ' -fPIC'; + + if (!empty($options['include_paths'])) { + $cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); } - + + $cmd .= $this->buildCompileOptions($options); + return $cmd; } @@ -126,6 +121,10 @@ class Gcc extends CompilerBackend $cmd .= ' -c'; $cmd .= ' ' . escapeshellarg($sourceFile); $cmd .= ' -o ' . escapeshellarg($outputFile); + + if (!empty($options['include_paths'])) { + $cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); + } // 优化级别(C 文件通常使用较低的优化) $optimizeLevel = $options['optimize'] ?? 0; @@ -149,22 +148,21 @@ class Gcc extends CompilerBackend $cmd = $this->getLinkerCommand(); $cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); $cmd .= ' -o ' . escapeshellarg($outputFile); - - // 共享库 - if (!empty($options['shared'])) { - $cmd .= ' ' . $this->platform->getSharedLinkFlag(); - - // macOS 需要 install_name - if ($this->platform instanceof \PhpAot\Php\Platform\Macos && !empty($options['install_name'])) { - $cmd .= ' ' . $this->platform->getCurrentInstallNameOption($options['install_name']); - } + + if (!empty($options['library_paths'])) { + $cmd .= ' ' . $this->formatLibraryPaths($options['library_paths']); } - - // RPATH(运行时库搜索路径) - if (!empty($options['rpath'])) { - $cmd .= ' ' . $this->platform->getRpathOptions($options['rpath']); + + if (!empty($options['ldflags'])) { + $cmd .= ' ' . $options['ldflags']; } - + + $cmd .= $this->buildLinkOptions($options); + + if (!empty($options['libraries'])) { + $cmd .= ' ' . $this->formatLibraries($options['libraries']); + } + return $cmd; } @@ -262,7 +260,7 @@ class Gcc extends CompilerBackend } // PIC (Position Independent Code) - if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') { + if ((!empty($config['build_mode']) && $config['build_mode'] === 'ext') || !empty($config['pic'])) { $cmd .= ' -fPIC'; } @@ -287,8 +285,12 @@ class Gcc extends CompilerBackend $cmd = ''; // 扩展模块选项 - if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') { - $cmd .= ' -shared'; + if ((!empty($config['build_mode']) && $config['build_mode'] === 'ext') || !empty($config['shared'])) { + $cmd .= ' ' . $this->platform->getSharedLinkFlag(); + + if ($this->platform instanceof \PhpAot\Php\Platform\Macos && !empty($config['install_name'])) { + $cmd .= ' ' . $this->platform->getCurrentInstallNameOption($config['install_name']); + } } // RPATH diff --git a/src/Php/Backend/Msvc.php b/src/Php/Backend/Msvc.php index 94b84dee..4a53a6b4 100644 --- a/src/Php/Backend/Msvc.php +++ b/src/Php/Backend/Msvc.php @@ -9,9 +9,14 @@ use PhpAot\Php\Platform\Windows; */ class Msvc extends CompilerBackend { - public function __construct(Windows $platform) + private string $compilerCommand; + private string $linkerCommand; + + public function __construct(Windows $platform, string $compilerCommand = 'cl', string $linkerCommand = 'link') { parent::__construct($platform); + $this->compilerCommand = $compilerCommand; + $this->linkerCommand = $linkerCommand; } public function getName(): string @@ -21,12 +26,12 @@ class Msvc extends CompilerBackend public function getCompilerCommand(): string { - return 'cl'; + return $this->compilerCommand; } public function getLinkerCommand(): string { - return 'link'; + return $this->linkerCommand; } public function compileFile( @@ -97,42 +102,14 @@ class Msvc extends CompilerBackend $cmd .= ' /c'; $cmd .= ' ' . escapeshellarg($sourceFile); $cmd .= ' /Fo' . escapeshellarg($outputFile); - - // 平台宏定义 - $cmd .= ' /DZEND_WIN32 /DPHP_WIN32 /DZEND_DEBUG=0'; - - // ZTS 支持 - if ($this->platform instanceof Windows && $this->platform->isZts()) { - $cmd .= ' /DZTS'; - } - - // 优化级别 - $optimizeLevel = $options['optimize'] ?? 2; - $cmd .= ' /O' . ($optimizeLevel >= 2 ? '2' : ($optimizeLevel === 0 ? 'd' : '1')); - - // 警告级别 - $cmd .= ' /W3'; - - // 禁用常见警告 - if (!empty($options['suppressed_warnings'])) { - foreach ($options['suppressed_warnings'] as $code => $description) { - $cmd .= " /wd{$code}"; - } + + if (!empty($options['include_paths'])) { + $cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); } - - // C++ 标准 - $cppStd = $options['cpp_std'] ?? 'c++17'; - $cmd .= ' /std:' . $cppStd; - - // 异常处理 - $cmd .= ' /EHsc'; - - // CRT - $cmd .= ' /MD'; - - // nologo - $cmd .= ' /nologo'; - + + $options['is_zts'] ??= $this->platform instanceof Windows && $this->platform->isZts(); + $cmd .= $this->buildCompileOptions($options); + return $cmd; } @@ -145,6 +122,10 @@ class Msvc extends CompilerBackend $cmd .= ' /c'; $cmd .= ' ' . escapeshellarg($sourceFile); $cmd .= ' /Fo' . escapeshellarg($outputFile); + + if (!empty($options['include_paths'])) { + $cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); + } // 平台宏定义 $cmd .= ' /DZEND_WIN32 /DPHP_WIN32 /DZEND_DEBUG=0'; @@ -180,23 +161,21 @@ class Msvc extends CompilerBackend $cmd = $this->getLinkerCommand(); $cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); $cmd .= ' /OUT:' . escapeshellarg($outputFile); - - // 调试信息 - if (!empty($options['debug'])) { - $cmd .= ' /DEBUG'; + + if (!empty($options['library_paths'])) { + $cmd .= ' ' . $this->formatLibraryPaths($options['library_paths']); } - - // Windows 子系统 - if (!empty($options['no_console'])) { - $cmd .= ' ' . $this->platform->getSubsystemOptions(true); + + if (!empty($options['ldflags'])) { + $cmd .= ' ' . $options['ldflags']; } - - // CRT 配置 - $cmd .= ' ' . $this->platform->getCrtConfig(); - - // nologo - $cmd .= ' /nologo'; - + + $cmd .= $this->buildLinkOptions($options); + + if (!empty($options['libraries'])) { + $cmd .= ' ' . $this->formatLibraries($options['libraries']); + } + return $cmd; } @@ -267,6 +246,7 @@ class Msvc extends CompilerBackend // 禁用常见警告(只使用键,即警告代码) if (!empty($options['suppressed_warnings'])) { foreach ($options['suppressed_warnings'] as $code => $description) { + $code = is_int($code) && $code < 100 ? $description : $code; $cmd .= " /wd{$code}"; } } @@ -354,6 +334,7 @@ class Msvc extends CompilerBackend // 禁用常见警告(只使用键,即警告代码) if (!empty($config['suppressed_warnings'])) { foreach ($config['suppressed_warnings'] as $code => $description) { + $code = is_int($code) && $code < 100 ? $description : $code; $cmd .= " /wd{$code}"; } } diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 7120baaa..a4829813 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -9,6 +9,8 @@ namespace PhpAot\Php; use League\CLImate\CLImate; +use PhpAot\Php\Backend\CompilerBackend; +use PhpAot\Php\Backend\CompilerFactory; use PhpAot\Php\Context\FunctionContext; use PhpAot\Php\Entity\ClassDef; use PhpAot\Php\Entity\ConstantDef; @@ -26,6 +28,11 @@ use PhpAot\Php\Generator\PlaceHolderGenerator; use PhpAot\Php\Generator\PropertyPromotion; use PhpAot\Php\Generator\Utils; use PhpAot\Php\Parser\StdArrayParser; +use PhpAot\Php\Platform\Linux; +use PhpAot\Php\Platform\Macos; +use PhpAot\Php\Platform\PlatformBase; +use PhpAot\Php\Platform\PlatformFactory; +use PhpAot\Php\Platform\Windows; use PhpParser\Modifiers; use PhpParser\Node; use PhpParser\Node\Expr; @@ -164,7 +171,6 @@ class CompilerBase extends \PhpAot\Core\Translator protected string $cxxflags = ''; protected string $cxxStd = 'c++17'; protected string $ldflags = ''; - protected string $linker = 'link'; // Windows linker: link.exe or lld-link protected int $floatPrecision = 17; protected bool $debug = false; protected bool $formatCode = false; @@ -268,8 +274,8 @@ class CompilerBase extends \PhpAot\Core\Translator protected string $windowsPhpCoreLib = ''; // php8ts.lib 或 php8.lib 路径 // 新的平台和编译器抽象层(可选使用) - protected ?\PhpAot\Php\Platform\PlatformBase $platform = null; - protected ?\PhpAot\Php\Backend\CompilerBackend $compilerBackend = null; + protected ?PlatformBase $platform = null; + protected ?CompilerBackend $compilerBackend = null; /** * 在预处理阶段获取所有类的方法名称,检测子类和父类中存在的同名方法,解决动态绑定方法调用的问题 @@ -307,53 +313,30 @@ class CompilerBase extends \PhpAot\Core\Translator */ protected function detectPlatform(): void { - // 检测是否为 Windows 系统 - $this->isWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; - - if ($this->isWindows) { - // Windows 下检测使用哪个编译器 - // 优先级:环境变量 PHPX_CC > cl (MSVC, 默认) > clang - $compilerEnv = getenv('PHPX_CC'); - - if ($compilerEnv) { - // 用户通过环境变量指定编译器 - $this->cppCompiler = $compilerEnv; - $this->climate->info("Using compiler from PHPX_CC: {$this->cppCompiler}"); - } else { - // 默认使用 MSVC(更稳定,与链接器兼容) - $this->cppCompiler = 'cl'; - $this->climate->info('Using MSVC compiler (cl)'); + try { + $this->platform = PlatformFactory::create(); + $this->isWindows = $this->platform instanceof Windows; + $this->cppCompiler = CompilerFactory::detectCompilerName($this->platform); + + if ($this->platform instanceof Windows) { + $libInfo = $this->platform->detectPhpLibs($this->getPhpDir()); + $this->windowsPhpEmbedLib = $libInfo['embed']; + $this->windowsPhpCoreLib = $libInfo['core']; + $this->isPhpZts = $libInfo['is_zts']; + + $this->platform = new Windows( + phpLibs: [$this->windowsPhpCoreLib, $this->windowsPhpEmbedLib], + isZts: $this->isPhpZts, + phpSdkPath: $this->getPhpDir() . '\\SDK' + ); } - // Windows 平台:检测 PHP lib 文件并决定 ZTS/NTS 模式 - $this->detectWindowsPhpLibs(); - - // 初始化新的 Platform 和 Backend 抽象层 - $this->initializeNewArchitecture(); - } else { - // Unix/Linux/macOS 下检测编译器 - // 优先级:环境变量 PHPX_CC > CXX > 平台默认 - $compilerEnv = getenv('PHPX_CC') ?: getenv('CXX'); - - if ($compilerEnv) { - // 用户通过环境变量指定编译器 - $this->cppCompiler = $compilerEnv; - $this->climate->info("Using compiler from environment: {$this->cppCompiler}"); - } else { - // 根据平台选择默认编译器 - if ($this->isMacos()) { - // macOS 默认使用 Clang(系统自带) - $this->cppCompiler = 'clang++'; - $this->climate->info('Using Clang compiler (clang++)'); - } else { - // Linux 默认使用 GCC - $this->cppCompiler = 'g++'; - $this->climate->info('Using GCC compiler (g++)'); - } - } - - // 初始化新的 Platform 和 Backend 抽象层 - $this->initializeNewArchitecture(); + $this->compilerBackend = CompilerFactory::createByName($this->cppCompiler, $this->platform); + $this->climate->info( + "Initialized platform/backend: {$this->platform->getName()} + {$this->compilerBackend->getName()} ({$this->compilerBackend->getCompilerCommand()})" + ); + } catch (\Throwable $e) { + $this->error($e->getMessage()); } } @@ -376,28 +359,14 @@ class CompilerBase extends \PhpAot\Core\Translator protected function initializeNewArchitecture(): void { try { + $platform = $this->platform ?? PlatformFactory::create(); + $this->platform = $platform; + // 自动检测平台和编译器 - $result = \PhpAot\Php\Backend\CompilerFactory::autoDetect($this->cppCompiler); + $result = CompilerFactory::autoDetect($this->cppCompiler, $platform); $this->platform = $result['platform']; $this->compilerBackend = $result['compiler']; - // Windows 平台:需要传递 ZTS 状态给 Platform 对象 - if ($this->platform instanceof \PhpAot\Php\Platform\Windows && $this->isWindows) { - // 重新创建 Windows Platform,传递正确的 ZTS 状态 - $phpSdkPath = $this->getPhpDir() . '\\SDK'; - $this->platform = new \PhpAot\Php\Platform\Windows( - phpLibs: [], - isZts: $this->isPhpZts, // ✅ 传递检测到的 ZTS 状态 - phpSdkPath: $phpSdkPath - ); - - // 重新创建 Backend,使用更新后的 Platform - $this->compilerBackend = \PhpAot\Php\Backend\CompilerFactory::createByName( - $this->cppCompiler, - $this->platform - ); - } - $this->climate->info( "Initialized new architecture: {$this->platform->getName()} + {$this->compilerBackend->getName()}" ); @@ -411,232 +380,86 @@ class CompilerBase extends \PhpAot\Core\Translator } } - /** - * Windows 平台:检测 PHP lib 文件并决定 ZTS/NTS 模式 - * 根据找到的 lib 文件来决定是 ZTS 还是 NTS: - * - php8ts.lib 存在 => ZTS 模式 - * - php8.lib 存在 => NTS 模式 - */ - protected function detectWindowsPhpLibs(): void + protected function getPhpxDir(): string { - $phpDirs = [ - $this->getPhpDir() . '\SDK\lib', // 优先从 SDK/lib 查找 - $this->getPhpDir() . '\lib', // 备选从 lib 查找 - ]; - - $embedLibPath = ''; - $tsLibPath = ''; - $ntsLibPath = ''; - - foreach ($phpDirs as $phpDir) { - if (!is_dir($phpDir)) { - continue; - } - - // 检查 php8embed.lib - if (empty($embedLibPath) && file_exists($phpDir . '\php8embed.lib')) { - $embedLibPath = $phpDir . '\php8embed.lib'; - } - - // 检查 php8ts.lib (ZTS) - if (empty($tsLibPath) && file_exists($phpDir . '\php8ts.lib')) { - $tsLibPath = $phpDir . '\php8ts.lib'; - } - - // 检查 php8.lib (NTS) - if (empty($ntsLibPath) && file_exists($phpDir . '\php8.lib')) { - $ntsLibPath = $phpDir . '\php8.lib'; - } - - // 如果已经找到所有需要的库,提前退出 - if ($embedLibPath && ($tsLibPath || $ntsLibPath)) { - break; - } - } - - // 验证必需的库文件是否存在 - if (!$embedLibPath) { - $this->error('php8embed.lib not found. Please ensure it exists in SDK/lib or lib directory of your PHP installation'); - } - - if (!$tsLibPath && !$ntsLibPath) { - $this->error('Neither php8ts.lib nor php8.lib found. Please ensure at least one exists in SDK/lib or lib directory of your PHP installation'); + // 优先使用环境变量 PHPX_HOME + $phpxDir = getenv('PHPX_HOME'); + if ($phpxDir && is_dir($phpxDir)) { + return rtrim($phpxDir, '\/'); } - // 保存库文件路径 - $this->windowsPhpEmbedLib = $embedLibPath; - - // 根据找到的库文件决定 ZTS/NTS 模式 - // 优先级:php8ts.lib > php8.lib - if ($tsLibPath) { - $this->isPhpZts = true; - $this->windowsPhpCoreLib = $tsLibPath; - $this->climate->info('Detected ZTS mode (php8ts.lib found)'); - } else { - $this->isPhpZts = false; - $this->windowsPhpCoreLib = $ntsLibPath; - $this->climate->info('Detected NTS mode (php8.lib found)'); + // 尝试使用 Composer 安装的 phpx + $composerPhpxDir = $this->rootPath . '/vendor/swoole/phpx'; + if (is_dir($composerPhpxDir)) { + return $composerPhpxDir; } - } - /** - * 检测 Clang 是否可用 - */ - protected function isClangAvailable(): bool - { - if (!$this->isWindows) { - return false; + $composerPhpxVendorDir = $this->rootPath . '/vendor/swoole/phpx-vendor'; + if (is_dir($composerPhpxVendorDir)) { + return $composerPhpxVendorDir; } - // 首先尝试 PATH 中的 clang++ - $output = []; - $returnCode = 0; - exec('clang++ --version 2>&1', $output, $returnCode); - - if ($returnCode === 0) { - // 检查是否有 lld-link - $this->checkLldLinker(); - return true; - } + if (defined('ROOT_PATH')) { + $rootPhpxDir = ROOT_PATH . '/vendor/swoole/phpx'; + if (is_dir($rootPhpxDir)) { + return $rootPhpxDir; + } - // 如果 PATH 中没有,尝试从 LLVM_HOME 环境变量获取 - $llvmHome = getenv('LLVM_HOME'); - if ($llvmHome && is_dir($llvmHome)) { - $clangPath = rtrim($llvmHome, '\/') . '\x64\bin\clang++.exe'; - if (file_exists($clangPath)) { - // 验证是否可以执行 - exec('"' . $clangPath . '" --version 2>&1', $output, $returnCode); - if ($returnCode === 0) { - // 将 Clang 路径添加到环境变量 - $clangDir = dirname($clangPath); - putenv("PATH={$clangDir};" . getenv('PATH')); - - // 检查是否有 lld-link - $this->checkLldLinker(); - return true; - } + $rootPhpxVendorDir = ROOT_PATH . '/vendor/swoole/phpx-vendor'; + if (is_dir($rootPhpxVendorDir)) { + return $rootPhpxVendorDir; } } - return false; + // 两个路径都不存在,报错 + $this->error( + 'phpx directory not found. Please either:\n' . + '1. Set PHPX_HOME environment variable to your phpx installation path\n' . + '2. Install phpx via Composer: composer require swoole/phpx' + ); } - /** - * 检查 lld-link 是否可用(用于更快的链接) - */ - protected function checkLldLinker(): void + protected function getPlatform(): PlatformBase { - // 优先使用 lld-link(更快),否则使用 link.exe - $output = []; - $returnCode = 0; - exec('lld-link --version 2>&1', $output, $returnCode); - - if ($returnCode === 0) { - $this->linker = 'lld-link'; - $this->climate->info('Using lld-link linker (faster than link.exe)'); - return; - } - - // 如果 PATH 中没有,尝试从 LLVM_HOME 获取 - $llvmHome = getenv('LLVM_HOME'); - if ($llvmHome && is_dir($llvmHome)) { - $lldLinkPath = rtrim($llvmHome, '\/') . '\x64\bin\lld-link.exe'; - if (file_exists($lldLinkPath)) { - exec('"' . $lldLinkPath . '" --version 2>&1', $output, $returnCode); - if ($returnCode === 0) { - // 将 lld-link 路径添加到环境变量 - $lldDir = dirname($lldLinkPath); - putenv("PATH={$lldDir};" . getenv('PATH')); - $this->linker = 'lld-link'; - $this->climate->info('Using lld-link linker from LLVM_HOME (faster than link.exe)'); - return; - } - } + if ($this->platform === null) { + $this->platform = PlatformFactory::create(); + $this->isWindows = $this->platform instanceof Windows; } - // Fallback 到 MSVC link.exe - $this->linker = 'link'; - $this->climate->info('Using MSVC link.exe linker'); + return $this->platform; } - protected function getPhpxDir(): string + protected function getCompilerBackend(): CompilerBackend { - // 优先使用环境变量 PHPX_HOME - $phpxDir = getenv('PHPX_HOME'); - if ($phpxDir && is_dir($phpxDir)) { - return rtrim($phpxDir, '\/'); - } - - // 尝试使用 Composer 安装的 phpx - $composerPhpxDir = $this->rootPath . '/vendor/swoole/phpx'; - if (is_dir($composerPhpxDir)) { - return $composerPhpxDir; + if ($this->compilerBackend === null) { + $this->cppCompiler = CompilerFactory::detectCompilerName($this->getPlatform(), $this->cppCompiler); + $this->compilerBackend = CompilerFactory::createByName($this->cppCompiler, $this->getPlatform()); } - // 两个路径都不存在,报错 - $this->error( - 'phpx directory not found. Please either:\n' . - '1. Set PHPX_HOME environment variable to your phpx installation path\n' . - '2. Install phpx via Composer: composer require swoole/phpx' - ); + return $this->compilerBackend; } public function isWindows(): bool { - return $this->isWindows; + return $this->getPlatform() instanceof Windows; + } + + public function isLinux(): bool + { + return $this->getPlatform() instanceof Linux; } public function isMacos(): bool { - return strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN'; + return $this->getPlatform() instanceof Macos; } public function getPhpDir(): string { - if ($this->isWindows()) { - // Windows 下尝试从环境变量获取 PHP 路径 - $phpDir = getenv('PHP_HOME'); - if ($phpDir && is_dir($phpDir)) { - return rtrim($phpDir, '\/'); - } - - // 尝试从 php.exe 路径推断(使用 where 命令) - $phpExe = exec('where php 2>nul'); - if ($phpExe) { - $phpDir = dirname($phpExe); - if (is_dir($phpDir)) { - return rtrim($phpDir, '\/'); - } - } - - // 默认路径 - return 'C:\php'; - } else { - // Unix/Linux/macOS 下获取 PHP 路径 - // 优先级:环境变量 PHP_HOME > php-config > which php - - // 1. 尝试环境变量 PHP_HOME - $phpDir = getenv('PHP_HOME'); - if ($phpDir && is_dir($phpDir)) { - return rtrim($phpDir, '\/'); - } - - // 2. 使用 php-config 获取 PHP 路径(优先从 PATH 中查找) - $phpDir = shell_exec('php-config --prefix 2>/dev/null'); - if (!empty($phpDir)) { - return trim($phpDir); - } - - // 3. 如果 php-config 不可用,尝试从 which php 推断 - $phpExe = trim(shell_exec('which php 2>/dev/null')); - if ($phpExe && file_exists($phpExe)) { - $phpDir = dirname(dirname($phpExe)); - if (is_dir($phpDir)) { - return $phpDir; - } - } - - $this->error('The `php-config` is not found. Please install PHP development package or set PHP_HOME environment variable'); + try { + return $this->getPlatform()->getPhpDir(); + } catch (\RuntimeException $e) { + $this->error($e->getMessage()); } } @@ -931,33 +754,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function removeCommonPrefix(string $short, string $long): string { - // Windows 下统一使用反斜杠 - if ($this->isWindows()) { - $short = str_replace('/', '\\', $short); - $long = str_replace('/', '\\', $long); - } - - $len = min(strlen($short), strlen($long)); - $prefixLen = 0; - - for ($i = 0; $i < $len; $i++) { - if ($short[$i] === $long[$i]) { - $prefixLen++; - } else { - break; - } - } - - $result = substr($long, $prefixLen); - - // 移除开头的路径分隔符 - if ($this->isWindows()) { - $result = ltrim($result, '\\'); - } else { - $result = ltrim($result, '/'); - } - - return $result; + return $this->getPlatform()->removeCommonPrefix($short, $long); } protected function getVarType(string $name): string @@ -1319,7 +1116,7 @@ class CompilerBase extends \PhpAot\Core\Translator $type = $expr->getType(); switch ($type) { case 'Scalar_Int': - return $expr->value . ($this->isLinux() ? 'L' : 'LL'); + return $expr->value . $this->getPlatform()->getIntegerLiteralSuffix(); case 'Scalar_Float': return $this->parseScalarFloat($expr); case 'Scalar_String': @@ -2427,10 +2224,11 @@ class CompilerBase extends \PhpAot\Core\Translator } /** - * 解析包含路径 + * 获取包含路径 */ - protected function parseIncludes(): string + protected function getIncludePaths(): array { + $platform = $this->getPlatform(); $includePaths = [ $this->getPhpxDir() . '/include', $this->getBuildDir() . '/include', @@ -2438,39 +2236,49 @@ class CompilerBase extends \PhpAot\Core\Translator ]; // 根据平台添加 PHP 包含路径 - if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { - /** @var \PhpAot\Php\Platform\Windows $platform */ - $platform = $this->platform; + if ($platform instanceof Windows) { $phpSdkPaths = $platform->buildPhpSdkIncludePaths($this->getPhpDir()); $includePaths = array_merge($includePaths, $phpSdkPaths); } else { // Linux/macOS - $phpPaths = $this->platform->buildPhpIncludePaths($this->getPhpDir()); + $phpPaths = $platform->buildPhpIncludePaths($this->getPhpDir()); $includePaths = array_merge($includePaths, $phpPaths); } - return $this->platform->getIncludeFlags($includePaths); + return $includePaths; } - protected function parseLdflags(): string + /** + * 解析包含路径 + */ + protected function parseIncludes(): string + { + return $this->getPlatform()->getIncludeFlags($this->getIncludePaths()); + } + + protected function getLibraryPaths(): array { + $platform = $this->getPlatform(); $libraryPaths = [ $this->getPhpxDir() . '/lib', ]; // 根据平台添加 PHP 库路径 - if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { - /** @var \PhpAot\Php\Platform\Windows $platform */ - $platform = $this->platform; + if ($platform instanceof Windows) { $phpLibPaths = $platform->buildPhpSdkLibPaths($this->getPhpDir()); $libraryPaths = array_merge($libraryPaths, $phpLibPaths); } else { // Linux/macOS - $phpLibPaths = $this->platform->buildPhpLibPaths($this->getPhpDir()); + $phpLibPaths = $platform->buildPhpLibPaths($this->getPhpDir()); $libraryPaths = array_merge($libraryPaths, $phpLibPaths); } - $flags = $this->platform->getLibraryPathFlags($libraryPaths); + return $libraryPaths; + } + + protected function parseLdflags(): string + { + $flags = $this->getPlatform()->getLibraryPathFlags($this->getLibraryPaths()); // 添加用户自定义的 ldflags if (!empty($this->ldflags)) { @@ -2481,14 +2289,15 @@ class CompilerBase extends \PhpAot\Core\Translator } /** - * 解析库文件 + * 获取库文件 */ - protected function parseLibs(): string + protected function getLibraries(): array { + $platform = $this->getPlatform(); $libraries = []; // phpx 库(根据平台使用不同的文件名格式) - if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { + if ($platform instanceof Windows) { // Windows: phpx.lib (无 lib 前缀) $phpxLibPath = $this->getPhpxDir() . '\\lib\\phpx.lib'; if (file_exists($phpxLibPath)) { @@ -2498,7 +2307,7 @@ class CompilerBase extends \PhpAot\Core\Translator } } else { // Linux/macOS: libphpx.so 或 libphpx.a - $sharedLibExt = $this->platform->getSharedLibraryExtension(); + $sharedLibExt = $platform->getSharedLibraryExtension(); // getSharedLibraryExtension() 返回的值可能带点或不带点,需要统一处理 $extWithoutDot = ltrim($sharedLibExt, '.'); $phpxLibPath = $this->getPhpxDir() . '/lib/libphpx.' . $extWithoutDot; @@ -2516,7 +2325,7 @@ class CompilerBase extends \PhpAot\Core\Translator } // extension 和 bin 模式都需要链接 PHP 库 - if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { + if ($platform instanceof Windows) { // Windows: 根据构建模式选择不同的库 if ($this->buildMode === 'bin') { // bin 模式:需要同时链接 php8ts.lib 和 php8embed.lib @@ -2545,7 +2354,15 @@ class CompilerBase extends \PhpAot\Core\Translator $libraries[] = 'php'; } - return $this->platform->getLibraryFlags($libraries); + return $libraries; + } + + /** + * 解析库文件 + */ + protected function parseLibs(): string + { + return $this->getPlatform()->getLibraryFlags($this->getLibraries()); } @@ -2573,7 +2390,7 @@ class CompilerBase extends \PhpAot\Core\Translator 'cxxflags' => $this->cxxflags, ]; - $cmd .= $this->compilerBackend->buildCompileOptions($config); + $cmd .= $this->getCompilerBackend()->buildCompileOptions($config); } else { // 链接时选项 @@ -2589,23 +2406,92 @@ class CompilerBase extends \PhpAot\Core\Translator ]; // 添加 RPATH(通过 Platform 层获取,仅 macOS 需要) - if ($this->platform !== null) { - $rpaths = $this->platform->getDefaultRpaths( - $this->getPhpxDir(), - $this->getPhpDir() - ); - if (!empty($rpaths)) { - $config['rpath'] = $rpaths; - } + $rpaths = $this->getPlatform()->getDefaultRpaths( + $this->getPhpxDir(), + $this->getPhpDir() + ); + if (!empty($rpaths)) { + $config['rpath'] = $rpaths; } - $cmd .= $this->compilerBackend->buildLinkOptions($config); + $cmd .= $this->getCompilerBackend()->buildLinkOptions($config); // 最后添加库文件(平台相关,必须在链接选项之后) $cmd .= ' ' . $this->parseLibs(); } } + protected function getCompileCommandOptions(): array + { + return [ + 'include_paths' => $this->getIncludePaths(), + 'optimize' => $this->optimizeLevel, + 'debug' => $this->debug, + 'sanitize' => $this->sanitize, + 'cpp_std' => $this->cxxStd, + 'is_zts' => $this->isPhpZts, + 'build_mode' => $this->buildMode, + 'enable_profiler' => $this->enableProfiler, + 'suppressed_warnings' => Constants::MSVC_SUPPRESSED_WARNINGS ?? [], + 'cxxflags' => $this->cxxflags, + ]; + } + + protected function getCCompileCommandOptions(): array + { + return [ + 'include_paths' => $this->getIncludePaths(), + 'optimize' => 0, + 'debug' => $this->debug, + 'is_zts' => $this->isPhpZts, + 'suppressed_warnings' => ['4244', '4146'], + ]; + } + + protected function getLinkCommandOptions(): array + { + $options = [ + 'library_paths' => $this->getLibraryPaths(), + 'libraries' => $this->getLibraries(), + 'ldflags' => $this->ldflags, + 'debug' => $this->debug, + 'no_console' => $this->noConsole, + 'build_mode' => $this->buildMode, + 'sanitize' => $this->sanitize, + ]; + + $rpaths = $this->getPlatform()->getDefaultRpaths( + $this->getPhpxDir(), + $this->getPhpDir() + ); + if (!empty($rpaths)) { + $options['rpath'] = $rpaths; + } + + return $options; + } + + protected function getTargetFileName(): string + { + $targetFile = $this->targetName; + $extension = $this->getPlatform()->getTargetExtension($this->buildMode); + + if ($extension !== '' && !str_ends_with($targetFile, $extension)) { + $targetFile .= $extension; + } + + return $targetFile; + } + + protected function buildLinkCommand(array $objectFiles, string $targetFile): string + { + return $this->getCompilerBackend()->buildLinkCommand( + $objectFiles, + $targetFile, + $this->getLinkCommandOptions() + ); + } + protected function parseBinaryOpConcat(Expr\BinaryOp\Concat $expr): string { @@ -4183,7 +4069,7 @@ class CompilerBase extends \PhpAot\Core\Translator } // Windows 下可能没有 clang-format,跳过格式化 - if ($this->isWindows()) { + if ($this->getPlatform() instanceof Windows) { return; } @@ -4883,12 +4769,14 @@ class CompilerBase extends \PhpAot\Core\Translator if ($scope) { break; } - $this->fatalError($expr, "Cannot access protected property `{$property}` of class `{$class}`"); + $displayClass = ltrim($class, '\\'); + $this->fatalError($expr, "Cannot access protected property `{$property}` of class `{$displayClass}`"); } else { if ($scope === $findClass) { break; } - $this->fatalError($expr, "Cannot access private property `{$property}` of class `{$class}`"); + $displayClass = ltrim($class, '\\'); + $this->fatalError($expr, "Cannot access private property `{$property}` of class `{$displayClass}`"); } } elseif ($classDef->extends) { $findClass = $classDef->extends; diff --git a/src/Php/Platform/Linux.php b/src/Php/Platform/Linux.php index cfc32d08..c01d5b28 100644 --- a/src/Php/Platform/Linux.php +++ b/src/Php/Platform/Linux.php @@ -86,6 +86,39 @@ class Linux extends PlatformBase return '/'; } + public function getDefaultCompiler(): string + { + return 'g++'; + } + + public function getPhpDir(): string + { + $phpDir = getenv('PHP_HOME'); + if ($phpDir && is_dir($phpDir)) { + return rtrim($phpDir, '\/'); + } + + $phpDir = shell_exec('php-config --prefix 2>/dev/null'); + if (!empty($phpDir)) { + return trim($phpDir); + } + + $phpExe = trim(shell_exec('which php 2>/dev/null')); + if ($phpExe && file_exists($phpExe)) { + $phpDir = dirname(dirname($phpExe)); + if (is_dir($phpDir)) { + return $phpDir; + } + } + + throw new \RuntimeException('The `php-config` is not found. Please install PHP development package or set PHP_HOME environment variable'); + } + + public function getIntegerLiteralSuffix(): string + { + return 'L'; + } + /** * 获取 RPATH 选项 */ diff --git a/src/Php/Platform/Macos.php b/src/Php/Platform/Macos.php index 160d0218..ecc5aa04 100644 --- a/src/Php/Platform/Macos.php +++ b/src/Php/Platform/Macos.php @@ -86,6 +86,34 @@ class Macos extends PlatformBase return '/'; } + public function getDefaultCompiler(): string + { + return 'clang++'; + } + + public function getPhpDir(): string + { + $phpDir = getenv('PHP_HOME'); + if ($phpDir && is_dir($phpDir)) { + return rtrim($phpDir, '\/'); + } + + $phpDir = shell_exec('php-config --prefix 2>/dev/null'); + if (!empty($phpDir)) { + return trim($phpDir); + } + + $phpExe = trim(shell_exec('which php 2>/dev/null')); + if ($phpExe && file_exists($phpExe)) { + $phpDir = dirname(dirname($phpExe)); + if (is_dir($phpDir)) { + return $phpDir; + } + } + + throw new \RuntimeException('The `php-config` is not found. Please install PHP development package or set PHP_HOME environment variable'); + } + /** * 获取 RPATH 选项(macOS 需要绝对路径) */ diff --git a/src/Php/Platform/PlatformBase.php b/src/Php/Platform/PlatformBase.php index 639d2cb3..272ca47d 100644 --- a/src/Php/Platform/PlatformBase.php +++ b/src/Php/Platform/PlatformBase.php @@ -53,6 +53,83 @@ abstract class PlatformBase */ abstract public function getPathSeparator(): string; + /** + * 获取该平台默认使用的 C++ 编译器命令 + */ + abstract public function getDefaultCompiler(): string; + + /** + * 获取 PHP 安装目录 + */ + abstract public function getPhpDir(): string; + + /** + * 构建 PHP 包含路径 + */ + abstract public function buildPhpIncludePaths(string $phpDir): array; + + /** + * 构建 PHP 库路径 + */ + abstract public function buildPhpLibPaths(string $phpDir): array; + + /** + * 检测 PHP 库文件 + */ + abstract public function detectPhpLibs(string $phpDir): array; + + /** + * 获取指定构建模式的目标文件扩展名 + */ + public function getTargetExtension(string $buildMode): string + { + return $buildMode === 'ext' + ? '.so' + : $this->getExecutableExtension(); + } + + /** + * 获取构建前的运行库检查告警 + */ + public function getBuildLibraryWarnings(string $phpDir, string $phpxDir, string $buildMode): array + { + if ($buildMode !== 'bin') { + return []; + } + + $ext = ltrim($this->getSharedLibraryExtension(), '.'); + $warnings = []; + + if (!is_file($phpDir . '/lib/libphp.' . $ext)) { + $warnings[] = [ + 'warning' => "The `libphp.{$ext}` is not found", + 'info' => 'Note: If you are building an extension (-m ext), this is OK. For binary mode, please run `make` to build it', + ]; + } + + if (!is_file($phpxDir . '/lib/libphpx.' . $ext)) { + $warnings[] = [ + 'warning' => "The `libphpx.{$ext}` is not found", + 'info' => 'Note: If you are building an extension (-m ext), this is OK. For binary mode, please run `make` to build it', + ]; + } + + return $warnings; + } + + /** + * 当前平台是否适合使用 pcntl_fork 并行编译 + */ + public function supportsPcntlParallelCompile(): bool + { + return true; + } + + public function getIntegerLiteralSuffix(): string + { + return 'LL'; + } + /** * 规范化路径 */ @@ -69,6 +146,28 @@ abstract class PlatformBase return implode($this->getPathSeparator(), $parts); } + public function removeCommonPrefix(string $short, string $long): string + { + $separator = $this->getPathSeparator(); + if ($separator === '\\') { + $short = str_replace('/', '\\', $short); + $long = str_replace('/', '\\', $long); + } + + $len = min(strlen($short), strlen($long)); + $prefixLen = 0; + + for ($i = 0; $i < $len; $i++) { + if ($short[$i] === $long[$i]) { + $prefixLen++; + } else { + break; + } + } + + return ltrim(substr($long, $prefixLen), $separator); + } + /** * 获取默认的 RPATH 路径列表(仅 macOS 需要) * diff --git a/src/Php/Platform/Windows.php b/src/Php/Platform/Windows.php index 08bf3730..5579dcfa 100644 --- a/src/Php/Platform/Windows.php +++ b/src/Php/Platform/Windows.php @@ -103,6 +103,34 @@ class Windows extends PlatformBase return '\\'; } + public function getTargetExtension(string $buildMode): string + { + return $buildMode === 'ext' ? '.dll' : '.exe'; + } + + public function getDefaultCompiler(): string + { + return 'cl'; + } + + public function getPhpDir(): string + { + $phpDir = getenv('PHP_HOME'); + if ($phpDir && is_dir($phpDir)) { + return rtrim($phpDir, '\/'); + } + + $phpExe = exec('where php 2>nul'); + if ($phpExe) { + $phpDir = dirname($phpExe); + if (is_dir($phpDir)) { + return rtrim($phpDir, '\/'); + } + } + + return 'C:\php'; + } + /** * 获取 PHP 库文件列表 */ @@ -147,6 +175,47 @@ class Windows extends PlatformBase return '/NODEFAULTLIB:LIBCMT'; } + public function getBuildLibraryWarnings(string $phpDir, string $phpxDir, string $buildMode): array + { + if ($buildMode !== 'bin') { + return []; + } + + $warnings = []; + $phpDirs = [ + $phpDir . '\SDK\lib', + $phpDir . '\lib', + ]; + + $foundLib = false; + foreach ($phpDirs as $dir) { + if (is_dir($dir) && (is_file($dir . '\php8.lib') || is_file($dir . '\php8ts.lib'))) { + $foundLib = true; + break; + } + } + + if (!$foundLib && !is_file($phpDir . '\php8.dll') && !is_file($phpDir . '\php8ts.dll')) { + $warnings[] = [ + 'warning' => 'The `php8.lib` or `php8.dll` is not found in PHP directory, please check your PHP installation', + ]; + } + + if (!is_file($phpxDir . '\lib\phpx.lib') && !is_file($phpxDir . '\lib\phpx.dll')) { + $warnings[] = [ + 'warning' => 'The `phpx.lib` or `phpx.dll` is not found in PHX directory', + 'info' => 'Note: If you are building an extension (-m ext), this is OK. For binary mode, please run `make` to build it', + ]; + } + + return $warnings; + } + + public function supportsPcntlParallelCompile(): bool + { + return false; + } + /** * 获取调试选项 */ @@ -159,6 +228,16 @@ class Windows extends PlatformBase return '/DEBUG'; } + public function buildPhpIncludePaths(string $phpDir): array + { + return $this->buildPhpSdkIncludePaths($phpDir); + } + + public function buildPhpLibPaths(string $phpDir): array + { + return $this->buildPhpSdkLibPaths($phpDir); + } + /** * 构建 PHP SDK 包含路径 */ diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index d23c7258..2abd85a8 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -72,8 +72,7 @@ class Preprocessor extends CompilerBase { $info = pathinfo($file); - // Windows 下使用反斜杠,其他平台使用正斜杠 - $separator = $this->isWindows() ? '\\' : '/'; + $separator = $this->getPlatform()->getPathSeparator(); $relativePath = $this->removeCommonPrefix($this->buildDir, $info['dirname']); return $this->buildDir . $separator . $relativePath . $separator . $info['filename'] . '.cc'; @@ -82,10 +81,10 @@ class Preprocessor extends CompilerBase public function getObjectFile(string $cppFile): string { $info = pathinfo($cppFile); - $ext = $this->isWindows() ? '.obj' : '.o'; + $ext = $this->getPlatform()->getObjectExtension(); // 保持与 cppFile 相同的路径分隔符 - return $info['dirname'] . ($this->isWindows() ? '\\' : '/') . $info['filename'] . $ext; + return $info['dirname'] . $this->getPlatform()->getPathSeparator() . $info['filename'] . $ext; } public function hasCppFileCache(string $file): bool diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 90f11bc2..0a25b5dd 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -283,52 +283,10 @@ class Translator extends Preprocessor { // 根据平台检查库文件(仅在构建二进制文件时需要) if ($this->buildMode === 'bin') { - if ($this->isWindows()) { - // Windows 平台检查 dll 和 lib 文件 - // 按照新的路径规则检查:SDK/lib, lib, 根目录 - $phpDirs = [ - $this->getPhpDir() . '\SDK\lib', - $this->getPhpDir() . '\lib', - ]; - - $foundLib = false; - foreach ($phpDirs as $phpDir) { - if (is_dir($phpDir)) { - if (is_file($phpDir . '\php8.lib') || is_file($phpDir . '\php8ts.lib')) { - $foundLib = true; - break; - } - } - } - - // 如果没找到 lib,检查根目录的 DLL - if (!$foundLib) { - $phpDll = $this->getPhpDir() . '\php8.dll'; - $phpTsDll = $this->getPhpDir() . '\php8ts.dll'; - if (!is_file($phpDll) && !is_file($phpTsDll)) { - $this->climate->warning('The `php8.lib` or `php8.dll` is not found in PHP directory, please check your PHP installation'); - } - } - - $phpxLib = $this->getPhpxDir() . '\lib\phpx.lib'; - $phpxDll = $this->getPhpxDir() . '\lib\phpx.dll'; - if (!is_file($phpxLib) && !is_file($phpxDll)) { - $this->climate->warning('The `phpx.lib` or `phpx.dll` is not found in PHX directory'); - $this->climate->info('Note: If you are building an extension (-m ext), this is OK. For binary mode, please run `make` to build it'); - } - } else { - // Unix/Linux/macOS 平台检查 .so 或 .dylib 文件 - $ext = $this->isMacos() ? 'dylib' : 'so'; - $phpLib = $this->getPhpDir() . '/lib/libphp.' . $ext; - $phpxLib = $this->getPhpxDir() . '/lib/libphpx.' . $ext; - - if (!is_file($phpLib)) { - $this->climate->warning("The `libphp.{$ext}` is not found"); - $this->climate->info('Note: If you are building an extension (-m ext), this is OK. For binary mode, please run `make` to build it'); - } - if (!is_file($phpxLib)) { - $this->climate->warning("The `libphpx.{$ext}` is not found"); - $this->climate->info('Note: If you are building an extension (-m ext), this is OK. For binary mode, please run `make` to build it'); + foreach ($this->getPlatform()->getBuildLibraryWarnings($this->getPhpDir(), $this->getPhpxDir(), $this->buildMode) as $message) { + $this->climate->warning($message['warning']); + if (!empty($message['info'])) { + $this->climate->info($message['info']); } } } @@ -743,38 +701,19 @@ CODE; // 使用 Backend 层构建编译命令 if ($isCppFile) { // C++ 文件:使用标准的编译命令构建 - $cmd = $this->compilerBackend->buildCompileCommand( + $cmd = $this->getCompilerBackend()->buildCompileCommand( $cppFile, $objectFile, - [ - 'optimize' => $this->optimizeLevel, - 'debug' => $this->debug, - 'sanitize' => $this->sanitize, - 'cpp_std' => $this->cxxStd, - 'is_zts' => $this->isPhpZts, - 'build_mode' => $this->buildMode, - 'enable_profiler' => $this->enableProfiler, - 'suppressed_warnings' => Constants::MSVC_SUPPRESSED_WARNINGS ?? [], - 'cxxflags' => $this->cxxflags, - ] + $this->getCompileCommandOptions() ); - - // 添加包含路径(通过 Platform 层) - $cmd .= ' ' . $this->parseIncludes(); } else { // C 文件:不能使用 C++ 特定选项(/EHsc, /std:c++17 等) // 使用 Backend 的 buildCCompileCommand() 方法 - $cmd = $this->compilerBackend->buildCCompileCommand( + $cmd = $this->getCompilerBackend()->buildCCompileCommand( $cppFile, $objectFile, - [ - 'optimize' => 0, - 'suppressed_warnings' => ['4244', '4146'], - ] + $this->getCCompileCommandOptions() ); - - // 手动添加包含路径 - $cmd .= ' ' . $this->parseIncludes(); } if (!$parallel) { @@ -814,8 +753,7 @@ CODE; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/ps_title.c'; } - // Windows 不支持 pcntl_fork,使用串行编译或 proc_open - if ($this->isWindows() or $job <= 1) { + if (!$this->getPlatform()->supportsPcntlParallelCompile() or $job <= 1) { return $this->compileSourceFile($sourceFiles); } @@ -956,40 +894,8 @@ CODE; public function build(array $objectFiles): void { - $targetFile = $this->targetName; - - // 根据平台设置目标文件扩展名 - if ($this->isWindows()) { - if ($this->buildMode == 'ext') { - if (!str_ends_with($targetFile, '.dll')) { - $targetFile .= '.dll'; - } - } else { - if (!str_ends_with($targetFile, '.exe')) { - $targetFile .= '.exe'; - } - } - } else { - if ($this->buildMode == 'ext' and !str_ends_with($targetFile, '.so')) { - $targetFile .= '.so'; - } - } - - // 根据平台构建链接命令 - if ($this->isWindows()) { - // Windows 使用 link.exe 或 lld-link 进行链接 - $objectList = implode(' ', $objectFiles); - - // 使用检测到的链接器(lld-link 或 link) - $linkerCmd = $this->linker; - $linkCmd = "{$linkerCmd} /nologo {$objectList} /OUT:{$targetFile}"; - } else { - // Unix/Linux/macOS GCC 链接命令 - $objectList = implode(' ', $objectFiles); - $linkCmd = $this->cppCompiler . ' ' . $objectList . ' -o ' . $targetFile; - } - - $this->addCompilationOption($linkCmd, true); + $targetFile = $this->getTargetFileName(); + $linkCmd = $this->buildLinkCommand($objectFiles, $targetFile); $this->climate->comment($linkCmd); // 执行链接并捕获输出