Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Quick Debugging in Test Explorer like in Project Outline #3451

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ RUN apt-get update && \
apt-get autoremove -y && \
apt-get clean -y && \
rm -rf /var/lib/apt/lists/*; \
curl -L -O https://github.com/Kitware/CMake/releases/download/v3.20.1/cmake-3.20.1-linux-x86_64.tar.gz && \
tar -xf cmake-3.20.1-linux-x86_64.tar.gz && \
rm cmake-3.20.1-linux-x86_64.tar.gz;
curl -L -O https://github.com/Kitware/CMake/releases/download/v3.27.8/cmake-3.27.8-linux-x86_64.tar.gz && \
tar -xf cmake-3.27.8-linux-x86_64.tar.gz && \
rm cmake-3.27.8-linux-x86_64.tar.gz;

ENV PATH "$PATH:/:/cmake-3.20.1-linux-x86_64/bin"
ENV PATH "$PATH:/:/cmake-3.27.8-linux-x86_64/bin"
43 changes: 30 additions & 13 deletions src/cmakeProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import paths from './paths';
import { ProjectController } from './projectController';
import { MessageItem } from 'vscode';
import { DebugTrackerFactory, DebuggerInformation, getDebuggerPipeName } from './debug/debuggerConfigureDriver';
import { VSCodeDebugConfiguration } from './debugger';

nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
Expand Down Expand Up @@ -2248,7 +2249,7 @@ export class CMakeProject {
}

if (name) {
const found = (await this.executableTargets).find(e => e.name === name);
const found = (await this.executableTargets).find(e => path.parse(e.path).name === name);
if (!found) {
return null;
}
Expand Down Expand Up @@ -2303,10 +2304,17 @@ export class CMakeProject {
return EnvironmentUtils.merge([env, configureEnv]);
}

/**
* Implementation of `cmake.debugTarget`
*/
async debugTarget(name?: string): Promise<vscode.DebugSession | null> {
async getDebugConfiguration(name?: string): Promise<VSCodeDebugConfiguration | null> {
const targetExecutable = await this.prepareLaunchTargetExecutable(name);
if (!targetExecutable) {
log.error(localize('failed.to.prepare.target', 'Failed to prepare executable target with name {0}', `"${name}"`));
return null;
}

if (!targetExecutable) {
return null;
}

const drv = await this.getCMakeDriverInstance();
if (!drv) {
void vscode.window.showErrorMessage(localize('set.up.and.build.project.before.debugging', 'Set up and build your CMake project before debugging.'));
Expand All @@ -2326,12 +2334,6 @@ export class CMakeProject {
return null;
}

const targetExecutable = await this.prepareLaunchTargetExecutable(name);
if (!targetExecutable) {
log.error(localize('failed.to.prepare.target', 'Failed to prepare executable target with name {0}', `"${name}"`));
return null;
}

let debugConfig;
try {
const cache = await CMakeCache.fromPath(drv.cachePath);
Expand Down Expand Up @@ -2370,10 +2372,25 @@ export class CMakeProject {
config: debugConfig
}));

return debugConfig;
}

/**
* Implementation of `cmake.debugTarget`
*/
async debugTarget(name?: string): Promise<vscode.DebugSession | null> {

const debugConfig = await this.getDebugConfiguration(name);

if (debugConfig === null) {
return null;
}

const cfg = vscode.workspace.getConfiguration('cmake', this.workspaceFolder.uri).inspect<object>('debugConfig');
const customSetting = (cfg?.globalValue !== undefined || cfg?.workspaceValue !== undefined || cfg?.workspaceFolderValue !== undefined);
let dbg = debugConfig.MIMode?.toString();
if (!dbg && debugConfig.type === "cppvsdbg") {

let dbg = debugConfig?.MIMode?.toString();
if (!dbg && debugConfig?.type === "cppvsdbg") {
dbg = "vsdbg";
} else {
dbg = "(unset)";
Expand Down
105 changes: 11 additions & 94 deletions src/ctest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,54 +758,22 @@ export class CTestDriver implements vscode.Disposable {

private async debugCTestImpl(workspaceFolder: vscode.WorkspaceFolder, testName: string, cancellation: vscode.CancellationToken): Promise<void> {
const magicValue = sessionNum++;
const launchConfig = vscode.workspace.getConfiguration(
'launch',
workspaceFolder.uri
);
const workspaceLaunchConfig = vscode.workspace.workspaceFile ? vscode.workspace.getConfiguration(
'launch',
vscode.workspace.workspaceFile
) : undefined;
const configs = launchConfig.get<vscode.DebugConfiguration[]>('configurations') ?? [];
const workspaceConfigs = workspaceLaunchConfig?.get<vscode.DebugConfiguration[]>('configurations') ?? [];
if (configs.length === 0 && workspaceConfigs.length === 0) {

const testProgram = this.testProgram(testName);
const basename = path.parse(testProgram).name;
const debugConfig = await this.projectController?.getActiveCMakeProject()?.getDebugConfiguration(basename);

if (debugConfig === null || debugConfig === undefined) {
log.error(localize('no.launch.config', 'No launch configurations found.'));
return;
}

interface ConfigItem extends vscode.QuickPickItem {
label: string;
config: vscode.DebugConfiguration;
detail: string;
// Undefined for workspace launch config
folder?: vscode.WorkspaceFolder;
}
let allConfigItems: ConfigItem[] = configs.map(config => ({ label: config.name, config, folder: workspaceFolder, detail: workspaceFolder.uri.fsPath }));
allConfigItems = allConfigItems.concat(workspaceConfigs.map(config => ({ label: config.name, config, detail: vscode.workspace.workspaceFile!.fsPath })));
let chosenConfig: ConfigItem | undefined;
if (allConfigItems.length === 1) {
chosenConfig = allConfigItems[0];
} else {
// TODO: we can remember the last choice once the CMake side panel work is done
const chosen = await vscode.window.showQuickPick(allConfigItems, { placeHolder: localize('choose.launch.config', 'Choose a launch configuration to debug the test with.') });
if (chosen) {
chosenConfig = chosen;
} else {
return;
}
}

// Commands can't be used to replace array (i.e., args); and both test program and test args requires folder and
// test name as parameters, which means one lauch config for each test. So replacing them here is a better way.
chosenConfig.config = this.replaceAllInObject<vscode.DebugConfiguration>(chosenConfig.config, '${cmake.testProgram}', this.testProgram(testName));
chosenConfig.config = this.replaceAllInObject<vscode.DebugConfiguration>(chosenConfig.config, '${cmake.testWorkingDirectory}', this.testWorkingDirectory(testName));

// Replace cmake.testArgs wrapped in quotes, like `"${command:cmake.testArgs}"`, without any spaces in between,
// since we need to repalce the quotes as well.
chosenConfig.config = this.replaceArrayItems(chosenConfig.config, '${cmake.testArgs}', this.testArgs(testName)) as vscode.DebugConfiguration;
debugConfig.cwd = this.testWorkingDirectory(testName)!;
debugConfig.args = this.testArgs(testName)!;

// Identify the session we started
chosenConfig.config[magicKey] = magicValue;
debugConfig[magicKey] = magicValue;

let onDidStartDebugSession: vscode.Disposable | undefined;
let onDidTerminateDebugSession: vscode.Disposable | undefined;
let sessionId: string | undefined;
Expand All @@ -828,7 +796,7 @@ export class CTestDriver implements vscode.Disposable {
log.info('debugSessionTerminated');
});

const debugStarted = await vscode.debug.startDebugging(chosenConfig.folder, chosenConfig.config!);
const debugStarted = await vscode.debug.startDebugging(workspaceFolder, debugConfig!);
if (debugStarted) {
const session = await started;
if (session) {
Expand Down Expand Up @@ -879,57 +847,6 @@ export class CTestDriver implements vscode.Disposable {
return [];
}

private replaceAllInObject<T>(obj: any, str: string, replace: string): T {
const regex = new RegExp(util.escapeStringForRegex(str), 'g');
if (util.isString(obj)) {
obj = obj.replace(regex, replace);
} else if (util.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
obj[i] = this.replaceAllInObject(obj[i], str, replace);
}
} else if (typeof obj === 'object') {
for (const key of Object.keys(obj)) {
obj[key] = this.replaceAllInObject(obj[key], str, replace);
}
}
return obj;
}

private replaceArrayItems(obj: any, str: string, replace: string[]) {
if (util.isArray(obj) && obj.length !== 0) {
const result: any[] = [];
for (let i = 0; i < obj.length; i++) {
if (util.isArray(obj[i]) || typeof obj[i] === 'object') {
result.push(this.replaceArrayItems(obj[i], str, replace));
} else if (util.isString(obj[i])) {
const replacedItem = this.replaceArrayItemsHelper(obj[i] as string, str, replace);
if (util.isArray(replacedItem)) {
result.push(...replacedItem);
} else {
result.push(replacedItem);
}
} else {
result.push(obj[i]);
}
}
return result;
}
if (typeof obj === 'object') {
for (const key of Object.keys(obj)) {
obj[key] = this.replaceArrayItems(obj[key], str, replace);
}
return obj;
}
return obj;
}

private replaceArrayItemsHelper(orig: string, str: string, replace: string[]): string | string[] {
if (orig === str) {
return replace;
}
return orig;
}

private async debugTestHandler(request: vscode.TestRunRequest, cancellation: vscode.CancellationToken) {
if (!testExplorer) {
return;
Expand Down