fix(php): 修复项目配置中的路径解析和文件过滤问题

- 修复 YAML 配置中 include-paths、link-paths 和 output 的相对路径解析
- 实现忽略文件过滤功能,过滤被 ignore 规则匹配的源文件
- 修复命令行参数 output 对目标名称设置的影响
- 添加单元测试验证相对路径选项相对于 YAML 目录的解析
- 添加测试验证 CLI 参数覆盖 YAML 配置的行为
- 添加测试验证 YAML 配置中忽略文件的过滤逻辑
pull/5/head
韩天峰 2 months ago
parent 5bbb50b031
commit 1ad6b54a49
  1. 78
      phpunit/src/CompilerBaseApiTest.php
  2. 56
      src/Php/Translator.php

@ -232,7 +232,7 @@ YAML);
$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(['/opt/mylib/include', dirname($projectFile) . '/../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());
@ -272,7 +272,58 @@ YAML, 'custom-name.yml', 'yaml-alias');
$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'));
$this->assertSame(dirname($projectFile) . '/out', $this->getPropertyValue('outputDir'));
}
public function testParseProjectYamlResolvesRelativePathOptionsAgainstYamlDirectory(): void
{
$projectFile = $this->createProjectFile(<<<'YAML'
sources:
- main.php
include-paths:
- includes
link-paths:
- libs
output: bin/my-app
YAML, 'myproject.yml', 'nested/config');
$this->invokeMethod('parseProjectYaml', $projectFile);
$projectDir = dirname($projectFile);
$this->assertSame([$projectDir . '/includes'], $this->compiler->getUserIncludePaths());
$this->assertSame([$projectDir . '/libs'], $this->compiler->getLinkPaths());
$this->assertSame($projectDir . '/bin', $this->getPropertyValue('outputDir'));
$this->assertSame('my_app', $this->getPropertyValue('targetName'));
}
public function testCliOutputOverridesYamlOutputOnlyWhenCommandLineArgumentsAreApplied(): void
{
global $argv;
$argv = ['compiler.php', '--output', 'cli/out-file'];
$compiler = CompilerTest::create($this->testDir);
$ref = new \ReflectionClass($compiler);
$parseMethod = $ref->getMethod('parseProjectYaml');
$parseMethod->setAccessible(true);
$applyMethod = $ref->getMethod('applyCommandLineArguments');
$applyMethod->setAccessible(true);
$targetProp = $ref->getProperty('targetName');
$targetProp->setAccessible(true);
$outputProp = $ref->getProperty('outputDir');
$outputProp->setAccessible(true);
$projectFile = $this->createProjectFile(<<<'YAML'
sources:
- main.php
output: yaml/out-file
YAML, 'myproject.yml', 'cli-output');
$parseMethod->invoke($compiler, $projectFile);
$this->assertSame(dirname($projectFile) . '/yaml', $outputProp->getValue($compiler));
$this->assertSame('out_file', $targetProp->getValue($compiler));
$applyMethod->invoke($compiler);
$this->assertSame('cli', $outputProp->getValue($compiler));
$this->assertSame('out_file', $targetProp->getValue($compiler));
}
public function testApplyCommandLineArgumentsDoesNotClearYamlRepeatableOptionsWhenCliAbsent(): void
@ -301,6 +352,29 @@ YAML);
$this->assertSame(['/yaml/lib'], $this->compiler->getLinkPaths());
}
public function testParseProjectYamlFiltersIgnoredFilesFromReturnedSources(): void
{
$projectFile = $this->createProjectFile(<<<'YAML'
sources:
- .
ignore:
- ignored.php
- skipped
YAML);
$projectDir = dirname($projectFile);
mkdir($projectDir . '/skipped', 0777, true);
file_put_contents($projectDir . '/ignored.php', "<?php\nfunction ignored() {}\n");
file_put_contents($projectDir . '/skipped/nested.php', "<?php\nfunction skipped() {}\n");
file_put_contents($projectDir . '/kept.php', "<?php\nfunction kept() {}\n");
$files = $this->invokeMethod('parseProjectYaml', $projectFile);
$this->assertContains(realpath($projectDir . '/main.php'), $files);
$this->assertContains(realpath($projectDir . '/kept.php'), $files);
$this->assertNotContains(realpath($projectDir . '/ignored.php'), $files);
$this->assertNotContains(realpath($projectDir . '/skipped/nested.php'), $files);
}
public function testCCompileCommandOptionsKeepCommonUserConfiguration(): void
{
$this->setPropertyValue('userIncludePaths', ['/user/include']);

@ -325,6 +325,11 @@ class Translator extends Preprocessor
$this->targetPlatform = $this->climate->arguments->get('target-platform');
}
// 输出文件名/路径
if ($this->climate->arguments->defined('output')) {
$this->setTargetName($this->climate->arguments->get('output'));
}
// 构建目录
if ($this->climate->arguments->defined('build-dir')) {
$buildDir = $this->climate->arguments->get('build-dir');
@ -546,9 +551,6 @@ class Translator extends Preprocessor
public function setTargetName(string $name): void
{
if ($this->climate->arguments->defined('output')) {
$name = $this->climate->arguments->get('output');
}
// 如果指定了路径(包含目录分隔符),提取目录和文件名
if (str_contains($name, '/') || str_contains($name, '\\')) {
$this->outputDir = dirname($name);
@ -609,7 +611,7 @@ class Translator extends Preprocessor
// 在所有配置加载完成后,应用命令行参数(确保优先级最高)
$this->applyCommandLineArguments();
return $list;
return $this->filterIgnoredFiles($list);
}
public function prepare(string $path): array
@ -627,20 +629,7 @@ class Translator extends Preprocessor
}
$files = $this->getFiles($path);
// 应用 ignorePaths 过滤
if (!empty($this->ignorePaths)) {
$files = array_filter($files, function ($file) {
foreach ($this->ignorePaths as $ignorePath) {
if ($file === $ignorePath) {
return false;
}
if (is_dir($ignorePath) && str_starts_with($file, $ignorePath . DIRECTORY_SEPARATOR)) {
return false;
}
}
return true;
});
}
$files = $this->filterIgnoredFiles($files);
// 分析 PHP 文件,预处理
foreach ($files as $k => $file) {
if (FileScanner::isPhpFile($file)) {
@ -659,6 +648,29 @@ class Translator extends Preprocessor
return $files;
}
protected function shouldIgnoreFile(string $file): bool
{
foreach ($this->ignorePaths as $ignorePath) {
if ($file === $ignorePath) {
return true;
}
if (is_dir($ignorePath) && str_starts_with($file, rtrim($ignorePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR)) {
return true;
}
}
return false;
}
protected function filterIgnoredFiles(array $files): array
{
if (empty($this->ignorePaths)) {
return $files;
}
return array_values(array_filter($files, fn(string $file): bool => !$this->shouldIgnoreFile($file)));
}
public function convert(array $files): array
{
$sourceFiles = [];
@ -2071,7 +2083,7 @@ CODE;
$includePaths = $cfg['include-paths'] ?? null;
if (!empty($includePaths) && is_array($includePaths)) {
foreach ($includePaths as $includePath) {
$this->userIncludePaths[] = (string) $includePath;
$this->userIncludePaths[] = $this->resolvePath((string) $includePath, $projectDir, 'Include path');
}
}
@ -2105,14 +2117,14 @@ CODE;
$linkPaths = $cfg['link-paths'] ?? null;
if (!empty($linkPaths) && is_array($linkPaths)) {
foreach ($linkPaths as $linkPath) {
$this->linkPaths[] = (string)$linkPath;
$this->linkPaths[] = $this->resolvePath((string) $linkPath, $projectDir, 'Link path');
}
}
// 读取 output/name
$output = $cfg['output'] ?? $cfg['name'] ?? null;
if (!empty($output)) {
$this->setTargetName((string) $output);
$this->setTargetName($this->resolvePath((string) $output, $projectDir, 'Output path'));
}
// 读取 cpp-compiler
@ -2175,7 +2187,7 @@ CODE;
$this->resourceConfig['_projectDir'] = $projectDir;
}
return $list;
return $this->filterIgnoredFiles($list);
}
protected function getInternalCeInfo(string $ce): array

Loading…
Cancel
Save