diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 080d0078..ddcaa871 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.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', "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']); diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 877a8031..dc5401cc 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -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