diff --git a/phpunit/src/Backend/BackendOptionsTest.php b/phpunit/src/Backend/BackendOptionsTest.php index bfc4c582..64901a27 100644 --- a/phpunit/src/Backend/BackendOptionsTest.php +++ b/phpunit/src/Backend/BackendOptionsTest.php @@ -62,10 +62,28 @@ class BackendOptionsTest extends TestCase $options = $compiler->buildCompileOptions([ 'debug' => true, + 'compiler_pdb' => 'C:\\build output\\cache\\msvc\\app.compile.pdb', ]); $this->assertStringContainsString('/Od', $options); // 禁用优化 $this->assertStringContainsString('/Zi', $options); // 生成调试信息 + $this->assertStringContainsString( + '/Fd' . escapeshellarg('C:\\build output\\cache\\msvc\\app.compile.pdb'), + $options + ); + $this->assertStringContainsString('/FS', $options); + } + + public function testMsvcReleaseCompileOptionsDoNotCreatePdb(): void + { + $compiler = new Msvc(new Windows()); + $options = $compiler->buildCompileOptions([ + 'debug' => false, + 'compiler_pdb' => 'C:\\build\\app.compile.pdb', + ]); + + $this->assertStringNotContainsString('/Fd', $options); + $this->assertStringNotContainsString('/FS', $options); } /** diff --git a/phpunit/src/Backend/BackendTest.php b/phpunit/src/Backend/BackendTest.php index 18997cf0..19bbf9ef 100644 --- a/phpunit/src/Backend/BackendTest.php +++ b/phpunit/src/Backend/BackendTest.php @@ -164,6 +164,21 @@ class BackendTest extends TestCase $this->assertStringNotContainsString('/std:', $cmd); } + public function testMsvcDebugPdbOptionsApplyToCppAndCCommands(): void + { + $compiler = new Msvc(new Windows()); + $pdb = 'C:\\build output\\cache\\msvc\\app.compile.pdb'; + $options = ['debug' => true, 'compiler_pdb' => $pdb]; + + $cpp = $compiler->buildCompileCommand('app.cpp', 'app.obj', $options); + $c = $compiler->buildCCompileCommand('helper.c', 'helper.obj', $options); + + foreach ([$cpp, $c] as $command) { + $this->assertStringContainsString('/Fd' . escapeshellarg($pdb), $command); + $this->assertStringContainsString('/FS', $command); + } + } + /** * 测试 MSVC 完整链接命令 */ @@ -184,11 +199,26 @@ class BackendTest extends TestCase $this->assertStringContainsString('link', $cmd); $this->assertStringContainsString('/OUT:', $cmd); $this->assertStringContainsString('/DEBUG', $cmd); + $this->assertStringContainsString('/PDB:' . escapeshellarg('output.pdb'), $cmd); $this->assertStringContainsString('/NODEFAULTLIB:LIBCMT', $cmd); $this->assertStringContainsString('/nologo', $cmd); $compiler->cleanupResponseFile(); } + public function testMsvcDebugLinkPdbFollowsOutputPath(): void + { + $compiler = new Msvc(new Windows()); + $output = 'C:\\build output\\app.dll'; + + $cmd = $compiler->buildLinkCommand(['app.obj'], $output, ['debug' => true]); + + $this->assertStringContainsString( + '/PDB:' . escapeshellarg('C:\\build output\\app.pdb'), + $cmd + ); + $compiler->cleanupResponseFile(); + } + /** * 测试 MSVC 完整编译选项 */ diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 0cffa399..8cdf666c 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -1269,6 +1269,21 @@ YAML); $this->assertSame('-Wl,--as-needed', $options['ldflags']); } + public function testWindowsDebugCompilerPdbFollowsBuildDirectory(): void + { + $this->compiler->setTargetName('pdb_app'); + + $pdb = $this->invokeMethod('getMsvcCompilerPdbFile'); + $expectedDirectory = $this->compiler->getBuildDir() + . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'msvc'; + + $this->assertSame( + $expectedDirectory . DIRECTORY_SEPARATOR . 'pdb_app.compile.pdb', + $pdb + ); + $this->assertDirectoryExists($expectedDirectory); + } + public function testFormatCppCodeEscapesPathsWithSpaces(): void { $spaceDir = sys_get_temp_dir() . '/compiler api format ' . uniqid(); diff --git a/src/Backend/CompilerBackend.php b/src/Backend/CompilerBackend.php index 0e3f8a64..43b91970 100644 --- a/src/Backend/CompilerBackend.php +++ b/src/Backend/CompilerBackend.php @@ -123,6 +123,7 @@ abstract class CompilerBackend * - enable_profiler: 是否启用性能分析 * - suppressed_warnings: 需要屏蔽的警告代码数组 * - cxxflags: 用户自定义编译标志 + * - compiler_pdb: MSVC 编译器 PDB 输出路径 */ abstract public function buildCompileOptions(array $config = []): string; diff --git a/src/Backend/Msvc.php b/src/Backend/Msvc.php index d7ab2387..4fa6457b 100644 --- a/src/Backend/Msvc.php +++ b/src/Backend/Msvc.php @@ -52,6 +52,12 @@ class Msvc extends CompilerBackend if (!empty($config['debug'])) { $cmd .= ' /Od /Zi'; + if (!empty($config['compiler_pdb'])) { + $cmd .= ' /Fd' . escapeshellarg($config['compiler_pdb']); + // All translation units of one target share this compiler PDB. + // Serialize writes within the target while /Fd isolates apps. + $cmd .= ' /FS'; + } } else { $optimizeLevel = $config['optimize'] ?? 2; $optMap = [0 => '/Od', 1 => '/O1', 2 => '/O2', 3 => '/Ox']; @@ -233,6 +239,10 @@ class Msvc extends CompilerBackend $cmd .= ' ' . $this->createResponseFile($objectFiles, $outputFile); $cmd .= ' /OUT:' . escapeshellarg($outputFile); + if (!empty($options['debug'])) { + $cmd .= ' /PDB:' . escapeshellarg($this->getLinkPdbFile($outputFile)); + } + if (!empty($options['library_paths'])) { $cmd .= ' ' . $this->formatLibraryPaths($options['library_paths']); } @@ -250,6 +260,21 @@ class Msvc extends CompilerBackend return $cmd; } + private function getLinkPdbFile(string $outputFile): string + { + $forwardSlash = strrpos($outputFile, '/'); + $backslash = strrpos($outputFile, '\\'); + $lastSlash = max( + $forwardSlash === false ? -1 : $forwardSlash, + $backslash === false ? -1 : $backslash, + ); + $lastDot = strrpos($outputFile, '.'); + $base = $lastDot !== false && $lastDot > $lastSlash + ? substr($outputFile, 0, $lastDot) + : $outputFile; + return $base . '.pdb'; + } + /** * 构建编译单个文件的完整命令 */ @@ -305,6 +330,10 @@ class Msvc extends CompilerBackend // 优化和调试 if (!empty($options['debug'])) { $cmd .= ' /Od /Zi'; + if (!empty($options['compiler_pdb'])) { + $cmd .= ' /Fd' . escapeshellarg($options['compiler_pdb']); + $cmd .= ' /FS'; + } } else { $optimizeLevel = $options['optimize'] ?? 2; $optMap = [0 => '/Od', 1 => '/O1', 2 => '/O2', 3 => '/Ox']; diff --git a/src/Build/NativeCommandOptionsTrait.php b/src/Build/NativeCommandOptionsTrait.php index accee17c..13f56348 100644 --- a/src/Build/NativeCommandOptionsTrait.php +++ b/src/Build/NativeCommandOptionsTrait.php @@ -28,7 +28,7 @@ trait NativeCommandOptionsTrait $userDefines[] = $this->getLibraryExportsMacroName() . '=1'; } - return new CompileOptions([ + $values = [ 'include_paths' => $includePaths, 'optimize' => $this->optimizeLevel, 'debug' => $this->debug, @@ -41,7 +41,22 @@ trait NativeCommandOptionsTrait 'prof_output' => $this->targetName . '.prof', 'user_defines' => $userDefines, 'lto' => $this->enableLto, - ]); + ]; + + if ($this->debug && $this->isWindows()) { + $values['compiler_pdb'] = $this->getMsvcCompilerPdbFile(); + } + + return new CompileOptions($values); + } + + protected function getMsvcCompilerPdbFile(): string + { + $directory = $this->getBuildDir() . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'msvc'; + if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new \RuntimeException('Cannot create MSVC PDB directory: ' . $directory); + } + return $directory . DIRECTORY_SEPARATOR . $this->targetName . '.compile.pdb'; } protected function getCompileCommandOptions(): CompileOptions diff --git a/version.txt b/version.txt index dfc8356d..39987d00 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1098 \ No newline at end of file +1099 \ No newline at end of file