feat(msvc): add compiler pdb support for debug builds

- Add compiler_pdb option to specify PDB file path in debug mode
- Implement /Fd and /FS flags for PDB generation during compilation
- Generate linker PDB files following output path conventions
- Create cache/msvc directory for storing compiler PDB files
- Add Windows-specific debug configuration for PDB handling
- Update version number to reflect new feature implementation
pull/45/head
韩天峰 3 weeks ago
parent ec298e66fe
commit 8a23368cf1
  1. 18
      phpunit/src/Backend/BackendOptionsTest.php
  2. 30
      phpunit/src/Backend/BackendTest.php
  3. 15
      phpunit/src/CompilerBaseApiTest.php
  4. 1
      src/Backend/CompilerBackend.php
  5. 29
      src/Backend/Msvc.php
  6. 19
      src/Build/NativeCommandOptionsTrait.php
  7. 2
      version.txt

@ -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);
}
/**

@ -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 完整编译选项
*/

@ -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();

@ -123,6 +123,7 @@ abstract class CompilerBackend
* - enable_profiler: 是否启用性能分析
* - suppressed_warnings: 需要屏蔽的警告代码数组
* - cxxflags: 用户自定义编译标志
* - compiler_pdb: MSVC 编译器 PDB 输出路径
*/
abstract public function buildCompileOptions(array $config = []): string;

@ -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'];

@ -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

@ -1 +1 @@
1098
1099
Loading…
Cancel
Save