Skip to content

Commit ce18d39

Browse files
authored
Merge branch 'main' into copilot/fix-bookmarks-behavior
2 parents 2279516 + 438cbb2 commit ce18d39

File tree

18 files changed

+332
-76
lines changed

18 files changed

+332
-76
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 1.23
44

55
Features:
6+
- Add `cmake.shell` setting to route CMake/CTest/CPack subprocess invocations through a custom shell (e.g., Git Bash, MSYS2), enabling embedded toolchains that require POSIX path translation on Windows. [#1750](https://github.com/microsoft/vscode-cmake-tools/issues/1750)
67
- triple: Add riscv32be riscv64be support. [#4648](https://github.com/microsoft/vscode-cmake-tools/pull/4648) [@lygstate](https://github.com/lygstate)
78
- Add command to clear build diagnostics from the Problems pane. [#4691](https://github.com/microsoft/vscode-cmake-tools/pull/4691)
89
- Clear build diagnostics from the Problems pane when a new build starts and populate them incrementally during the build. [#4608](https://github.com/microsoft/vscode-cmake-tools/issues/4608)
@@ -14,6 +15,7 @@ Features:
1415
- Support `targetName` argument for launch-target command substitutions (`cmake.launchTargetPath`, etc.) via `${input:...}` variables, enabling build-before-run for non-active executable targets without changing the active launch target. [#4656](https://github.com/microsoft/vscode-cmake-tools/issues/4656)
1516

1617
Improvements:
18+
- Clicking on a CTest in the Project Outline now navigates to the test source file, matching the existing Test Explorer behavior. [#4773](https://github.com/microsoft/vscode-cmake-tools/issues/4773)
1719
- Clicking on a CTest unit test in the Test Explorer now navigates to the test source file by matching the test executable to its CMake target's source files. [#4449](https://github.com/microsoft/vscode-cmake-tools/issues/4449)
1820
- Make "CMake: Add ... Preset" commands available in the command palette when `cmake.useCMakePresets` is set to `auto`, even before a CMakePresets.json file exists. [#4401](https://github.com/microsoft/vscode-cmake-tools/issues/4401)
1921
- Improve CMake syntax highlighting with command-scoped keyword rules, expanded variable recognition, and better generator-expression support. Remove obsolete deprecated-keyword entries that caused false positives on user-defined names. [#4709](https://github.com/microsoft/vscode-cmake-tools/issues/4709) [#4508](https://github.com/microsoft/vscode-cmake-tools/issues/4508) [#4613](https://github.com/microsoft/vscode-cmake-tools/issues/4613)
@@ -22,6 +24,7 @@ Improvements:
2224
- Allow preset modification commands to target CMakeUserPresets.json. The target file is determined by the focused editor, or by prompting the user when both files exist. [#4564](https://github.com/microsoft/vscode-cmake-tools/issues/4564)
2325
- Display info tooltip when hovering over CMake policy identifiers (e.g., `CMP0177`), showing the CMake version that introduced the policy, a short description, and a link to the official documentation. [#4544](https://github.com/microsoft/vscode-cmake-tools/issues/4544)
2426
- Append `cmake` to diagnostics that this extension contributes to the Problems Pane. [PR #4766](https://github.com/microsoft/vscode-cmake-tools/pull/4766)
27+
- Honor `debugger.workingDirectory` from the CMake File API when debugging a target, so that the `DEBUGGER_WORKING_DIRECTORY` target property is used as the debugger working directory. [#4595](https://github.com/microsoft/vscode-cmake-tools/issues/4595)
2528

2629
Bug Fixes:
2730
- Fix bookmarked targets running a full build instead of building only the specific target when triggered from the Bookmarks view. [#4771](https://github.com/microsoft/vscode-cmake-tools/issues/4771)

docs/cmake-settings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Options that support substitution, in the table below, allow variable references
7878
| `cmake.saveBeforeBuild` | If `true` (the default), saves open text documents when build or configure is invoked before running CMake. | `true` | no |
7979
| `cmake.setBuildTargetSameAsLaunchTarget` | If `true`, setting the launch/debug target automatically sets the build target to match. | `false` | no |
8080
| `cmake.setBuildTypeOnMultiConfig` | If `true`, set build type on multi-config generators. | `false` | no |
81+
| `cmake.shell` | Path to a shell executable to route all CMake/CTest/CPack subprocess invocations through (e.g., Git Bash or MSYS2). Useful for embedded toolchains that require POSIX path translation on Windows. When `null`, the default system shell behavior is used. | `null` | no |
8182
| `cmake.showConfigureWithDebuggerNotification` | If `true`, show notification when configure with debugger. | `true` | no |
8283
| `cmake.showNotAllDocumentsSavedQuestion` | If `true`, show not all documents saved question. | `true` | no |
8384
| `cmake.showSystemKits` | If `true`, show system kits in kit selection. | `true` | no |

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,6 +2394,15 @@
23942394
"description": "%cmake-tools.configuration.cmake.platform.description%",
23952395
"scope": "resource"
23962396
},
2397+
"cmake.shell": {
2398+
"type": [
2399+
"string",
2400+
"null"
2401+
],
2402+
"default": null,
2403+
"description": "%cmake-tools.configuration.cmake.shell.description%",
2404+
"scope": "resource"
2405+
},
23972406
"cmake.configureArgs": {
23982407
"type": "array",
23992408
"description": "%cmake-tools.configuration.cmake.configureArgs.description%",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"cmake-tools.configuration.cmake.generator.description": "The CMake generator to use.",
122122
"cmake-tools.configuration.cmake.toolset.description": "The CMake toolset to use when configuring.",
123123
"cmake-tools.configuration.cmake.platform.description": "The CMake platform to use when configuring.",
124+
"cmake-tools.configuration.cmake.shell.description": "Path to a shell executable to use when running CMake, CTest, and CPack commands (e.g., Git Bash or MSYS2). When set, all subprocess invocations are routed through this shell. Useful for embedded toolchains that require POSIX path translation. When null, the default system shell behavior is used.",
124125
"cmake-tools.configuration.cmake.configureArgs.description": "Additional arguments to pass to CMake when configuring. When using CMake Presets, these arguments are temporarily appended to the arguments provided by the active configure preset.",
125126
"cmake-tools.configuration.cmake.buildArgs.description": "Additional arguments to pass to CMake when building. When using CMake Presets, these arguments are temporarily appended to the arguments provided by the active build preset.",
126127
"cmake-tools.configuration.cmake.buildToolArgs.description": "Additional arguments to pass to the underlying build tool when building. When using CMake Presets, these arguments are temporarily appended to the arguments provided by the active build preset to invoke the build tool.",

src/cmakeProject.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,11 @@ export class CMakeProject {
847847
await this.reloadCMakeDriver();
848848
});
849849

850+
private readonly shellSub = this.workspaceContext.config.onChange('shell', async () => {
851+
log.info(localize('shell.changed.restart.driver', "Restarting the CMake driver after a shell change."));
852+
await this.reloadCMakeDriver();
853+
});
854+
850855
/**
851856
* The variant manager keeps track of build variants. Has two-phase init.
852857
*/
@@ -890,7 +895,8 @@ export class CMakeProject {
890895
this.generatorSub,
891896
this.preferredGeneratorsSub,
892897
this.communicationModeSub,
893-
this.cmakePathSub
898+
this.cmakePathSub,
899+
this.shellSub
894900
]) {
895901
sub.dispose();
896902
}

src/cmakeTaskProvider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,10 @@ export class CustomBuildTaskTerminal extends proc.CommandConsumer implements vsc
587587
this.writeEmitter.fire(localize("build.started", "{0} task started....", taskName) + endOfLine);
588588
this.writeEmitter.fire(proc.buildCmdStr(cmakePath, args) + endOfLine);
589589
try {
590-
this._process = proc.execute(cmakePath, args, this, this.options);
590+
// On Windows, command-type-specific detection takes precedence over config.shell
591+
const commandShell = process.platform === 'win32' ? proc.determineShell(cmakePath) : false;
592+
const shell = (commandShell || undefined) ?? cmakeDriver.config.shell ?? undefined;
593+
this._process = proc.execute(cmakePath, args, this, { ...this.options, shell });
591594
const result: proc.ExecutionResult = await this._process.result;
592595
this._process = undefined;
593596
if (result.retc) {

src/config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ export interface ExtensionConfigurationSettings {
237237
useFolderPropertyInBuildTargetDropdown: boolean;
238238
setBuildTargetSameAsLaunchTarget: boolean;
239239
additionalBuildProblemMatchers: BuildProblemMatcherConfig[];
240+
shell: string | null;
240241
}
241242

242243
type EmittersOf<T> = {
@@ -628,6 +629,10 @@ export class ConfigurationReader implements vscode.Disposable {
628629
return this.configData.useFolderPropertyInBuildTargetDropdown;
629630
}
630631

632+
get shell(): string | null {
633+
return this.configData.shell;
634+
}
635+
631636
get setBuildTargetSameAsLaunchTarget(): boolean {
632637
return this.configData.setBuildTargetSameAsLaunchTarget;
633638
}
@@ -703,8 +708,9 @@ export class ConfigurationReader implements vscode.Disposable {
703708
postRunCoverageTarget: new vscode.EventEmitter<string | null>(),
704709
coverageInfoFiles: new vscode.EventEmitter<string[]>(),
705710
useFolderPropertyInBuildTargetDropdown: new vscode.EventEmitter<boolean>(),
706-
setBuildTargetSameAsLaunchTarget: new vscode.EventEmitter<boolean>(),
707-
additionalBuildProblemMatchers: new vscode.EventEmitter<BuildProblemMatcherConfig[]>()
711+
additionalBuildProblemMatchers: new vscode.EventEmitter<BuildProblemMatcherConfig[]>(),
712+
shell: new vscode.EventEmitter<string | null>(),
713+
setBuildTargetSameAsLaunchTarget: new vscode.EventEmitter<boolean>()
708714
};
709715

710716
/**

src/ctest.ts

Lines changed: 74 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as xml2js from 'xml2js';
55
import * as zlib from 'zlib';
66

77
import { CMakeDriver } from '@cmt/drivers/drivers';
8+
import { CodeModelContent } from '@cmt/drivers/codeModel';
89
import * as logging from '@cmt/logging';
910
import { fs } from '@cmt/pr';
1011
import * as util from '@cmt/util';
@@ -782,9 +783,8 @@ export class CTestDriver implements vscode.Disposable {
782783
* Builds a map from normalized executable paths to source file information
783784
* by looking at the code model content from the CMake driver.
784785
*/
785-
private buildExecutableToSourcesMap(driver: CMakeDriver): Map<string, { sourceDir: string; sources: string[] }> {
786+
private buildExecutableToSourcesMap(codeModelContent: CodeModelContent | null): Map<string, { sourceDir: string; sources: string[] }> {
786787
const map = new Map<string, { sourceDir: string; sources: string[] }>();
787-
const codeModelContent = driver.codeModelContent;
788788
if (!codeModelContent) {
789789
return map;
790790
}
@@ -812,6 +812,61 @@ export class CTestDriver implements vscode.Disposable {
812812
return map;
813813
}
814814

815+
/**
816+
* Resolves the source file and line number for a single CTest test entry.
817+
* Uses a 3-step priority:
818+
* 1) DEF_SOURCE_LINE test property
819+
* 2) Code model executable-to-sources matching
820+
* 3) Backtrace graph (falls back to CMakeLists.txt)
821+
*/
822+
private resolveTestSourceLocation(
823+
test: CTestInfo['tests'][0],
824+
executableToSources: Map<string, { sourceDir: string; sources: string[] }> | undefined,
825+
backtraceGraph: CTestInfo['backtraceGraph'] | undefined
826+
): { file?: string; line?: number } {
827+
let file: string | undefined;
828+
let line: number | undefined;
829+
830+
// 1. Use DEF_SOURCE_LINE CMake test property
831+
const defSourceLineProperty = test.properties.filter(p => p.name === "DEF_SOURCE_LINE")[0];
832+
if (defSourceLineProperty && defSourceLineProperty.value && typeof defSourceLineProperty.value === 'string') {
833+
const match = defSourceLineProperty.value.match(/(.*):(\d+)/);
834+
if (match && match[1] && match[2]) {
835+
file = match[1];
836+
line = parseInt(match[2]);
837+
if (isNaN(line)) {
838+
line = undefined;
839+
file = undefined;
840+
}
841+
}
842+
}
843+
844+
// 2. Match test executable to CMake target sources
845+
if (!file && test.command && test.command.length > 0 && executableToSources) {
846+
const testExe = util.platformNormalizePath(test.command[0]);
847+
const targetInfo = executableToSources.get(testExe);
848+
if (targetInfo && targetInfo.sources.length > 0) {
849+
file = path.resolve(targetInfo.sourceDir, targetInfo.sources[0]);
850+
line = 1;
851+
}
852+
}
853+
854+
// 3. Backtrace graph (falls back to CMakeLists.txt)
855+
if (!file && backtraceGraph) {
856+
const nodes = backtraceGraph.nodes;
857+
if (test.backtrace !== undefined && nodes[test.backtrace] !== undefined) {
858+
let node = nodes[test.backtrace];
859+
while (node.parent !== undefined && nodes[node.parent].command !== undefined) {
860+
node = nodes[node.parent];
861+
}
862+
file = backtraceGraph.files[node.file];
863+
line = node.line;
864+
}
865+
}
866+
867+
return { file, line };
868+
}
869+
815870
private createTestItemAndSuiteTree(testName: string, testExplorerRoot: vscode.TestItem, initializedTestExplorer: vscode.TestController, uri?: vscode.Uri): TestAndParentSuite {
816871
let parentSuiteItem = testExplorerRoot;
817872
let testLabel = testName;
@@ -883,49 +938,10 @@ export class CTestDriver implements vscode.Disposable {
883938
} else if (testType === "CTestInfo" && this.tests !== undefined) {
884939
if (this.tests && this.tests.kind === 'ctestInfo') {
885940
// Build a map from executable paths to source files using the code model
886-
const executableToSources = this.buildExecutableToSourcesMap(driver);
941+
const executableToSources = this.buildExecutableToSourcesMap(driver.codeModelContent);
887942

888943
this.tests.tests.forEach(test => {
889-
let testDefFile: string | undefined;
890-
let testDefLine: number | undefined;
891-
892-
// Use DEF_SOURCE_LINE CMake test property to find file and line number
893-
// Property must be set in the test's CMakeLists.txt file or its included modules for this to work
894-
const defSourceLineProperty = test.properties.filter(property => property.name === "DEF_SOURCE_LINE")[0];
895-
if (defSourceLineProperty && defSourceLineProperty.value && typeof defSourceLineProperty.value === 'string') {
896-
// Use RegEx to match the format "file_path:line" in value[0]
897-
const match = defSourceLineProperty.value.match(/(.*):(\d+)/);
898-
if (match && match[1] && match[2]) {
899-
testDefFile = match[1];
900-
testDefLine = parseInt(match[2]);
901-
if (isNaN(testDefLine)) {
902-
testDefLine = undefined;
903-
testDefFile = undefined;
904-
}
905-
}
906-
}
907-
908-
// Try to find the test source file by matching the test executable to a CMake target
909-
if (!testDefFile && test.command && test.command.length > 0) {
910-
const testExe = util.platformNormalizePath(test.command[0]);
911-
const targetInfo = executableToSources.get(testExe);
912-
if (targetInfo && targetInfo.sources.length > 0) {
913-
testDefFile = path.resolve(targetInfo.sourceDir, targetInfo.sources[0]);
914-
testDefLine = 1;
915-
}
916-
}
917-
918-
const nodes = this.tests!.backtraceGraph.nodes;
919-
if (!testDefFile && test.backtrace !== undefined && nodes[test.backtrace] !== undefined) {
920-
// Use the backtrace graph to find the file and line number
921-
// This finds the CMake module's file and line number and not the test file and line number
922-
let node = nodes[test.backtrace];
923-
while (node.parent !== undefined && nodes[node.parent].command !== undefined) {
924-
node = nodes[node.parent];
925-
}
926-
testDefFile = this.tests!.backtraceGraph.files[node.file];
927-
testDefLine = node.line;
928-
}
944+
const { file: testDefFile, line: testDefLine } = this.resolveTestSourceLocation(test, executableToSources, this.tests!.backtraceGraph);
929945

930946
const testAndParentSuite = this.createTestItemAndSuiteTree(test.name, testExplorerRoot, initializedTestExplorer, testDefFile ? vscode.Uri.file(testDefFile) : undefined);
931947
const testItem = testAndParentSuite.test;
@@ -1048,14 +1064,23 @@ export class CTestDriver implements vscode.Disposable {
10481064

10491065
/**
10501066
* Returns test information suitable for the project outline view.
1051-
* Each entry maps a test name to its executable path.
1067+
* Each entry maps a test name to its executable path and optionally
1068+
* the resolved source file path for click-to-navigate.
10521069
*/
1053-
getTestsForOutline(): { name: string; executablePath: string }[] {
1070+
getTestsForOutline(codeModelContent?: CodeModelContent | null): { name: string; executablePath: string; sourceFilePath?: string; sourceFileLine?: number }[] {
10541071
if (this.tests) {
1055-
return this.tests.tests.map(test => ({
1056-
name: test.name,
1057-
executablePath: test.command[0]
1058-
}));
1072+
const executableToSources = codeModelContent ? this.buildExecutableToSourcesMap(codeModelContent) : undefined;
1073+
1074+
return this.tests.tests.map(test => {
1075+
const { file: sourceFilePath, line: sourceFileLine } = this.resolveTestSourceLocation(test, executableToSources, this.tests!.backtraceGraph);
1076+
1077+
return {
1078+
name: test.name,
1079+
executablePath: test.command[0],
1080+
sourceFilePath,
1081+
sourceFileLine
1082+
};
1083+
});
10591084
}
10601085
return [];
10611086
}

src/debug/cmakeDebugger/debuggerScriptDriver.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ export async function executeScriptWithDebugger(scriptPath: string, scriptArgs:
4646
cmakeLogger.info(localize('run.script', "Executing CMake script: {0}", scriptPath));
4747

4848
const env = EnvironmentUtils.merge([process.env, EnvironmentUtils.create(scriptEnv)]);
49-
const child = proc.execute(cmakeExe.path, concreteArgs, outputConsumer, { environment: env});
49+
const commandShell = process.platform === 'win32' ? proc.determineShell(cmakeExe.path) : false;
50+
const configShell = cmakeProject.workspaceContext.config.shell;
51+
const shell = (commandShell || undefined) ?? configShell ?? undefined;
52+
const child = proc.execute(cmakeExe.path, concreteArgs, outputConsumer, { environment: env, shell });
5053

5154
while (
5255
!outputConsumer.stateMessages.includes(

src/debug/debugger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ async function createGDBDebugConfiguration(debuggerPath: string, target: Executa
9595
type: 'cppdbg',
9696
name: `Debug ${target.name}`,
9797
request: 'launch',
98-
cwd: path.dirname(target.path),
98+
cwd: target.debuggerWorkingDirectory || path.dirname(target.path),
9999
args: [],
100100
MIMode: MIModes.gdb,
101101
miDebuggerPath: debuggerPath,
@@ -127,7 +127,7 @@ async function createLLDBDebugConfiguration(debuggerPath: string, target: Execut
127127
type: 'cppdbg',
128128
name: `Debug ${target.name}`,
129129
request: 'launch',
130-
cwd: path.dirname(target.path),
130+
cwd: target.debuggerWorkingDirectory || path.dirname(target.path),
131131
args: [],
132132
MIMode: MIModes.lldb,
133133
miDebuggerPath: debuggerPath,
@@ -146,7 +146,7 @@ function createMsvcDebugConfiguration(target: ExecutableTarget): VSCodeDebugConf
146146
type: 'cppvsdbg',
147147
name: `Debug ${target.name}`,
148148
request: 'launch',
149-
cwd: path.dirname(target.path),
149+
cwd: target.debuggerWorkingDirectory || path.dirname(target.path),
150150
args: [],
151151
program: target.path
152152
};

0 commit comments

Comments
 (0)