fix(backend): 修复编译选项中的安全定义转义和路径处理问题

- 为MSVC和GCC编译器添加unsafe defines的安全转义测试
- 实现formatDefineFlag方法统一处理宏定义转义逻辑
- 添加临时目录管理功能用于测试清理
- 重构共享编译选项构建逻辑到buildSharedCompileFlags方法
- 修复response file参数转义问题解决路径空格问题
- 更新clang-format命令执行时的路径转义
- 改进项目配置文件解析支持多种配置选项
- 添加对YAML配置文件自定义名称和相对路径的支持
- 实现代码格式化功能的可用性检查机制
- 修复默认formatCode选项设置为false的问题
pull/5/head
韩天峰 2 months ago
parent 4632a8bba4
commit 47d11655ef
  1. 30
      phpunit/src/Backend/BackendOptionsTest.php
  2. 105
      phpunit/src/Backend/BackendTest.php
  3. 122
      phpunit/src/CompilerBaseApiTest.php
  4. 11
      src/Php/Backend/CompilerBackend.php
  5. 154
      src/Php/Backend/GccLikeBackend.php
  6. 178
      src/Php/Backend/Msvc.php
  7. 2
      src/Php/CompilerBase.php
  8. 128
      src/Php/Translator.php

@ -121,6 +121,21 @@ class BackendOptionsTest extends TestCase
$this->assertStringContainsString('/DPPROF_ON=1', $options);
}
public function testMsvcCompileOptionsEscapesUnsafeDefines(): void
{
$platform = new Windows();
$compiler = new Msvc($platform);
$options = $compiler->buildCompileOptions([
'enable_profiler' => true,
'prof_output' => 'profile output.log',
'user_defines' => ['APP_NAME="hello world"'],
]);
$this->assertStringContainsString('/D' . escapeshellarg('APP_NAME="hello world"'), $options);
$this->assertStringContainsString('/D' . escapeshellarg('PROF_OUTPUT_FILE="profile output.log"'), $options);
}
/**
* 测试 MSVC 编译选项 - 自定义标志
*/
@ -276,6 +291,21 @@ class BackendOptionsTest extends TestCase
$this->assertStringContainsString('-fPIC', $options);
}
public function testGccCompileOptionsEscapesUnsafeDefines(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$options = $compiler->buildCompileOptions([
'enable_profiler' => true,
'prof_output' => 'profile output.log',
'user_defines' => ['APP_NAME="hello world"'],
]);
$this->assertStringContainsString('-D' . escapeshellarg('APP_NAME="hello world"'), $options);
$this->assertStringContainsString('-D' . escapeshellarg('PROF_OUTPUT_FILE="profile output.log"'), $options);
}
/**
* 测试 GCC 链接选项 - 基本配置
*/

@ -13,6 +13,39 @@ use PhpAot\Php\Backend\CompilerFactory;
class BackendTest extends TestCase
{
private array $temporaryDirectories = [];
protected function tearDown(): void
{
parent::tearDown();
foreach ($this->temporaryDirectories as $dir) {
$this->removeDirectory($dir);
}
$this->temporaryDirectories = [];
}
private function createTemporaryDirectory(string $prefix): string
{
$dir = sys_get_temp_dir() . '/' . $prefix . '_' . uniqid();
mkdir($dir, 0777, true);
$this->temporaryDirectories[] = $dir;
return $dir;
}
private function removeDirectory(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
}
rmdir($dir);
}
/**
* 测试 MSVC 编译器基本信息
*/
@ -103,6 +136,31 @@ class BackendTest extends TestCase
$this->assertStringContainsString('/nologo', $cmd);
}
public function testMsvcBuildCCompileCommandKeepsSharedCompilerOptions(): void
{
$platform = new Windows([], true);
$compiler = new Msvc($platform);
$cmd = $compiler->buildCCompileCommand('misc.c', 'misc.obj', [
'sanitize' => 'address',
'enable_profiler' => true,
'prof_output' => 'app.prof',
'user_defines' => ['FEATURE_X=1'],
'lto' => true,
'is_zts' => true,
]);
$this->assertStringContainsString('/TC', $cmd);
$this->assertStringContainsString('/fsanitize=address', $cmd);
$this->assertStringContainsString('/DPPROF_ON=1', $cmd);
$this->assertStringContainsString('/DPROF_OUTPUT_FILE=', $cmd);
$this->assertStringContainsString('/DFEATURE_X=1', $cmd);
$this->assertStringContainsString('/GL', $cmd);
$this->assertStringContainsString('/DZTS', $cmd);
$this->assertStringNotContainsString('/EHsc', $cmd);
$this->assertStringNotContainsString('/std:', $cmd);
}
/**
* 测试 MSVC 完整链接命令
*/
@ -250,6 +308,32 @@ class BackendTest extends TestCase
$this->assertStringContainsString('-fno-rtti', $cmd);
}
public function testGccBuildCCompileCommandKeepsSharedCompilerOptions(): void
{
$platform = new Linux();
$compiler = new Gcc($platform);
$cmd = $compiler->buildCCompileCommand('misc.c', 'misc.o', [
'sanitize' => 'address',
'enable_profiler' => true,
'prof_output' => 'app.prof',
'user_defines' => ['FEATURE_X=1'],
'lto' => true,
'march' => 'native',
'target_platform' => 'aarch64-linux-gnu',
'build_mode' => 'ext',
]);
$this->assertStringContainsString('-fsanitize=address', $cmd);
$this->assertStringContainsString('-DPPROF_ON=1', $cmd);
$this->assertStringContainsString('-DPROF_OUTPUT_FILE=', $cmd);
$this->assertStringContainsString('-DFEATURE_X=1', $cmd);
$this->assertStringContainsString('-flto', $cmd);
$this->assertStringContainsString('-march=native', $cmd);
$this->assertStringContainsString('--target=aarch64-linux-gnu', $cmd);
$this->assertStringContainsString('-fPIC', $cmd);
}
public function testGccBuildLinkCommandIncludesPlatformPathsOptionsAndLibraries(): void
{
$platform = new Linux();
@ -270,6 +354,27 @@ class BackendTest extends TestCase
$this->assertStringContainsString('-lphp', $cmd);
}
public function testResponseFileArgumentIsEscapedForPathsWithSpaces(): void
{
$platform = new Linux();
$compiler = new Gcc($platform, 'g++');
$dir = $this->createTemporaryDirectory('backend link path');
$target = $dir . '/my app';
$rspFile = $target . '.rsp';
$objectWithSpace = $dir . '/object one.o';
$objectWithoutSpace = $dir . '/object_two.o';
$cmd = $compiler->buildLinkCommand([$objectWithSpace, $objectWithoutSpace], $target);
$this->assertStringContainsString(escapeshellarg('@' . $rspFile), $cmd);
$this->assertStringContainsString('-o ' . escapeshellarg($target), $cmd);
$this->assertFileExists($rspFile);
$lines = file($rspFile, FILE_IGNORE_NEW_LINES);
$this->assertSame('"' . $objectWithSpace . '"', $lines[0]);
$this->assertSame('"' . $objectWithoutSpace . '"', $lines[1]);
}
/**
* 测试 GCC 完整编译选项
*/

@ -12,12 +12,14 @@ class CompilerBaseApiTest extends TestCase
private CompilerTest $compiler;
private \ReflectionClass $ref;
private array $originalArgv;
private string|false $originalPath;
protected function setUp(): void
{
parent::setUp();
global $argv;
$this->originalArgv = $argv ?? [];
$this->originalPath = getenv('PATH');
$this->testDir = sys_get_temp_dir() . '/compiler_api_test_' . uniqid();
mkdir($this->testDir, 0777, true);
$this->compiler = CompilerTest::create($this->testDir);
@ -29,6 +31,11 @@ class CompilerBaseApiTest extends TestCase
parent::tearDown();
global $argv;
$argv = $this->originalArgv;
if ($this->originalPath === false) {
putenv('PATH');
} else {
putenv('PATH=' . $this->originalPath);
}
// Recursively remove the test directory (compiler creates build/ subdir)
$this->removeDirectory($this->testDir);
}
@ -67,17 +74,29 @@ class CompilerBaseApiTest extends TestCase
return $m->invoke($this->compiler, ...$args);
}
private function createProjectFile(string $yaml): string
private function createProjectFile(string $yaml, string $filename = 'project.yml', string $baseDir = ''): string
{
$sourceFile = $this->testDir . '/main.php';
$projectDir = $baseDir === '' ? $this->testDir : $this->testDir . '/' . trim($baseDir, '/');
if (!is_dir($projectDir)) {
mkdir($projectDir, 0777, true);
}
$sourceFile = $projectDir . '/main.php';
file_put_contents($sourceFile, "<?php\nfunction main() {}\n");
$projectFile = $this->testDir . '/project.yml';
$projectFile = $projectDir . '/' . $filename;
file_put_contents($projectFile, $yaml);
return $projectFile;
}
private function createFakeClangFormat(string $binDir, string $logFile): void
{
mkdir($binDir, 0777, true);
file_put_contents($binDir . '/clang-format', "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then\n echo 'clang-format version test'\n exit 0\nfi\npwd > " . escapeshellarg($logFile) . "\nprintf '%s\\n' \"$@\" >> " . escapeshellarg($logFile) . "\n");
chmod($binDir . '/clang-format', 0755);
}
// ========================================================================
// getTypeFromZendType
// ========================================================================
@ -154,6 +173,11 @@ class CompilerBaseApiTest extends TestCase
$this->assertStringStartsWith($this->testDir, $buildDir);
}
public function testFormatCodeDisabledByDefault(): void
{
$this->assertFalse($this->getPropertyValue('formatCode'));
}
public function testGetIncludeDir(): void
{
$includeDir = $this->compiler->getIncludeDir();
@ -163,9 +187,24 @@ class CompilerBaseApiTest extends TestCase
public function testParseProjectYamlLoadsDocumentedCompilerOptions(): void
{
$binDir = $this->testDir . '/bin';
$formatLog = $this->testDir . '/format.log';
$this->createFakeClangFormat($binDir, $formatLog);
putenv('PATH=' . $binDir . ':' . ($this->originalPath ?: ''));
$projectFile = $this->createProjectFile(<<<'YAML'
sources:
- main.php
optimize: 2
job: 8
debug: true
profile: true
no-progress: true
no-console: true
no-literal-strings: true
sanitize: address
target-platform: aarch64-linux-gnu
build-dir: /tmp/project-build
include-paths:
- /opt/mylib/include
- ../shared/headers
@ -173,6 +212,7 @@ defines:
- ENABLE_LOGGING=1
- DEBUG_LEVEL=3
lto: true
format: true
link-libs:
- curl
- ssl
@ -183,11 +223,56 @@ YAML);
$this->invokeMethod('parseProjectYaml', $projectFile);
$this->assertSame(2, $this->getPropertyValue('optimizeLevel'));
$this->assertSame(8, $this->getPropertyValue('maxJob'));
$this->assertTrue($this->getPropertyValue('debug'));
$this->assertTrue($this->getPropertyValue('enableProfiler'));
$this->assertTrue($this->getPropertyValue('noProgress'));
$this->assertTrue($this->getPropertyValue('noConsole'));
$this->assertTrue($this->getPropertyValue('noLiteralStrings'));
$this->assertSame('address', $this->getPropertyValue('sanitize'));
$this->assertSame('aarch64-linux-gnu', $this->getPropertyValue('targetPlatform'));
$this->assertSame(['/opt/mylib/include', '../shared/headers'], $this->compiler->getUserIncludePaths());
$this->assertSame(['ENABLE_LOGGING=1', 'DEBUG_LEVEL=3'], $this->compiler->getUserDefines());
$this->assertTrue($this->compiler->isLtoEnabled());
$this->assertSame(['curl', 'ssl'], $this->compiler->getLinkLibs());
$this->assertSame(['/usr/local/lib', '/opt/custom/lib'], $this->compiler->getLinkPaths());
$this->assertSame('/tmp/project-build', $this->compiler->getBuildDir());
$this->assertTrue($this->getPropertyValue('formatCode'));
}
public function testParseProjectYamlSupportsCustomFilenameAndRelativeBuildDir(): void
{
$projectFile = $this->createProjectFile(<<<'YAML'
sources:
- main.php
build-dir: build/output
YAML, 'myproject.yml', 'nested/config');
$this->invokeMethod('parseProjectYaml', $projectFile);
$this->assertSame(
realpath($this->testDir . '/nested/config/build/output'),
$this->compiler->getBuildDir()
);
}
public function testParseProjectYamlSupportsCliStyleModeAndOutputAliases(): void
{
$projectFile = $this->createProjectFile(<<<'YAML'
sources:
- main.php
mode: ext
output: out/custom-ext
dry: true
YAML, 'custom-name.yml', 'yaml-alias');
$this->invokeMethod('parseProjectYaml', $projectFile);
$this->assertSame(CompilerBase::BUILD_MODE_EXT, $this->getPropertyValue('buildMode'));
$this->assertTrue($this->getPropertyValue('dryRun'));
$this->assertSame('custom_ext', $this->getPropertyValue('targetName'));
$this->assertSame('out', $this->getPropertyValue('outputDir'));
}
public function testApplyCommandLineArgumentsDoesNotClearYamlRepeatableOptionsWhenCliAbsent(): void
@ -216,6 +301,37 @@ YAML);
$this->assertSame(['/yaml/lib'], $this->compiler->getLinkPaths());
}
public function testFormatCppCodeEscapesPathsWithSpaces(): void
{
$spaceDir = sys_get_temp_dir() . '/compiler api format ' . uniqid();
mkdir($spaceDir, 0777, true);
$binDir = $spaceDir . '/bin';
$logFile = $spaceDir . '/format.log';
$sourceFile = $spaceDir . '/hello world.cc';
file_put_contents($sourceFile, "int main() { return 0; }\n");
$this->createFakeClangFormat($binDir, $logFile);
putenv('PATH=' . $binDir . ':' . ($this->originalPath ?: ''));
$compiler = CompilerTest::create($spaceDir);
$ref = new \ReflectionClass($compiler);
$formatProp = $ref->getProperty('formatCode');
$formatProp->setAccessible(true);
$formatProp->setValue($compiler, true);
$method = $ref->getMethod('formatCppCode');
$method->setAccessible(true);
$method->invoke($compiler, $sourceFile);
$this->assertFileExists($logFile);
$lines = file($logFile, FILE_IGNORE_NEW_LINES);
$this->assertSame($spaceDir, $lines[0]);
$this->assertSame('-i', $lines[1]);
$this->assertSame($sourceFile, $lines[2]);
$this->removeDirectory($spaceDir);
}
// ========================================================================
// isWindows / isLinux / isMacos
// ========================================================================

@ -158,6 +158,15 @@ abstract class CompilerBackend
return $this->platform->getLibraryFlags($libraries);
}
protected function formatDefineFlag(string $define, string $prefix): string
{
if (preg_match('/^[A-Za-z_][A-Za-z0-9_]*(=(?:[A-Za-z0-9_.,:+\/@%-]+))?$/', $define) === 1) {
return $prefix . $define;
}
return $prefix . escapeshellarg($define);
}
/**
* 将目标文件列表写入 Response File,避免命令行参数过长超出 OS 限制(Windows 8191 字符)
*
@ -178,7 +187,7 @@ abstract class CompilerBackend
$lines[] = $file;
}
file_put_contents($rspFile, implode("\n", $lines));
return '@' . $rspFile;
return escapeshellarg('@' . $rspFile);
}
/**

@ -59,6 +59,63 @@ abstract class GccLikeBackend extends CompilerBackend
return '';
}
/** 构建 GCC/Clang 共享编译选项,C 和 C++ 编译路径都复用这里 */
protected function buildSharedCompileFlags(array $config, bool $includeCppStd = false): string
{
$cmd = '';
if (!empty($config['sanitize'])) {
$cmd .= ' ' . $this->formatSanitizerFlag($config['sanitize']);
}
if (!empty($config['debug'])) {
$cmd .= ' -O0 -g';
} else {
$optimizeLevel = $config['optimize'] ?? 2;
$cmd .= ' -O' . $optimizeLevel;
}
$cmd .= ' -Wall';
if ($includeCppStd && !empty($config['cpp_std'])) {
$cmd .= ' -std=' . $config['cpp_std'];
}
if (!empty($config['march'])) {
$cmd .= ' -march=' . $config['march'];
}
if (!empty($config['target_platform'])) {
$cmd .= ' --target=' . $config['target_platform'];
}
$cmd .= $this->getPICFlag($config);
if (!empty($config['enable_profiler'])) {
$cmd .= ' ' . $this->formatDefineFlag('PPROF_ON=1', '-D');
if (!empty($config['prof_output'])) {
$profOutput = addcslashes($config['prof_output'], "\\\"");
$cmd .= ' ' . $this->formatDefineFlag('PROF_OUTPUT_FILE="' . $profOutput . '"', '-D');
}
}
if ($includeCppStd && !empty($config['cxxflags'])) {
$cmd .= ' ' . $config['cxxflags'];
}
if (!empty($config['user_defines'])) {
foreach ($config['user_defines'] as $define) {
$cmd .= ' ' . $this->formatDefineFlag($define, '-D');
}
}
if (!empty($config['lto'])) {
$cmd .= ' -flto';
}
return $cmd;
}
/** 获取平台特定的链接选项 */
protected function getPlatformLinkFlags(array $config): string
{
@ -116,7 +173,7 @@ abstract class GccLikeBackend extends CompilerBackend
}
foreach ($defines as $define) {
$cmd .= ' -D' . $define;
$cmd .= ' ' . $this->formatDefineFlag($define, '-D');
}
if (!empty($flags)) {
@ -181,15 +238,7 @@ abstract class GccLikeBackend extends CompilerBackend
if (!empty($options['include_paths'])) {
$cmd .= ' ' . $this->formatIncludePaths($options['include_paths']);
}
$optimizeLevel = $options['optimize'] ?? 0;
if (!empty($options['debug'])) {
$cmd .= ' -O0 -g';
} else {
$cmd .= ' -O' . $optimizeLevel;
}
$cmd .= ' -Wall';
$cmd .= $this->buildSharedCompileFlags($options, false);
return $cmd;
}
@ -239,58 +288,8 @@ abstract class GccLikeBackend extends CompilerBackend
public function buildCompileOptions(array $config = []): string
{
$cmd = '';
$cmd .= $this->getCompilerPrefixFlags();
if (!empty($config['sanitize'])) {
$cmd .= ' ' . $this->formatSanitizerFlag($config['sanitize']);
}
if (!empty($config['debug'])) {
$cmd .= ' -O0 -g';
} else {
$optimizeLevel = $config['optimize'] ?? 2;
$cmd .= ' -O' . $optimizeLevel;
}
$cmd .= ' -Wall';
if (!empty($config['cpp_std'])) {
$cmd .= ' -std=' . $config['cpp_std'];
}
if (!empty($config['march'])) {
$cmd .= ' -march=' . $config['march'];
}
if (!empty($config['target_platform'])) {
$cmd .= ' --target=' . $config['target_platform'];
}
$cmd .= $this->getPICFlag($config);
if (!empty($config['enable_profiler'])) {
$cmd .= ' -DPPROF_ON=1';
if (!empty($config['prof_output'])) {
$cmd .= ' -DPROF_OUTPUT_FILE=\'"' . $config['prof_output'] . '"\'';
}
}
if (!empty($config['cxxflags'])) {
$cmd .= ' ' . $config['cxxflags'];
}
if (!empty($config['user_defines'])) {
foreach ($config['user_defines'] as $define) {
$cmd .= ' -D' . $define;
}
}
if (!empty($config['lto'])) {
$cmd .= ' -flto';
}
$cmd = $this->getCompilerPrefixFlags();
$cmd .= $this->buildSharedCompileFlags($config, true);
return $cmd;
}
@ -317,35 +316,8 @@ abstract class GccLikeBackend extends CompilerBackend
public function buildFullCompileOptions(array $options = []): string
{
$cmd = '';
$cmd .= $this->getCompilerPrefixFlags();
if (!empty($options['debug'])) {
$cmd .= ' -O0 -g';
} else {
$optimizeLevel = $options['optimize'] ?? 2;
$cmd .= ' -O' . $optimizeLevel;
}
$cmd .= ' -Wall';
if (!empty($options['cpp_std'])) {
$cmd .= ' -std=' . $options['cpp_std'];
}
if (!empty($options['march'])) {
$cmd .= ' -march=' . $options['march'];
}
if (!empty($options['sanitize'])) {
$cmd .= ' ' . $this->formatSanitizerFlag($options['sanitize']);
}
if (!empty($options['pic'])) {
$cmd .= ' -fPIC';
}
$cmd = $this->getCompilerPrefixFlags();
$cmd .= $this->buildSharedCompileFlags($options, true);
return $cmd;
}

@ -34,6 +34,75 @@ class Msvc extends CompilerBackend
return $this->linkerCommand;
}
private function buildCommonCompileFlags(array $config, bool $includeCppOptions = true): string
{
$cmd = '';
$cmd .= ' /DZEND_WIN32 /DPHP_WIN32 /DZEND_DEBUG=0';
if (!empty($config['is_zts'])) {
$cmd .= ' /DZTS';
}
if (!empty($config['sanitize'])) {
if ($config['sanitize'] === 'address' || $config['sanitize'] === 'addr') {
$cmd .= ' /fsanitize=address';
}
}
if (!empty($config['debug'])) {
$cmd .= ' /Od /Zi';
} else {
$optimizeLevel = $config['optimize'] ?? 2;
$optMap = [0 => '/Od', 1 => '/O1', 2 => '/O2', 3 => '/Ox'];
$cmd .= ' ' . ($optMap[$optimizeLevel] ?? '/O2');
}
$cmd .= ' /W3';
if (!empty($config['suppressed_warnings'])) {
foreach ($config['suppressed_warnings'] as $code => $description) {
$code = is_int($code) && $code < 100 ? $description : $code;
$cmd .= " /wd{$code}";
}
}
if (!empty($config['enable_profiler'])) {
$cmd .= ' ' . $this->formatDefineFlag('PPROF_ON=1', '/D');
if (!empty($config['prof_output'])) {
$profOutput = addcslashes($config['prof_output'], "\\\"");
$cmd .= ' ' . $this->formatDefineFlag('PROF_OUTPUT_FILE="' . $profOutput . '"', '/D');
}
}
if (!empty($config['user_defines'])) {
foreach ($config['user_defines'] as $define) {
$cmd .= ' ' . $this->formatDefineFlag($define, '/D');
}
}
if (!empty($config['lto'])) {
$cmd .= ' /GL';
}
if ($includeCppOptions) {
$cmd .= ' /EHsc';
if (!empty($config['cpp_std'])) {
$cmd .= ' /std:' . $config['cpp_std'];
}
$cmd .= ' /MD';
if (!empty($config['cxxflags'])) {
$cmd .= ' ' . $config['cxxflags'];
}
}
$cmd .= ' /nologo';
return $cmd;
}
public function compileFile(
string $sourceFile,
string $outputFile,
@ -52,7 +121,7 @@ class Msvc extends CompilerBackend
// 添加宏定义
foreach ($defines as $define) {
$cmd .= ' /D' . $define;
$cmd .= ' ' . $this->formatDefineFlag($define, '/D');
}
// 添加额外标志
@ -129,29 +198,7 @@ class Msvc extends CompilerBackend
}
// 平台宏定义
$cmd .= ' /DZEND_WIN32 /DPHP_WIN32 /DZEND_DEBUG=0';
// ZTS 支持
if ($this->platform instanceof Windows && $this->platform->isZts()) {
$cmd .= ' /DZTS';
}
// 优化级别(C 文件通常使用较低的优化)
$optimizeLevel = $options['optimize'] ?? 0;
$optMap = [0 => '/Od', 1 => '/O1', 2 => '/O2', 3 => '/Ox'];
$cmd .= ' ' . ($optMap[$optimizeLevel] ?? '/O2');
// 警告级别
$cmd .= ' /W3';
// 禁用常见警告
$suppressedWarnings = $options['suppressed_warnings'] ?? ['4244', '4146'];
foreach ($suppressedWarnings as $code) {
$cmd .= " /wd{$code}";
}
// nologo
$cmd .= ' /nologo';
$cmd .= $this->buildCommonCompileFlags($options, false);
// 注意:C 文件不使用 /EHsc, /std:c++17, /MD 等 C++ 特定选项
@ -220,7 +267,7 @@ class Msvc extends CompilerBackend
// 宏定义
foreach ($defines as $define) {
$cmd .= ' /D' . $define;
$cmd .= ' ' . $this->formatDefineFlag($define, '/D');
}
// 编译选项
@ -322,86 +369,7 @@ class Msvc extends CompilerBackend
*/
public function buildCompileOptions(array $config = []): string
{
$cmd = '';
// 平台宏定义
$cmd .= ' /DZEND_WIN32 /DPHP_WIN32 /DZEND_DEBUG=0';
// ZTS
if (!empty($config['is_zts'])) {
$cmd .= ' /DZTS';
}
// Sanitizer
if (!empty($config['sanitize'])) {
if ($config['sanitize'] === 'address' || $config['sanitize'] === 'addr') {
$cmd .= ' /fsanitize=address';
}
}
// 优化和调试
if (!empty($config['debug'])) {
$cmd .= ' /Od /Zi';
} else {
$optimizeLevel = $config['optimize'] ?? 2;
$optMap = [0 => '/Od', 1 => '/O1', 2 => '/O2', 3 => '/Ox'];
$cmd .= ' ' . ($optMap[$optimizeLevel] ?? '/O2');
}
// 警告
$cmd .= ' /W3';
// 禁用常见警告(只使用键,即警告代码)
if (!empty($config['suppressed_warnings'])) {
foreach ($config['suppressed_warnings'] as $code => $description) {
$code = is_int($code) && $code < 100 ? $description : $code;
$cmd .= " /wd{$code}";
}
}
// C++ 选项
$cmd .= ' /EHsc';
if (!empty($config['cpp_std'])) {
$cmd .= ' /std:' . $config['cpp_std'];
}
// 扩展模块选项
if (!empty($config['build_mode']) && $config['build_mode'] === 'ext') {
// MSVC 不需要 -fPIC,默认就是位置无关代码
}
// CRT
$cmd .= ' /MD';
// nologo
$cmd .= ' /nologo';
// 性能分析宏
if (!empty($config['enable_profiler'])) {
$cmd .= ' /DPPROF_ON=1';
if (!empty($config['prof_output'])) {
$cmd .= ' /DPROF_OUTPUT_FILE=\'"' . $config['prof_output'] . '"\'';
}
}
// 用户自定义编译标志
if (!empty($config['cxxflags'])) {
$cmd .= ' ' . $config['cxxflags'];
}
// 用户自定义预处理器宏
if (!empty($config['user_defines'])) {
foreach ($config['user_defines'] as $define) {
$cmd .= ' /D' . $define;
}
}
// LTO(全程序优化 /GL + 链接时代码生成 /LTCG)
if (!empty($config['lto'])) {
$cmd .= ' /GL';
}
return $cmd;
return $this->buildCommonCompileFlags($config, true);
}
/**

@ -251,7 +251,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected array $linkPaths = []; // --link-path / -L: user-specified library search paths
protected int $floatPrecision = 17;
protected bool $debug = false;
protected bool $formatCode = true; // --format: enable clang-format (disabled by default)
protected bool $formatCode = false; // --format: enable clang-format (disabled by default)
protected bool $printBacktraceOnError = true;
protected bool $noLiteralStrings = false;
protected bool $noConsole = false; // Windows: hide console window

@ -206,11 +206,11 @@ class Translator extends Preprocessor
$cmd = $argv[0];
$climate->bold('USAGE:');
$climate->tab()->out($cmd . ' <file/dir/project.yml> [options]');
$climate->tab()->out($cmd . ' <file/dir/config.yml> [options]');
$climate->br();
$climate->bold('ARGUMENTS:');
$climate->tab()->out('<file> Input PHP file/directory/project.yml to compile');
$climate->tab()->out('<file> Input PHP file/directory/YAML config to compile');
$climate->br();
$climate->bold('EXAMPLES:');
@ -354,12 +354,7 @@ class Translator extends Preprocessor
// clang-format 代码格式化(默认关闭,需显式 --format 开启)
if ($this->climate->arguments->defined('format')) {
$clangFormatVersion = shell_exec('clang-format --version');
if (!empty($clangFormatVersion)) {
$this->formatCode = true;
} else {
$this->climate->warning('--format requested but clang-format not found, skipping formatting');
}
$this->enableCodeFormattingIfAvailable('--format');
}
// 用户自定义链接库(直接从 argv 解析以支持多值)
@ -450,13 +445,24 @@ class Translator extends Preprocessor
$this->climate->bold()->out(self::APP_NAME . ' v' . self::VERSION);
}
private function enableCodeFormattingIfAvailable(string $source): void
{
$clangFormatVersion = shell_exec('clang-format --version');
if (!empty($clangFormatVersion)) {
$this->formatCode = true;
return;
}
$this->climate->warning($source . ' requested but clang-format not found, skipping formatting');
}
protected function formatCppCode(string $file): void
{
if (!$this->formatCode) {
return;
}
$cmd = 'cd ' . $this->rootPath . ' && clang-format -i ' . $file;
$cmd = 'cd ' . escapeshellarg($this->rootPath) . ' && clang-format -i ' . escapeshellarg($file);
$this->climate->info('format: ' . $this->getRelativePath($file));
$this->climate->comment($cmd);
shell_exec($cmd);
@ -1513,7 +1519,14 @@ CODE;
'include_paths' => $this->getIncludePaths(),
'optimize' => 0,
'debug' => $this->debug,
'sanitize' => $this->sanitize,
'is_zts' => $this->isPhpZts,
'enable_profiler' => $this->enableProfiler,
'prof_output' => $this->targetName . '.prof',
'user_defines' => $this->userDefines,
'lto' => $this->enableLto,
'march' => $this->march,
'target_platform' => $this->targetPlatform,
'suppressed_warnings' => ['4244', '4146'],
];
}
@ -1927,17 +1940,33 @@ CODE;
}
protected function getAbsolutePath(string $path, string $projectDir): string
{
$absPath = $this->resolvePath($path, $projectDir, 'Source path');
return realpath($absPath);
}
protected function resolvePath(string $path, string $baseDir, string $label = 'Path'): string
{
$path = trim($path);
if ($path === '') {
$this->error('Source path must not be empty');
$this->error($label . ' must not be empty');
}
if ($path[0] !== '/') {
$absPath = $projectDir . '/' . $path;
} else {
$absPath = $path;
if ($this->isAbsolutePath($path)) {
return $path;
}
return realpath($absPath);
return $baseDir . '/' . $path;
}
protected function isAbsolutePath(string $path): bool
{
return $path !== ''
&& (
$path[0] === '/'
|| $path[0] === '\\'
|| preg_match('/^[A-Za-z]:[\\\\\\/]/', $path) === 1
);
}
protected function parseProjectYaml(string $path): array
@ -1969,6 +1998,43 @@ CODE;
$list = $this->getFilesFromDir($projectDir);
}
if (array_key_exists('optimize', $cfg)) {
$this->optimizeLevel = (int) $cfg['optimize'];
}
if (array_key_exists('job', $cfg)) {
$this->maxJob = (int) $cfg['job'];
}
if (!empty($cfg['debug'])) {
$this->debug = true;
}
if (!empty($cfg['no-literal-strings'])) {
$this->noLiteralStrings = true;
}
if (!empty($cfg['profile'])) {
if (!$this->isLinux()) {
$this->climate->error('`profile` in YAML is only supported on Linux (requires gperftools)');
exit(1);
}
$this->enableProfiler = true;
}
if (!empty($cfg['no-progress'])) {
$this->noProgress = true;
}
if (!empty($cfg['no-console'])) {
$this->noConsole = true;
}
$sanitize = $cfg['sanitize'] ?? null;
if (!empty($sanitize)) {
$this->sanitize = (string) $sanitize;
}
// 读取 cxx-flags
$cxxFlags = $cfg['cxx-flags'] ?? null;
if (!empty($cxxFlags)) {
@ -1991,6 +2057,22 @@ CODE;
$this->march = $march;
}
// 读取 target-platform
$targetPlatform = $cfg['target-platform'] ?? null;
if (!empty($targetPlatform)) {
$this->targetPlatform = (string) $targetPlatform;
}
// 读取 build-dir
$buildDir = $cfg['build-dir'] ?? null;
if (!empty($buildDir)) {
$this->setBuildDir($this->resolvePath((string) $buildDir, $projectDir, 'Build path'));
}
if (!empty($cfg['dry'])) {
$this->dryRun = true;
}
// 读取 ld-flags
$ldflags = $cfg['ld-flags'] ?? null;
if (!empty($ldflags)) {
@ -2022,6 +2104,11 @@ CODE;
$this->enableLto = true;
}
// 读取 format
if (!empty($cfg['format'])) {
$this->enableCodeFormattingIfAvailable('YAML format');
}
// 读取 link-libs
$linkLibs = $cfg['link-libs'] ?? null;
if (!empty($linkLibs) && is_array($linkLibs)) {
@ -2038,9 +2125,10 @@ CODE;
}
}
// 读取 name
if (!empty($cfg['name'])) {
$this->setTargetName($cfg['name']);
// 读取 output/name
$output = $cfg['output'] ?? $cfg['name'] ?? null;
if (!empty($output)) {
$this->setTargetName((string) $output);
}
// 读取 cpp-compiler
@ -2049,8 +2137,8 @@ CODE;
$this->setCppCompiler($cppCompiler);
}
// 读取 type/build-mode(支持中横线和下划线
$buildMode = $cfg['build-mode'] ?? $cfg['type'] ?? null;
// 读取 mode/type/build-mode(支持 CLI/YAML 两套命名
$buildMode = $cfg['mode'] ?? $cfg['build-mode'] ?? $cfg['type'] ?? null;
if (!empty($buildMode)) {
// 映射常见的类型名称到内部 buildMode
$modeMap = [

Loading…
Cancel
Save