- 实现 CompilerBackend 抽象基类定义编译器接口 - 添加 Clang、GCC、MSVC 编译器后端具体实现 - 创建 PlatformBase 抽象基类定义平台接口 - 添加 Windows、Linux、macOS 平台具体实现 - 实现 CompilerFactory 工厂类自动创建编译器 - 实现 PlatformFactory 工厂类自动检测平台 - 提供跨平台路径处理和命令行参数生成 - 添加完整的使用示例和架构说明文档pull/1/head
parent
0793a647ee
commit
95827cfb73
12 changed files with 1471 additions and 0 deletions
@ -0,0 +1,200 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Backend; |
||||
|
||||
use PhpAot\Php\Platform\PlatformBase; |
||||
|
||||
/** |
||||
* Clang 编译器后端实现 |
||||
*/ |
||||
class Clang extends CompilerBackend |
||||
{ |
||||
public function getName(): string |
||||
{ |
||||
return 'Clang'; |
||||
} |
||||
|
||||
public function getCompilerCommand(): string |
||||
{ |
||||
return 'clang++'; |
||||
} |
||||
|
||||
public function getLinkerCommand(): string |
||||
{ |
||||
// Windows 下使用 MSVC 链接器,其他平台使用 clang++ |
||||
if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { |
||||
return 'link'; |
||||
} |
||||
return 'clang++'; |
||||
} |
||||
|
||||
public function compileFile( |
||||
string $sourceFile, |
||||
string $outputFile, |
||||
array $includePaths = [], |
||||
array $defines = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getCompilerCommand(); |
||||
|
||||
// Windows 下需要 MSVC 兼容模式 |
||||
if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { |
||||
$cmd .= ' -fms-compatibility'; |
||||
$cmd .= ' -fms-compatibility-version=19.40'; |
||||
$cmd .= ' -fdelayed-template-parsing'; |
||||
$cmd .= ' -fms-extensions'; |
||||
} |
||||
|
||||
$cmd .= ' -c ' . escapeshellarg($sourceFile); |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
|
||||
// 添加包含路径 |
||||
if (!empty($includePaths)) { |
||||
$cmd .= ' ' . $this->formatIncludePaths($includePaths); |
||||
} |
||||
|
||||
// 添加宏定义 |
||||
foreach ($defines as $define) { |
||||
$cmd .= ' -D' . $define; |
||||
} |
||||
|
||||
// 添加额外标志 |
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function linkObjects( |
||||
array $objectFiles, |
||||
string $outputFile, |
||||
array $libraryPaths = [], |
||||
array $libraries = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getLinkerCommand(); |
||||
|
||||
// 添加目标文件 |
||||
$cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); |
||||
|
||||
// 输出文件 |
||||
if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { |
||||
$cmd .= ' /OUT:' . escapeshellarg($outputFile); |
||||
} else { |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
} |
||||
|
||||
// 添加库路径 |
||||
if (!empty($libraryPaths)) { |
||||
$cmd .= ' ' . $this->formatLibraryPaths($libraryPaths); |
||||
} |
||||
|
||||
// 添加库文件 |
||||
if (!empty($libraries)) { |
||||
$cmd .= ' ' . $this->formatLibraries($libraries); |
||||
} |
||||
|
||||
// 添加额外标志 |
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildCompileCommand(string $sourceFile, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getCompilerCommand(); |
||||
|
||||
// Windows 下需要 MSVC 兼容模式 |
||||
if ($this->platform instanceof \PhpAot\Php\Platform\Windows) { |
||||
$cmd .= ' -fms-compatibility'; |
||||
$cmd .= ' -fms-compatibility-version=19.40'; |
||||
$cmd .= ' -fdelayed-template-parsing'; |
||||
$cmd .= ' -fms-extensions'; |
||||
} |
||||
|
||||
$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'; |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildLinkCommand(array $objectFiles, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getLinkerCommand(); |
||||
$cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); |
||||
|
||||
// Windows 使用 MSVC 链接器语法 |
||||
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']; |
||||
} |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
} |
||||
@ -0,0 +1,109 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Backend; |
||||
|
||||
use PhpAot\Php\Platform\PlatformBase; |
||||
|
||||
/** |
||||
* 编译器后端抽象基类 |
||||
* 定义所有编译器必须实现的接口 |
||||
*/ |
||||
abstract class CompilerBackend |
||||
{ |
||||
/** |
||||
* 平台实例 |
||||
*/ |
||||
protected PlatformBase $platform; |
||||
|
||||
public function __construct(PlatformBase $platform) |
||||
{ |
||||
$this->platform = $platform; |
||||
} |
||||
|
||||
/** |
||||
* 获取编译器名称 |
||||
*/ |
||||
abstract public function getName(): string; |
||||
|
||||
/** |
||||
* 获取编译器命令 |
||||
*/ |
||||
abstract public function getCompilerCommand(): string; |
||||
|
||||
/** |
||||
* 获取链接器命令 |
||||
*/ |
||||
abstract public function getLinkerCommand(): string; |
||||
|
||||
/** |
||||
* 编译单个文件 |
||||
*/ |
||||
abstract public function compileFile( |
||||
string $sourceFile, |
||||
string $outputFile, |
||||
array $includePaths = [], |
||||
array $defines = [], |
||||
array $flags = [] |
||||
): string; |
||||
|
||||
/** |
||||
* 链接目标文件 |
||||
*/ |
||||
abstract public function linkObjects( |
||||
array $objectFiles, |
||||
string $outputFile, |
||||
array $libraryPaths = [], |
||||
array $libraries = [], |
||||
array $flags = [] |
||||
): string; |
||||
|
||||
/** |
||||
* 构建完整的编译命令 |
||||
*/ |
||||
abstract public function buildCompileCommand( |
||||
string $sourceFile, |
||||
string $outputFile, |
||||
array $options = [] |
||||
): string; |
||||
|
||||
/** |
||||
* 构建完整的链接命令 |
||||
*/ |
||||
abstract public function buildLinkCommand( |
||||
array $objectFiles, |
||||
string $outputFile, |
||||
array $options = [] |
||||
): string; |
||||
|
||||
/** |
||||
* 获取平台实例 |
||||
*/ |
||||
public function getPlatform(): PlatformBase |
||||
{ |
||||
return $this->platform; |
||||
} |
||||
|
||||
/** |
||||
* 格式化包含路径 |
||||
*/ |
||||
protected function formatIncludePaths(array $includePaths): string |
||||
{ |
||||
return $this->platform->getIncludeFlags($includePaths); |
||||
} |
||||
|
||||
/** |
||||
* 格式化库路径 |
||||
*/ |
||||
protected function formatLibraryPaths(array $libraryPaths): string |
||||
{ |
||||
return $this->platform->getLibraryPathFlags($libraryPaths); |
||||
} |
||||
|
||||
/** |
||||
* 格式化库文件 |
||||
*/ |
||||
protected function formatLibraries(array $libraries): string |
||||
{ |
||||
return $this->platform->getLibraryFlags($libraries); |
||||
} |
||||
} |
||||
@ -0,0 +1,68 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Backend; |
||||
|
||||
use PhpAot\Php\Platform\PlatformBase; |
||||
use PhpAot\Php\Platform\Windows; |
||||
use PhpAot\Php\Platform\Linux; |
||||
use PhpAot\Php\Platform\Macos; |
||||
|
||||
/** |
||||
* 编译器工厂类 |
||||
* 根据平台自动创建合适的编译器后端 |
||||
*/ |
||||
class CompilerFactory |
||||
{ |
||||
/** |
||||
* 创建默认编译器后端 |
||||
*/ |
||||
public static function create(PlatformBase $platform): CompilerBackend |
||||
{ |
||||
if ($platform instanceof Windows) { |
||||
// Windows 默认使用 MSVC |
||||
return new Msvc($platform); |
||||
} elseif ($platform instanceof Linux) { |
||||
// Linux 默认使用 GCC |
||||
return new Gcc($platform); |
||||
} elseif ($platform instanceof Macos) { |
||||
// macOS 默认使用 Clang |
||||
return new Clang($platform); |
||||
} else { |
||||
throw new \RuntimeException("Unsupported platform: " . $platform->getName()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 创建指定类型的编译器后端 |
||||
*/ |
||||
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}"), |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* 自动检测并创建编译器和平台 |
||||
*/ |
||||
public static function autoDetect(string $compilerName = ''): array |
||||
{ |
||||
// 创建平台 |
||||
$platform = \PhpAot\Php\Platform\PlatformFactory::create(); |
||||
|
||||
// 创建编译器 |
||||
if (!empty($compilerName)) { |
||||
$compiler = self::createByName($compilerName, $platform); |
||||
} else { |
||||
$compiler = self::create($platform); |
||||
} |
||||
|
||||
return [ |
||||
'platform' => $platform, |
||||
'compiler' => $compiler, |
||||
]; |
||||
} |
||||
} |
||||
@ -0,0 +1,143 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Backend; |
||||
|
||||
use PhpAot\Php\Platform\PlatformBase; |
||||
|
||||
/** |
||||
* GCC 编译器后端实现 |
||||
*/ |
||||
class Gcc extends CompilerBackend |
||||
{ |
||||
public function getName(): string |
||||
{ |
||||
return 'GCC'; |
||||
} |
||||
|
||||
public function getCompilerCommand(): string |
||||
{ |
||||
return 'g++'; |
||||
} |
||||
|
||||
public function getLinkerCommand(): string |
||||
{ |
||||
return 'g++'; |
||||
} |
||||
|
||||
public function compileFile( |
||||
string $sourceFile, |
||||
string $outputFile, |
||||
array $includePaths = [], |
||||
array $defines = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getCompilerCommand(); |
||||
$cmd .= ' -c ' . escapeshellarg($sourceFile); |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
|
||||
// 添加包含路径 |
||||
if (!empty($includePaths)) { |
||||
$cmd .= ' ' . $this->formatIncludePaths($includePaths); |
||||
} |
||||
|
||||
// 添加宏定义 |
||||
foreach ($defines as $define) { |
||||
$cmd .= ' -D' . $define; |
||||
} |
||||
|
||||
// 添加额外标志 |
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function linkObjects( |
||||
array $objectFiles, |
||||
string $outputFile, |
||||
array $libraryPaths = [], |
||||
array $libraries = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getLinkerCommand(); |
||||
|
||||
// 添加目标文件 |
||||
$cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); |
||||
|
||||
// 输出文件 |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
|
||||
// 添加库路径 |
||||
if (!empty($libraryPaths)) { |
||||
$cmd .= ' ' . $this->formatLibraryPaths($libraryPaths); |
||||
} |
||||
|
||||
// 添加库文件 |
||||
if (!empty($libraries)) { |
||||
$cmd .= ' ' . $this->formatLibraries($libraries); |
||||
} |
||||
|
||||
// 添加额外标志 |
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildCompileCommand(string $sourceFile, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getCompilerCommand(); |
||||
$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'; |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildLinkCommand(array $objectFiles, string $outputFile, array $options = []): string |
||||
{ |
||||
$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']); |
||||
} |
||||
} |
||||
|
||||
// RPATH(运行时库搜索路径) |
||||
if (!empty($options['rpath'])) { |
||||
$cmd .= ' ' . $this->platform->getRpathOptions($options['rpath']); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
} |
||||
@ -0,0 +1,156 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Backend; |
||||
|
||||
use PhpAot\Php\Platform\Windows; |
||||
|
||||
/** |
||||
* MSVC 编译器后端实现 |
||||
*/ |
||||
class Msvc extends CompilerBackend |
||||
{ |
||||
public function __construct(Windows $platform) |
||||
{ |
||||
parent::__construct($platform); |
||||
} |
||||
|
||||
public function getName(): string |
||||
{ |
||||
return 'MSVC'; |
||||
} |
||||
|
||||
public function getCompilerCommand(): string |
||||
{ |
||||
return 'cl'; |
||||
} |
||||
|
||||
public function getLinkerCommand(): string |
||||
{ |
||||
return 'link'; |
||||
} |
||||
|
||||
public function compileFile( |
||||
string $sourceFile, |
||||
string $outputFile, |
||||
array $includePaths = [], |
||||
array $defines = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getCompilerCommand(); |
||||
$cmd .= ' /c ' . escapeshellarg($sourceFile); |
||||
$cmd .= ' /Fo' . escapeshellarg($outputFile); |
||||
|
||||
// 添加包含路径 |
||||
if (!empty($includePaths)) { |
||||
$cmd .= ' ' . $this->formatIncludePaths($includePaths); |
||||
} |
||||
|
||||
// 添加宏定义 |
||||
foreach ($defines as $define) { |
||||
$cmd .= ' /D' . $define; |
||||
} |
||||
|
||||
// 添加额外标志 |
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function linkObjects( |
||||
array $objectFiles, |
||||
string $outputFile, |
||||
array $libraryPaths = [], |
||||
array $libraries = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getLinkerCommand(); |
||||
|
||||
// 添加目标文件 |
||||
$cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); |
||||
|
||||
// 输出文件 |
||||
$cmd .= ' /OUT:' . escapeshellarg($outputFile); |
||||
|
||||
// 添加库路径 |
||||
if (!empty($libraryPaths)) { |
||||
$cmd .= ' ' . $this->formatLibraryPaths($libraryPaths); |
||||
} |
||||
|
||||
// 添加库文件 |
||||
if (!empty($libraries)) { |
||||
$cmd .= ' ' . $this->formatLibraries($libraries); |
||||
} |
||||
|
||||
// 添加额外标志 |
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildCompileCommand(string $sourceFile, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getCompilerCommand(); |
||||
$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'; |
||||
|
||||
// C++ 标准 |
||||
$cppStd = $options['cpp_std'] ?? 'c++17'; |
||||
$cmd .= ' /std:' . $cppStd; |
||||
|
||||
// 异常处理 |
||||
$cmd .= ' /EHsc'; |
||||
|
||||
// CRT |
||||
$cmd .= ' /MD'; |
||||
|
||||
// nologo |
||||
$cmd .= ' /nologo'; |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildLinkCommand(array $objectFiles, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getLinkerCommand(); |
||||
$cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); |
||||
$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(); |
||||
|
||||
// nologo |
||||
$cmd .= ' /nologo'; |
||||
|
||||
return $cmd; |
||||
} |
||||
} |
||||
@ -0,0 +1,193 @@ |
||||
# 编译器和平台抽象层 |
||||
|
||||
## 目录结构 |
||||
|
||||
``` |
||||
src/Php/ |
||||
├── Backend/ # 编译器后端抽象 |
||||
│ ├── CompilerBackend.php # 编译器抽象基类 |
||||
│ ├── CompilerFactory.php # 编译器工厂(自动检测) |
||||
│ ├── Msvc.php # MSVC 编译器实现 ✅ |
||||
│ ├── Gcc.php # GCC 编译器实现 ✅ |
||||
│ ├── Clang.php # Clang 编译器实现 ✅ |
||||
│ ├── example_usage.php # 使用示例 |
||||
│ └── README.md # 本文档 |
||||
└── Platform/ # 平台抽象 |
||||
├── PlatformBase.php # 平台抽象基类 |
||||
├── PlatformFactory.php # 平台工厂(自动检测) |
||||
├── Windows.php # Windows 平台实现 ✅ |
||||
├── Linux.php # Linux 平台实现 ✅ |
||||
└── Macos.php # macOS 平台实现 ✅ |
||||
``` |
||||
|
||||
## 设计理念 |
||||
|
||||
### 1. 平台抽象 (Platform) |
||||
|
||||
**职责:** |
||||
- 处理不同操作系统的差异 |
||||
- 提供统一的路径、文件扩展名、命令行参数格式 |
||||
- 封装平台特定的配置和选项 |
||||
|
||||
**核心方法:** |
||||
- `getIncludeFlags()` - 获取包含路径参数 |
||||
- `getLibraryPathFlags()` - 获取库路径参数 |
||||
- `getLibraryFlags()` - 获取库文件参数 |
||||
- `getObjectExtension()` - 获取对象文件扩展名 |
||||
- `getExecutableExtension()` - 获取可执行文件扩展名 |
||||
|
||||
### 2. 编译器后端 (Backend) |
||||
|
||||
**职责:** |
||||
- 处理不同编译器的差异 |
||||
- 生成编译和链接命令 |
||||
- 封装编译器特定的选项和标志 |
||||
|
||||
**核心方法:** |
||||
- `compileFile()` - 编译单个文件 |
||||
- `linkObjects()` - 链接目标文件 |
||||
- `buildCompileCommand()` - 构建完整编译命令 |
||||
- `buildLinkCommand()` - 构建完整链接命令 |
||||
|
||||
## 使用示例 |
||||
|
||||
### 基本用法 |
||||
|
||||
```php |
||||
use PhpAot\Php\Platform\Windows; |
||||
use PhpAot\Php\Backend\Msvc; |
||||
|
||||
// 创建平台实例 |
||||
$platform = new Windows( |
||||
phpLibs: ['php8embed.lib', 'php8ts.lib'], |
||||
isZts: true |
||||
); |
||||
|
||||
// 创建编译器后端 |
||||
$compiler = new Msvc($platform); |
||||
|
||||
// 构建编译命令 |
||||
$compileCmd = $compiler->buildCompileCommand( |
||||
'source.cpp', |
||||
'source.obj', |
||||
[ |
||||
'optimize' => 2, |
||||
'cpp_std' => 'c++17', |
||||
] |
||||
); |
||||
|
||||
// 构建链接命令 |
||||
$linkCmd = $compiler->buildLinkCommand( |
||||
['source.obj'], |
||||
'output.exe', |
||||
[ |
||||
'debug' => true, |
||||
'no_console' => false, |
||||
] |
||||
); |
||||
``` |
||||
|
||||
### 跨平台支持 |
||||
|
||||
```php |
||||
use PhpAot\Php\Platform\Linux; |
||||
use PhpAot\Php\Platform\Macos; |
||||
use PhpAot\Php\Backend\Gcc; |
||||
|
||||
// 自动检测当前平台 |
||||
if ((new Windows())->isCurrent()) { |
||||
$platform = new Windows(); |
||||
$compiler = new Msvc($platform); |
||||
} elseif ((new Linux())->isCurrent()) { |
||||
$platform = new Linux(); |
||||
$compiler = new Gcc($platform); |
||||
} elseif ((new Macos())->isCurrent()) { |
||||
$platform = new Macos(); |
||||
$compiler = new Gcc($platform); // macOS 通常也用 GCC/Clang |
||||
} |
||||
``` |
||||
|
||||
## 优势 |
||||
|
||||
### 1. 关注点分离 |
||||
- **Platform**: 只关心操作系统差异 |
||||
- **Backend**: 只关心编译器差异 |
||||
- 两者独立,可以任意组合 |
||||
|
||||
### 2. 易于扩展 |
||||
- 添加新平台:继承 `PlatformBase` |
||||
- 添加新编译器:继承 `CompilerBackend` |
||||
- 无需修改现有代码 |
||||
|
||||
### 3. 易于测试 |
||||
- 每个类职责单一 |
||||
- 可以独立单元测试 |
||||
- 便于 Mock 和 stub |
||||
|
||||
### 4. 代码复用 |
||||
- 平台相关逻辑集中管理 |
||||
- 编译器相关逻辑集中管理 |
||||
- 避免重复代码 |
||||
|
||||
## 迁移计划 |
||||
|
||||
### 阶段 1:创建抽象层(✅ 已完成) |
||||
- ✅ 创建 Platform 抽象和实现 |
||||
- ✅ PlatformBase.php |
||||
- ✅ Windows.php |
||||
- ✅ Linux.php |
||||
- ✅ Macos.php |
||||
- ✅ PlatformFactory.php |
||||
- ✅ 创建 Backend 抽象和实现 |
||||
- ✅ CompilerBackend.php |
||||
- ✅ Msvc.php |
||||
- ✅ Gcc.php |
||||
- ✅ Clang.php |
||||
- ✅ CompilerFactory.php |
||||
- ✅ 创建使用示例和文档 |
||||
- ✅ example_usage.php |
||||
- ✅ README.md |
||||
|
||||
### 阶段 2:重构 CompilerBase |
||||
- 将平台相关代码迁移到 Platform 类 |
||||
- 将编译器相关代码迁移到 Backend 类 |
||||
- CompilerBase 作为协调者使用这些类 |
||||
|
||||
### 阶段 3:清理和优化 |
||||
- 删除冗余代码 |
||||
- 更新文档 |
||||
- 完善测试 |
||||
|
||||
## 下一步工作 |
||||
|
||||
### ✅ 已完成 |
||||
1. **完成 Backend 实现** |
||||
- ✅ 创建 `Gcc.php` |
||||
- ✅ 创建 `Clang.php` |
||||
- ✅ 创建 `CompilerFactory.php`(自动检测) |
||||
|
||||
2. **完成 Platform 实现** |
||||
- ✅ 创建所有平台实现 |
||||
- ✅ 创建 `PlatformFactory.php`(自动检测) |
||||
|
||||
3. **文档和示例** |
||||
- ✅ 创建详细 README |
||||
- ✅ 创建使用示例 |
||||
|
||||
### 🔄 进行中 |
||||
4. **重构 CompilerBase** |
||||
- ⏳ 使用新的 Platform 和 Backend 类 |
||||
- ⏳ 保持向后兼容 |
||||
- ⏳ 逐步迁移现有代码 |
||||
|
||||
### 📋 待办 |
||||
5. **添加单元测试** |
||||
- ⏳ 测试所有 Platform 实现 |
||||
- ⏳ 测试所有 Backend 实现 |
||||
- ⏳ 测试工厂类 |
||||
- ⏳ 测试集成场景 |
||||
|
||||
6. **文档完善** |
||||
- ⏳ 添加更多使用示例 |
||||
- ⏳ 编写迁移指南 |
||||
- ⏳ 更新 API 文档 |
||||
@ -0,0 +1,78 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* 新架构使用示例 |
||||
* |
||||
* 展示如何使用 Platform 和 Backend 抽象层 |
||||
*/ |
||||
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php'; |
||||
|
||||
use PhpAot\Php\Platform\PlatformFactory; |
||||
use PhpAot\Php\Backend\CompilerFactory; |
||||
|
||||
echo "=== 平台和编译器抽象层示例 ===\n\n"; |
||||
|
||||
// 示例 1:自动检测平台和编译器 |
||||
echo "1. 自动检测平台和编译器:\n"; |
||||
$result = CompilerFactory::autoDetect(); |
||||
$platform = $result['platform']; |
||||
$compiler = $result['compiler']; |
||||
|
||||
echo " 平台: {$platform->getName()}\n"; |
||||
echo " 编译器: {$compiler->getName()}\n"; |
||||
echo " 对象文件扩展名: {$platform->getObjectExtension()}\n"; |
||||
echo " 可执行文件扩展名: {$platform->getExecutableExtension()}\n"; |
||||
echo "\n"; |
||||
|
||||
// 示例 2:构建编译命令 |
||||
echo "2. 构建编译命令:\n"; |
||||
$compileCmd = $compiler->buildCompileCommand( |
||||
'test.cpp', |
||||
'test' . $platform->getObjectExtension(), |
||||
[ |
||||
'optimize' => 2, |
||||
'debug' => false, |
||||
'cpp_std' => 'c++17', |
||||
] |
||||
); |
||||
echo " {$compileCmd}\n"; |
||||
echo "\n"; |
||||
|
||||
// 示例 3:构建链接命令 |
||||
echo "3. 构建链接命令:\n"; |
||||
$linkCmd = $compiler->buildLinkCommand( |
||||
['test' . $platform->getObjectExtension()], |
||||
'output' . $platform->getExecutableExtension(), |
||||
[ |
||||
'debug' => true, |
||||
'no_console' => false, |
||||
] |
||||
); |
||||
echo " {$linkCmd}\n"; |
||||
echo "\n"; |
||||
|
||||
// 示例 4:手动指定平台和编译器 |
||||
echo "4. 手动指定平台和编译器:\n"; |
||||
use PhpAot\Php\Platform\Windows; |
||||
use PhpAot\Php\Backend\Msvc; |
||||
|
||||
$windowsPlatform = new Windows( |
||||
phpLibs: ['php8embed.lib', 'php8ts.lib'], |
||||
isZts: true |
||||
); |
||||
$msvcCompiler = new Msvc($windowsPlatform); |
||||
|
||||
echo " 平台: {$windowsPlatform->getName()}\n"; |
||||
echo " 编译器: {$msvcCompiler->getName()}\n"; |
||||
echo " ZTS: " . ($windowsPlatform->isZts() ? 'Yes' : 'No') . "\n"; |
||||
echo "\n"; |
||||
|
||||
// 示例 5:跨平台路径处理 |
||||
echo "5. 跨平台路径处理:\n"; |
||||
$path = $platform->joinPath('src', 'Php', 'Backend'); |
||||
echo " 组合路径: {$path}\n"; |
||||
echo " 路径分隔符: '{$platform->getPathSeparator()}'\n"; |
||||
echo "\n"; |
||||
|
||||
echo "=== 示例完成 ===\n"; |
||||
@ -0,0 +1,121 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Platform; |
||||
|
||||
/** |
||||
* Linux 平台实现 |
||||
*/ |
||||
class Linux extends PlatformBase |
||||
{ |
||||
public function getName(): string |
||||
{ |
||||
return 'Linux'; |
||||
} |
||||
|
||||
public function isCurrent(): bool |
||||
{ |
||||
return PHP_OS === 'Linux'; |
||||
} |
||||
|
||||
public function getIncludeFlags(array $includePaths): string |
||||
{ |
||||
if (empty($includePaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($includePaths as $path) { |
||||
$flags[] = '-I' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryPathFlags(array $libraryPaths): string |
||||
{ |
||||
if (empty($libraryPaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($libraryPaths as $path) { |
||||
$flags[] = '-L' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryFlags(array $libraries): string |
||||
{ |
||||
if (empty($libraries)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($libraries as $lib) { |
||||
// 移除 lib 前缀和 .a/.so 后缀 |
||||
$libName = basename($lib); |
||||
if (str_starts_with($libName, 'lib')) { |
||||
$libName = substr($libName, 3); |
||||
} |
||||
$libName = preg_replace('/\.(a|so)$/', '', $libName); |
||||
|
||||
$flags[] = '-l' . $libName; |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getObjectExtension(): string |
||||
{ |
||||
return '.o'; |
||||
} |
||||
|
||||
public function getExecutableExtension(): string |
||||
{ |
||||
return ''; |
||||
} |
||||
|
||||
public function getSharedLibraryExtension(): string |
||||
{ |
||||
return '.so'; |
||||
} |
||||
|
||||
public function getPathSeparator(): string |
||||
{ |
||||
return '/'; |
||||
} |
||||
|
||||
/** |
||||
* 获取 RPATH 选项 |
||||
*/ |
||||
public function getRpathOptions(array $paths): string |
||||
{ |
||||
if (empty($paths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$rpaths = []; |
||||
foreach ($paths as $path) { |
||||
$rpaths[] = '-Wl,-rpath,' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $rpaths); |
||||
} |
||||
|
||||
/** |
||||
* 获取 PIC 选项 |
||||
*/ |
||||
public function getPicFlag(): string |
||||
{ |
||||
return '-fPIC'; |
||||
} |
||||
|
||||
/** |
||||
* 获取共享库链接选项 |
||||
*/ |
||||
public function getSharedLinkFlag(): string |
||||
{ |
||||
return '-shared'; |
||||
} |
||||
} |
||||
@ -0,0 +1,129 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Platform; |
||||
|
||||
/** |
||||
* macOS 平台实现 |
||||
*/ |
||||
class Macos extends PlatformBase |
||||
{ |
||||
public function getName(): string |
||||
{ |
||||
return 'macOS'; |
||||
} |
||||
|
||||
public function isCurrent(): bool |
||||
{ |
||||
return strtoupper(substr(PHP_OS, 0, 6)) === 'DARWIN'; |
||||
} |
||||
|
||||
public function getIncludeFlags(array $includePaths): string |
||||
{ |
||||
if (empty($includePaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($includePaths as $path) { |
||||
$flags[] = '-I' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryPathFlags(array $libraryPaths): string |
||||
{ |
||||
if (empty($libraryPaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($libraryPaths as $path) { |
||||
$flags[] = '-L' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryFlags(array $libraries): string |
||||
{ |
||||
if (empty($libraries)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($libraries as $lib) { |
||||
// 移除 lib 前缀和 .a/.dylib 后缀 |
||||
$libName = basename($lib); |
||||
if (str_starts_with($libName, 'lib')) { |
||||
$libName = substr($libName, 3); |
||||
} |
||||
$libName = preg_replace('/\.(a|dylib)$/', '', $libName); |
||||
|
||||
$flags[] = '-l' . $libName; |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getObjectExtension(): string |
||||
{ |
||||
return '.o'; |
||||
} |
||||
|
||||
public function getExecutableExtension(): string |
||||
{ |
||||
return ''; |
||||
} |
||||
|
||||
public function getSharedLibraryExtension(): string |
||||
{ |
||||
return '.dylib'; |
||||
} |
||||
|
||||
public function getPathSeparator(): string |
||||
{ |
||||
return '/'; |
||||
} |
||||
|
||||
/** |
||||
* 获取 RPATH 选项(macOS 需要绝对路径) |
||||
*/ |
||||
public function getRpathOptions(array $paths): string |
||||
{ |
||||
if (empty($paths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$rpaths = []; |
||||
foreach ($paths as $path) { |
||||
$rpaths[] = '-Wl,-rpath,' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $rpaths); |
||||
} |
||||
|
||||
/** |
||||
* 获取 PIC 选项 |
||||
*/ |
||||
public function getPicFlag(): string |
||||
{ |
||||
return '-fPIC'; |
||||
} |
||||
|
||||
/** |
||||
* 获取共享库链接选项 |
||||
*/ |
||||
public function getSharedLinkFlag(): string |
||||
{ |
||||
return '-dynamiclib'; |
||||
} |
||||
|
||||
/** |
||||
* 获取当前安装名称选项 |
||||
*/ |
||||
public function getCurrentInstallNameOption(string $path): string |
||||
{ |
||||
return '-install_name ' . escapeshellarg($path); |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Platform; |
||||
|
||||
/** |
||||
* 平台抽象基类 |
||||
* 定义所有平台必须实现的接口 |
||||
*/ |
||||
abstract class PlatformBase |
||||
{ |
||||
/** |
||||
* 获取平台名称 |
||||
*/ |
||||
abstract public function getName(): string; |
||||
|
||||
/** |
||||
* 判断是否为当前平台 |
||||
*/ |
||||
abstract public function isCurrent(): bool; |
||||
|
||||
/** |
||||
* 获取编译器包含路径参数 |
||||
*/ |
||||
abstract public function getIncludeFlags(array $includePaths): string; |
||||
|
||||
/** |
||||
* 获取链接器库路径参数 |
||||
*/ |
||||
abstract public function getLibraryPathFlags(array $libraryPaths): string; |
||||
|
||||
/** |
||||
* 获取链接库参数 |
||||
*/ |
||||
abstract public function getLibraryFlags(array $libraries): string; |
||||
|
||||
/** |
||||
* 获取文件扩展名 |
||||
*/ |
||||
abstract public function getObjectExtension(): string; |
||||
|
||||
/** |
||||
* 获取可执行文件扩展名 |
||||
*/ |
||||
abstract public function getExecutableExtension(): string; |
||||
|
||||
/** |
||||
* 获取动态库扩展名 |
||||
*/ |
||||
abstract public function getSharedLibraryExtension(): string; |
||||
|
||||
/** |
||||
* 获取路径分隔符 |
||||
*/ |
||||
abstract public function getPathSeparator(): string; |
||||
|
||||
/** |
||||
* 规范化路径 |
||||
*/ |
||||
public function normalizePath(string $path): string |
||||
{ |
||||
return str_replace('/', $this->getPathSeparator(), $path); |
||||
} |
||||
|
||||
/** |
||||
* 组合路径 |
||||
*/ |
||||
public function joinPath(string ...$parts): string |
||||
{ |
||||
return implode($this->getPathSeparator(), $parts); |
||||
} |
||||
} |
||||
@ -0,0 +1,58 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Platform; |
||||
|
||||
/** |
||||
* 平台工厂类 |
||||
* 自动检测并创建当前平台的实例 |
||||
*/ |
||||
class PlatformFactory |
||||
{ |
||||
/** |
||||
* 创建当前平台的实例 |
||||
*/ |
||||
public static function create(): PlatformBase |
||||
{ |
||||
if ((new Windows())->isCurrent()) { |
||||
return new Windows(); |
||||
} elseif ((new Linux())->isCurrent()) { |
||||
return new Linux(); |
||||
} elseif ((new Macos())->isCurrent()) { |
||||
return new Macos(); |
||||
} else { |
||||
throw new \RuntimeException("Unsupported platform: " . PHP_OS); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取当前平台名称 |
||||
*/ |
||||
public static function getCurrentPlatformName(): string |
||||
{ |
||||
return self::create()->getName(); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否为 Windows 平台 |
||||
*/ |
||||
public static function isWindows(): bool |
||||
{ |
||||
return (new Windows())->isCurrent(); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否为 Linux 平台 |
||||
*/ |
||||
public static function isLinux(): bool |
||||
{ |
||||
return (new Linux())->isCurrent(); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否为 macOS 平台 |
||||
*/ |
||||
public static function isMacos(): bool |
||||
{ |
||||
return (new Macos())->isCurrent(); |
||||
} |
||||
} |
||||
@ -0,0 +1,145 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Platform; |
||||
|
||||
/** |
||||
* Windows 平台实现 |
||||
*/ |
||||
class Windows extends PlatformBase |
||||
{ |
||||
/** |
||||
* PHP 库文件信息 |
||||
*/ |
||||
private array $phpLibs = []; |
||||
|
||||
/** |
||||
* 是否为 ZTS 模式 |
||||
*/ |
||||
private bool $isZts = false; |
||||
|
||||
public function __construct(array $phpLibs = [], bool $isZts = false) |
||||
{ |
||||
$this->phpLibs = $phpLibs; |
||||
$this->isZts = $isZts; |
||||
} |
||||
|
||||
public function getName(): string |
||||
{ |
||||
return 'Windows'; |
||||
} |
||||
|
||||
public function isCurrent(): bool |
||||
{ |
||||
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; |
||||
} |
||||
|
||||
public function getIncludeFlags(array $includePaths): string |
||||
{ |
||||
if (empty($includePaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($includePaths as $path) { |
||||
$flags[] = '/I "' . $path . '"'; |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryPathFlags(array $libraryPaths): string |
||||
{ |
||||
if (empty($libraryPaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($libraryPaths as $path) { |
||||
$flags[] = '/LIBPATH:"' . $path . '"'; |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryFlags(array $libraries): string |
||||
{ |
||||
if (empty($libraries)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($libraries as $lib) { |
||||
$flags[] = '"' . $lib . '"'; |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getObjectExtension(): string |
||||
{ |
||||
return '.obj'; |
||||
} |
||||
|
||||
public function getExecutableExtension(): string |
||||
{ |
||||
return '.exe'; |
||||
} |
||||
|
||||
public function getSharedLibraryExtension(): string |
||||
{ |
||||
return '.dll'; |
||||
} |
||||
|
||||
public function getPathSeparator(): string |
||||
{ |
||||
return '\\'; |
||||
} |
||||
|
||||
/** |
||||
* 获取 PHP 库文件列表 |
||||
*/ |
||||
public function getPhpLibs(): array |
||||
{ |
||||
return $this->phpLibs; |
||||
} |
||||
|
||||
/** |
||||
* 判断是否为 ZTS 模式 |
||||
*/ |
||||
public function isZts(): bool |
||||
{ |
||||
return $this->isZts; |
||||
} |
||||
|
||||
/** |
||||
* 获取 Windows 子系统选项 |
||||
*/ |
||||
public function getSubsystemOptions(bool $noConsole): string |
||||
{ |
||||
if (!$noConsole) { |
||||
return ''; |
||||
} |
||||
|
||||
return '/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup'; |
||||
} |
||||
|
||||
/** |
||||
* 获取 CRT 库配置 |
||||
*/ |
||||
public function getCrtConfig(): string |
||||
{ |
||||
return '/NODEFAULTLIB:LIBCMT'; |
||||
} |
||||
|
||||
/** |
||||
* 获取调试选项 |
||||
*/ |
||||
public function getDebugOptions(bool $debugInfo): string |
||||
{ |
||||
if (!$debugInfo) { |
||||
return ''; |
||||
} |
||||
|
||||
return '/DEBUG'; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue