feat(compiler): 增强 PHP 编译器对 Windows 平台和 ZTS 版本的支持

- 检测 PHP 是否为线程安全版本(ZTS)并相应配置编译选项
- 添加 PHPX_HOME 环境变量支持用于自定义 phpx 目录
- 优化 Windows 平台下 PHP 路径检测逻辑,支持从 SDK/include 目录读取头文件
- 实现 Windows 平台下 PHP 库文件的多路径查找策略(SDK/lib, lib, 根目录)
- 添加 Windows 编译时必需的宏定义(ZEND_WIN32, PHP_WIN32, ZTS 等)
- 修复跨平台路径分隔符处理问题,统一 Windows 使用反斜杠
- 增强错误处理和用户提示信息
- 新增 Preprocessor 类用于源文件预处理和依赖分析
pull/1/head
韩天峰 4 months ago
parent 69886abb5d
commit be77cfaab1
  1. 210
      src/Php/CompilerBase.php
  2. 12
      src/Php/Preprocessor.php
  3. 152
      src/Php/Translator.php

@ -253,6 +253,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected bool $forTest = false;
protected Parser $parser;
protected PrettyPrinter $printer;
protected bool $isPhpZts = false; // PHP 是否为线程安全版本
/**
* 在预处理阶段获取所有类的方法名称,检测子类和父类中存在的同名方法,解决动态绑定方法调用的问题
@ -287,6 +288,9 @@ class CompilerBase extends \PhpAot\Core\Translator
// 检测操作系统并设置编译器
$this->detectPlatform();
// 检测 PHP 是否为线程安全版本(ZTS)
$this->isPhpZts = defined('PHP_ZTS') && PHP_ZTS === 1;
}
protected function detectPlatform(): void
@ -307,6 +311,13 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function getPhpxDir(): string
{
// 优先使用环境变量 PHPX_HOME
$phpxDir = getenv('PHPX_HOME');
if ($phpxDir && is_dir($phpxDir)) {
return rtrim($phpxDir, '\\/');
}
// 默认路径
return $this->rootPath . '/vendor/swoole/phpx';
}
@ -323,13 +334,13 @@ class CompilerBase extends \PhpAot\Core\Translator
public function getPhpDir(): string
{
if ($this->isWindows()) {
// Windows 下尝试从环境变量或注册表获取 PHP 路径
// Windows 下尝试从环境变量获取 PHP 路径
$phpDir = getenv('PHP_HOME');
if ($phpDir && is_dir($phpDir)) {
return rtrim($phpDir, '\\/');
}
// 尝试从 php.exe 路径推断
// 尝试从 php.exe 路径推断(使用 where 命令)
$phpExe = exec('where php 2>nul');
if ($phpExe) {
$phpDir = dirname($phpExe);
@ -341,9 +352,18 @@ class CompilerBase extends \PhpAot\Core\Translator
// 默认路径
return 'C:\\php';
} else {
$phpDir = shell_exec('php-config --prefix');
// Unix/Linux/macOS 下使用 php-config 获取 PHP 路径
$phpDir = shell_exec('php-config --prefix 2>/dev/null');
if (empty($phpDir)) {
$this->error('The `php-config` is not found');
// 如果 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');
}
return trim($phpDir);
}
@ -640,6 +660,12 @@ 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;
@ -651,7 +677,16 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
return substr($long, $prefixLen);
$result = substr($long, $prefixLen);
// 移除开头的路径分隔符
if ($this->isWindows()) {
$result = ltrim($result, '\\');
} else {
$result = ltrim($result, '/');
}
return $result;
}
protected function getVarType(string $name): string
@ -2105,7 +2140,21 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->getBuildDir() . '/include',
$this->getPhpxDir() . '/src/misc',
];
$out = '$(php-config --includes) ';
// 尝试使用 php-config 获取包含路径,如果失败则手动构建
$phpIncludes = shell_exec('php-config --includes 2>/dev/null');
if ($phpIncludes) {
$out = trim($phpIncludes) . ' ';
} else {
// 手动构建包含路径
$phpInclude = $this->getPhpDir() . '/include/php';
if (is_dir($phpInclude)) {
$out = '-I ' . $phpInclude . ' ';
} else {
$out = '';
}
}
foreach ($list as $li) {
$out .= '-I ' . $li . ' ';
}
@ -2121,15 +2170,32 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->getPhpxDir() . '\\src\\misc',
];
// 获取 PHP 的包含路径(Windows)
$phpInclude = $this->getPhpDir() . '\\include';
if (is_dir($phpInclude)) {
$list[] = $phpInclude;
// Windows 下 PHP 头文件必须从 SDK/include 读取
$phpSdkInclude = $this->getPhpDir() . '\\SDK\\include';
if (!is_dir($phpSdkInclude)) {
throw new \RuntimeException(
"PHP SDK include directory not found: {$phpSdkInclude}\n" .
"Please ensure PHP is installed with SDK headers at the expected location."
);
}
// 添加主包含目录
$list[] = $phpSdkInclude;
// 添加子目录:main, Zend, TSRM, ext
$subDirs = ['main', 'Zend', 'TSRM', 'ext'];
foreach ($subDirs as $subDir) {
$subPath = $phpSdkInclude . '\\' . $subDir;
if (is_dir($subPath)) {
$list[] = $subPath;
}
}
$out = '';
foreach ($list as $li) {
$out .= '/I "' . $li . '" ';
// 确保路径使用双引号包裹,处理可能的空格
$normalizedPath = str_replace('/', '\\', $li);
$out .= '/I "' . $normalizedPath . '" ';
}
return $out;
@ -2142,9 +2208,24 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$list = [
'$(php-config --prefix)/lib',
$this->getPhpxDir() . '/lib',
];
// 尝试使用 php-config 获取库路径,如果失败则手动构建
$phpLibDir = shell_exec('php-config --prefix 2>/dev/null');
if ($phpLibDir) {
$phpLibPath = trim($phpLibDir) . '/lib';
if (is_dir($phpLibPath)) {
array_unshift($list, $phpLibPath);
}
} else {
// 手动添加 PHP 库路径
$phpLibPath = $this->getPhpDir() . '/lib';
if (is_dir($phpLibPath)) {
array_unshift($list, $phpLibPath);
}
}
$out = '';
foreach ($list as $li) {
$out .= '-L ' . $li . ' ';
@ -2155,10 +2236,25 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseWindowsLdflags(): string
{
$list = [
$this->getPhpDir() . '\\lib',
$this->getPhpxDir() . '\\lib',
];
$list = [];
// Windows 下 PHP 库文件固定从 SDK/lib 读取
$phpLib = $this->getPhpDir() . '\\SDK\\lib';
if (is_dir($phpLib)) {
$list[] = $phpLib;
} else {
// 备选:尝试直接从 lib 目录
$phpLibAlt = $this->getPhpDir() . '\\lib';
if (is_dir($phpLibAlt)) {
$list[] = $phpLibAlt;
}
}
// phpx 库文件
$phpxLib = $this->getPhpxDir() . '\\lib';
if (is_dir($phpxLib)) {
$list[] = $phpxLib;
}
$out = '';
foreach ($list as $li) {
@ -2207,22 +2303,53 @@ class CompilerBase extends \PhpAot\Core\Translator
}
if ($this->buildMode === 'bin') {
// 优先使用 .lib 导入库
$phpLib = $this->getPhpDir() . '\\lib\\php8.lib';
if (file_exists($phpLib)) {
$list[] = '"' . $phpLib . '"';
} else {
// 尝试其他可能的文件名
$altPhpLib = $this->getPhpDir() . '\\lib\\php8ts.lib';
// Windows 下 PHP 库文件和 DLL 的查找顺序:
// 1. SDK/lib/php8.lib
// 2. SDK/lib/php8ts.lib
// 3. lib/php8.lib
// 4. lib/php8ts.lib
// 5. 根目录/php8.dll
$phpDirs = [
$this->getPhpDir() . '\\SDK\\lib', // 优先从 SDK/lib 查找
$this->getPhpDir() . '\\lib', // 备选从 lib 查找
];
$found = false;
foreach ($phpDirs as $phpDir) {
if (!is_dir($phpDir)) continue;
// 尝试 php8.lib
$phpLib = $phpDir . '\\php8.lib';
if (file_exists($phpLib)) {
$list[] = '"' . $phpLib . '"';
$found = true;
break;
}
// 尝试 php8ts.lib
$altPhpLib = $phpDir . '\\php8ts.lib';
if (file_exists($altPhpLib)) {
$list[] = '"' . $altPhpLib . '"';
$found = true;
break;
}
}
// 如果都没找到 .lib,尝试直接使用根目录的 DLL
if (!$found) {
$phpDll = $this->getPhpDir() . '\\php8.dll';
if (file_exists($phpDll)) {
$this->climate->magenta('php8.lib not found, using php8.dll directly');
$this->climate->info('Note: This may require additional setup');
$list[] = '"' . $phpDll . '"';
} else {
// 最后尝试直接使用 DLL
$phpDll = $this->getPhpDir() . '\\php8.dll';
if (file_exists($phpDll)) {
$this->climate->magenta('php8.lib not found, using php8.dll directly');
// 尝试 php8ts.dll
$phpTsDll = $this->getPhpDir() . '\\php8ts.dll';
if (file_exists($phpTsDll)) {
$this->climate->magenta('php8.lib not found, using php8ts.dll directly');
$this->climate->info('Note: This may require additional setup');
$list[] = '"' . $phpDll . '"';
$list[] = '"' . $phpTsDll . '"';
}
}
}
@ -2249,8 +2376,22 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function addWindowsCompilationOption(string &$cmd, bool $link): void
{
// 添加包含路径
$cmd .= ' ' . $this->parseWindowsIncludes();
// 添加包含路径(仅在编译时,不在链接时)
if (!$link) {
$cmd .= ' ' . $this->parseWindowsIncludes();
}
// Windows 平台必需的宏定义(参考 CMakeLists.txt)
$cmd .= ' /DZEND_WIN32'; // 标识 Windows 平台
$cmd .= ' /DPHP_WIN32'; // 标识 Windows 平台
$cmd .= ' /DZEND_DEBUG=0'; // 禁用调试模式
// 根据 PHP 是否为线程安全版本决定是否添加 ZTS 宏
if ($this->isPhpZts) {
$cmd .= ' /DZTS'; // 启用线程安全
}
$cmd .= ' /DZEND_ENABLE_STATIC_TSRMLS_CACHE'; // 启用静态 TSRM 缓存
// 优化级别
switch ($this->optimizeLevel) {
@ -2273,8 +2414,15 @@ class CompilerBase extends \PhpAot\Core\Translator
// 警告级别
$cmd .= ' /W3';
// 禁用 PHP SDK 头文件中的常见警告
$cmd .= ' /wd4244'; // 禁用类型转换警告(__int64 到 int)
$cmd .= ' /wd4146'; // 禁用一元负运算符应用于无符号类型的警告
// 禁用编译器版权信息输出
$cmd .= ' /nologo';
// C++ 标准
// C++ 标准(仅在编译时)
if (!$link && !str_contains($this->cxxflags, '/std:')) {
$cmd .= ' /std:c++17';
}

@ -66,15 +66,21 @@ class Preprocessor extends CompilerBase
public function getCppFile(string $file): string
{
$info = pathinfo($file);
return $this->buildDir . '/' . $this->removeCommonPrefix($this->buildDir, $info['dirname'] . '/' . $info['filename'] . '.cc');
// Windows 下使用反斜杠,其他平台使用正斜杠
$separator = $this->isWindows() ? '\\' : '/';
$relativePath = $this->removeCommonPrefix($this->buildDir, $info['dirname']);
return $this->buildDir . $separator . $relativePath . $separator . $info['filename'] . '.cc';
}
public function getObjectFile(string $cppFile): string
{
$info = pathinfo($cppFile);
$ext = $this->isWindows() ? '.obj' : '.o';
return $info['dirname'] . '/' . $info['filename'] . $ext;
// 保持与 cppFile 相同的路径分隔符
return $info['dirname'] . ($this->isWindows() ? '\\' : '/') . $info['filename'] . $ext;
}
public function hasCppFileCache(string $file): bool

@ -213,12 +213,56 @@ class Translator extends Preprocessor
public function prepare(string $path): array
{
$ext = $this->isMacos() ? 'dylib' : 'so';
if (!is_file($this->getPhpDir() . '/lib/libphp.' . $ext)) {
$this->error("The `libphp.{$ext}` is not found, please run `make` to build it");
}
if (!is_file($this->getPhpxDir() . '/lib/libphpx.' . $ext)) {
$this->error("The `libphpx.{$ext}` is not found, please run `make` to build it");
// 根据平台检查库文件(仅在构建二进制文件时需要)
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");
}
}
}
$clangFormatVersion = shell_exec('clang-format --version');
@ -321,13 +365,14 @@ class Translator extends Preprocessor
$literalStringsCount = count($this->literalStrings);
$lines[] = 'extern ' . self::TYPE_STR . ' ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL;
$classCount = count($this->classMap);
// 确保数组大小至少为 1,避免 C/C++ 编译错误
$classCount = max(1, count($this->classMap));
$lines[] = 'extern zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . $classCount . '];' . PHP_EOL;
$funcCount = count($this->funcMap);
$funcCount = max(1, count($this->funcMap));
$lines[] = 'extern zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . $funcCount . '];' . PHP_EOL;
$propCount = count($this->propMap);
$propCount = max(1, count($this->propMap));
$lines[] = 'extern uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . $propCount . '];' . PHP_EOL;
foreach ($this->classes as $classDef) {
@ -378,13 +423,14 @@ class Translator extends Preprocessor
}
$code .= "// class entry \n";
$code .= 'zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . count($this->classMap) . '];' . PHP_EOL;
// 确保数组大小至少为 1,避免 C/C++ 编译错误
$code .= 'zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . max(1, count($this->classMap)) . '];' . PHP_EOL;
$code .= "// func \n";
$code .= 'zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . count($this->funcMap) . '];' . PHP_EOL;
$code .= 'zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . max(1, count($this->funcMap)) . '];' . PHP_EOL;
$code .= "// property \n";
$code .= 'uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . count($this->propMap) . '];' . PHP_EOL;
$code .= 'uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . max(1, count($this->propMap)) . '];' . PHP_EOL;
$code .= <<<'CODE'
zend_class_entry *php_get_class(int class_id, const php::Str &class_name) {
@ -661,7 +707,63 @@ CODE;
$sourceFiles[] = $this->getPhpxDir() . '/src/misc/ps_title.c';
}
// 并行编译
// Windows 不支持 pcntl_fork,使用串行编译或 proc_open
if ($this->isWindows()) {
return $this->compileOnWindows($sourceFiles);
}
// Unix/Linux/macOS 使用 pcntl 并行编译
return $this->compileWithPcntl($sourceFiles, $job);
}
/**
* Windows 平台编译(不使用 pcntl)
*/
protected function compileOnWindows(array $sourceFiles): array
{
$objectFiles = [];
$totalFiles = count($sourceFiles);
$failedFiles = [];
$this->climate->lightBlue("Starting compilation for {$totalFiles} files (Windows mode)");
foreach ($sourceFiles as $cppFile) {
$objectFile = $this->getObjectFile($cppFile);
try {
$this->compileFile($cppFile, $objectFile, false);
if (is_file($objectFile)) {
$objectFiles[] = $objectFile;
} else {
$failedFiles[] = $cppFile;
$this->climate->red("Compilation failed: {$cppFile}");
}
} catch (\Throwable $e) {
$failedFiles[] = $cppFile;
$this->climate->red("Compilation error: {$cppFile} - " . $e->getMessage());
}
}
if (!empty($failedFiles)) {
throw new \Exception('Compilation failed for: ' . implode(', ', $failedFiles));
}
$this->climate->green("Successfully compiled {$totalFiles} files");
return $objectFiles;
}
/**
* Unix/Linux/macOS 平台并行编译(使用 pcntl)
*/
protected function compileWithPcntl(array $sourceFiles, int $job): array
{
// 检查 pcntl 扩展是否可用
if (!function_exists('pcntl_fork')) {
$this->climate->warning('pcntl extension not available, using sequential compilation');
return $this->compileOnWindows($sourceFiles);
}
$objectFiles = [];
$totalFiles = count($sourceFiles);
$runningProcesses = 0;
$processPipes = [];
@ -738,7 +840,6 @@ CODE;
}
$this->climate->green("Successfully compiled {$totalFiles} files");
return $objectFiles;
}
@ -781,7 +882,28 @@ CODE;
$this->addCompilationOption($linkCmd, true);
$this->climate->comment($linkCmd);
shell_exec($linkCmd);
// 执行链接并捕获输出
exec($linkCmd . ' 2>&1', $output, $ret);
// 显示输出(如果有)
if (!empty($output)) {
foreach ($output as $line) {
$this->climate->out($line);
}
}
// 检查链接是否成功
if ($ret !== 0) {
$this->error('link failed: ' . $targetFile);
}
// 验证目标文件是否生成
if (!file_exists($targetFile)) {
$this->error('target file not generated: ' . $targetFile);
}
$this->climate->green('Build successful: ' . $targetFile);
}
public function genFunctionDeclaration(string $file): void

Loading…
Cancel
Save