Skip to content

Commit 7810d7c

Browse files
resolve Debug configuration (#9179)
1 parent 3243e39 commit 7810d7c

2 files changed

Lines changed: 98 additions & 123 deletions

File tree

Extension/src/Debugger/configurationProvider.ts

Lines changed: 91 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { IConfiguration, IConfigurationSnippet, DebuggerType, DebuggerEvent, MIC
1717
import { parse } from 'comment-json';
1818
import { PlatformInformation } from '../platform';
1919
import { Environment, ParsedEnvironmentFile } from './ParsedEnvironmentFile';
20+
import { CppSettings } from '../LanguageServer/settings';
2021
import { configPrefix } from '../LanguageServer/extension';
2122

2223
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
@@ -107,7 +108,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
107108

108109
const items: MenuItem[] = configs.map<MenuItem>(config => {
109110
const reducedConfig: vscode.DebugConfiguration = {...config};
110-
// Remove the "detail" property from the DebugConfiguration that will be written in launch.json.
111+
// Remove the extra properties that are not a part of the DebugConfiguration.
111112
reducedConfig.detail = undefined;
112113
reducedConfig.existing = undefined;
113114
reducedConfig.isDefault = undefined;
@@ -129,11 +130,6 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
129130
this.showErrorIfClNotAvailable(selection.label);
130131
}
131132

132-
await this.resolvePreLaunchTask(folder, configs[0], DebuggerEvent.debugPanel);
133-
if (!folder) {
134-
// In case of singleFile, remove the preLaunch task.
135-
selection.configuration.preLaunchTask = undefined;
136-
}
137133
return [selection.configuration];
138134
}
139135

@@ -143,43 +139,38 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
143139
* If return "null", the debugging will be aborted and launch.json will be opened.
144140
* resolveDebugConfigurationWithSubstitutedVariables will be automatically called after this function.
145141
*/
146-
resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
142+
async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration | null | undefined> {
143+
const isIntelliSenseDisabled: boolean = new CppSettings((vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) ? vscode.workspace.workspaceFolders[0]?.uri : undefined).intelliSenseEngine === "Disabled";
147144
if (!config || !config.type) {
148145
// When DebugConfigurationProviderTriggerKind is Dynamic, this function will be called with an empty config.
149146
// Hence, providing debug configs, and start debugging should be done manually.
150147
// resolveDebugConfiguration will be automatically called after calling provideDebugConfigurations.
151-
this.provideDebugConfigurations(folder).then(async configs => {
152-
if (!configs || configs.length === 0) {
153-
Telemetry.logDebuggerEvent(DebuggerEvent.debugPanel, { "debugType": "debug", "folderMode": folder ? "folder" : "singleFile", "cancelled": "true" });
154-
return undefined; // aborts debugging silently
155-
} else {
156-
// Currently, we expect only one debug config to be selected.
157-
console.assert(configs.length === 1, "More than one debug config is selected.");
158-
// await this.resolvePreLaunchTask(folder, configs[0], DebuggerEvent.debugPanel);
159-
// await this.startDebugging(folder, configs[0], DebuggerEvent.debugPanel);
160-
return configs[0];
161-
}
162-
});
163-
} else {
164-
if (config.type === 'cppvsdbg') {
165-
// Handle legacy 'externalConsole' bool and convert to console: "externalTerminal"
166-
if (config.hasOwnProperty("externalConsole")) {
167-
logger.getOutputChannelLogger().showWarningMessage(localize("debugger.deprecated.config", "The key '{0}' is deprecated. Please use '{1}' instead.", "externalConsole", "console"));
168-
if (config.externalConsole && !config.console) {
169-
config.console = "externalTerminal";
170-
}
171-
delete config.externalConsole;
172-
}
173-
174-
// Fail if cppvsdbg type is running on non-Windows
175-
if (os.platform() !== 'win32') {
176-
logger.getOutputChannelLogger().showWarningMessage(localize("debugger.not.available", "Debugger of type: '{0}' is only available on Windows. Use type: '{1}' on the current OS platform.", "cppvsdbg", "cppdbg"));
177-
return undefined; // Stop debugging
178-
}
148+
const configs: vscode.DebugConfiguration[]= await this.provideDebugConfigurations(folder);
149+
if (!configs || configs.length === 0) {
150+
Telemetry.logDebuggerEvent(DebuggerEvent.debugPanel, { "debugType": "debug", "folderMode": folder ? "folder" : "singleFile", "cancelled": "true" });
151+
return undefined; // aborts debugging silently
152+
} else {
153+
// Currently, we expect only one debug config to be selected.
154+
console.assert(configs.length === 1, "More than one debug config is selected.");
155+
config = configs[0];
156+
// Keep track of the entry point where the debug config has been selected, for telemetry purposes.
157+
config.debuggerEvent = DebuggerEvent.debugPanel;
179158
}
180-
// resolveDebugConfigurationWithSubstitutedVariables will be automatically called after this return.
181-
return config;
182159
}
160+
if ((!folder || isIntelliSenseDisabled) && config.preLaunchTask) {
161+
/* There are two cases where folder is undefined:
162+
* when debugging is done on a single file where there is no folder open,
163+
* or when the debug configuration is defined at the User level.
164+
*/
165+
await this.resolvePreLaunchTask(undefined, config);
166+
config.preLaunchTask = undefined;
167+
} else {
168+
await this.resolvePreLaunchTask(folder, config);
169+
}
170+
await this.sendDebugTelemetry(folder, config);
171+
172+
// resolveDebugConfigurationWithSubstitutedVariables will be automatically called after this return.
173+
return config;
183174
}
184175

185176
/**
@@ -192,10 +183,25 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
192183
*/
193184
resolveDebugConfigurationWithSubstitutedVariables(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
194185
if (!config || !config.type) {
195-
return undefined;
186+
return undefined; // Abort debugging silently.
196187
}
197188

198189
if (config.type === 'cppvsdbg') {
190+
// Fail if cppvsdbg type is running on non-Windows
191+
if (os.platform() !== 'win32') {
192+
logger.getOutputChannelLogger().showWarningMessage(localize("debugger.not.available", "Debugger of type: '{0}' is only available on Windows. Use type: '{1}' on the current OS platform.", "cppvsdbg", "cppdbg"));
193+
return undefined; // Abort debugging silently.
194+
}
195+
196+
// Handle legacy 'externalConsole' bool and convert to console: "externalTerminal"
197+
if (config.hasOwnProperty("externalConsole")) {
198+
logger.getOutputChannelLogger().showWarningMessage(localize("debugger.deprecated.config", "The key '{0}' is deprecated. Please use '{1}' instead.", "externalConsole", "console"));
199+
if (config.externalConsole && !config.console) {
200+
config.console = "externalTerminal";
201+
}
202+
delete config.externalConsole;
203+
}
204+
199205
// Disable debug heap by default, enable if 'enableDebugHeap' is set.
200206
if (!config.enableDebugHeap) {
201207
const disableDebugHeapEnvSetting: Environment = {"name" : "_NO_DEBUG_HEAP", "value" : "1"};
@@ -392,32 +398,24 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
392398
});
393399
}));
394400
configs.push(defaultTemplateConfig);
395-
// Get existing debug configurations from launch.json.
396-
if (folder) {
397-
const existingConfigs: vscode.DebugConfiguration[] | undefined = (await this.getLaunchConfigs(folder, type))?.map(config => ({
398-
name: config.name,
399-
type: config.type,
400-
request: config.request,
401-
detail: config.detail ? config.detail :
402-
config.preLaunchTask ? localize("pre.Launch.Task", "preLaunchTask: {0}", config.preLaunchTask) : undefined,
403-
existing: TaskConfigStatus.configured,
404-
preLaunchTask: config.preLaunchTask
405-
}));
406-
if (existingConfigs) {
407-
const areEqual = (config1: vscode.DebugConfiguration, config2: vscode.DebugConfiguration): boolean =>
408-
(config1.preLaunchTask === config2.preLaunchTask
409-
&& config1.type === config2.type && config1.request === config2.request);
410-
// Remove the detected configs that are already configured once in launch.json.
411-
const dedupExistingConfigs: vscode.DebugConfiguration[] = configs.filter(detectedConfig => !existingConfigs.some(config => {
412-
if (areEqual(config, detectedConfig)) {
413-
// Carry the default task information.
414-
config.isDefault = detectedConfig.isDefault ? detectedConfig.isDefault : undefined;
415-
return true;
416-
}
417-
return false;
418-
}));
419-
configs = existingConfigs.concat(dedupExistingConfigs);
401+
const existingConfigs: vscode.DebugConfiguration[] | undefined = this.getLaunchConfigs(folder, type)?.map(config => {
402+
if (!config.detail && config.preLaunchTask) {
403+
config.detail = localize("pre.Launch.Task", "preLaunchTask: {0}", config.preLaunchTask);
420404
}
405+
config.existing = TaskConfigStatus.configured;
406+
return config;
407+
});
408+
if (existingConfigs) {
409+
// Remove the detected configs that are already configured once in launch.json.
410+
const dedupExistingConfigs: vscode.DebugConfiguration[] = configs.filter(detectedConfig => !existingConfigs.some(config => {
411+
if (config.preLaunchTask === detectedConfig.preLaunchTask && config.type === detectedConfig.type && config.request === detectedConfig.request) {
412+
// Carry the default task information.
413+
config.isDefault = detectedConfig.isDefault ? detectedConfig.isDefault : undefined;
414+
return true;
415+
}
416+
return false;
417+
}));
418+
configs = existingConfigs.concat(dedupExistingConfigs);
421419
}
422420
return configs;
423421
}
@@ -574,15 +572,27 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
574572
}
575573
}
576574

577-
public async getLaunchConfigs(folder: vscode.WorkspaceFolder | undefined, type: DebuggerType): Promise<vscode.WorkspaceConfiguration[] | undefined> {
578-
let configs: vscode.WorkspaceConfiguration[] | undefined = vscode.workspace.getConfiguration('launch', folder).get('configurations');
575+
public getLaunchConfigs(folder?: vscode.WorkspaceFolder, type?: DebuggerType): vscode.DebugConfiguration[] | undefined {
576+
// Get existing debug configurations from launch.json or user/workspace "launch" settings.
577+
let configs: any = vscode.workspace.getConfiguration('launch', folder).get('configurations');
579578
if (!configs) {
580579
return undefined;
581580
}
582-
configs = configs.filter(config => (config.name && config.request === "launch" && config.type === type));
581+
configs = configs.filter((config: any) => (config.name && config.request === "launch" && type ? (config.type === type) : true));
583582
return configs;
584583
}
585584

585+
public checkDebugConfigExists(configName: string, folder?: vscode.WorkspaceFolder, type?: DebuggerType): boolean {
586+
const configs: vscode.DebugConfiguration[] | undefined = this.getLaunchConfigs(folder, type);
587+
if (configs && configs.length > 0) {
588+
const selectedConfig: any | undefined = configs.find((config: any) => config.name && config.name === configName);
589+
if (selectedConfig) {
590+
return true;
591+
}
592+
}
593+
return false;
594+
}
595+
586596
public async buildAndRun(textEditor: vscode.TextEditor): Promise<void> {
587597
// Turn off the debug mode.
588598
return this.buildAndDebug(textEditor, false);
@@ -627,33 +637,28 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
627637
});
628638
}
629639

630-
const debuggerEvent: string = DebuggerEvent.launchPlayButton;
631640
if (!selection) {
632-
Telemetry.logDebuggerEvent(debuggerEvent, { "debugType": debugModeOn ? "debug" : "run", "folderMode": folder ? "folder" : "singleFile", "cancelled": "true" });
641+
Telemetry.logDebuggerEvent(DebuggerEvent.launchPlayButton, { "debugType": debugModeOn ? "debug" : "run", "folderMode": folder ? "folder" : "singleFile", "cancelled": "true" });
633642
return; // User canceled it.
634643
}
635644

636645
if (this.isClConfiguration(selection.label) && this.showErrorIfClNotAvailable(selection.label)) {
637646
return;
638647
}
639648

640-
// Resolve config before start debugging.
641-
let resolvedConfig: vscode.DebugConfiguration | undefined | null;
642-
if (selection.configuration && selection.configuration.type) {
643-
resolvedConfig = await this.resolveDebugConfiguration(folder, selection.configuration);
644-
if (resolvedConfig) {
645-
resolvedConfig = await this.resolveDebugConfigurationWithSubstitutedVariables(folder, resolvedConfig);
646-
}
647-
}
648-
if (resolvedConfig) {
649-
await this.resolvePreLaunchTask(folder, resolvedConfig, debuggerEvent, debugModeOn);
650-
await this.startDebugging(folder, resolvedConfig, debuggerEvent, debugModeOn);
651-
}
649+
// Keep track of the entry point where the debug has been selected, for telemetry purposes.
650+
selection.configuration.debuggerEvent = DebuggerEvent.launchPlayButton;
651+
// startDebugging will trigger a call to resolveDebugConfiguration.
652+
await vscode.debug.startDebugging(folder, selection.configuration, {noDebug: !debugModeOn});
653+
652654
}
653655

654-
private async resolvePreLaunchTask(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration, debuggerEvent: string, debugModeOn: boolean = true): Promise<void> {
656+
private async resolvePreLaunchTask(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration, debugModeOn: boolean = true): Promise<void> {
655657
const debugType: string = debugModeOn ? "debug" : "run";
656658
const folderMode: string = folder ? "folder" : "singleFile";
659+
// if configuration.debuggerEvent === undefined, it means this configuration is already defined in launch.json and is shown in debugPanel.
660+
// All the other configs that are selected by user, have a debuggerEvent element.
661+
const debuggerEvent: string = configuration.debuggerEvent ? configuration.debuggerEvent : DebuggerEvent.debugPanel;
657662
if (configuration.preLaunchTask) {
658663
try {
659664
if (folder) {
@@ -673,31 +678,14 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
673678
}
674679
}
675680

676-
private async startDebugging(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration, debuggerEvent: string, debugModeOn: boolean = true): Promise<void> {
681+
private async sendDebugTelemetry(folder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration, debugModeOn: boolean = true): Promise<void> {
677682
const debugType: string = debugModeOn ? "debug" : "run";
678683
const folderMode: string = folder ? "folder" : "singleFile";
679-
if (!folder) {
680-
// In case of singleFile, remove the preLaunch task.
681-
configuration.preLaunchTask = undefined;
682-
}
683-
try {
684-
// Check if the debug configuration exists in launch.json.
685-
await cppBuildTaskProvider.checkDebugConfigExists(configuration.name);
686-
try {
687-
await vscode.debug.startDebugging(folder, configuration.name, {noDebug: !debugModeOn});
688-
Telemetry.logDebuggerEvent(debuggerEvent, { "debugType": debugType, "folderMode": folderMode, "config": "launchConfig", "success": "true" });
689-
} catch (e) {
690-
Telemetry.logDebuggerEvent(debuggerEvent, { "debugType": debugType, "folderMode": folderMode, "config": "launchConfig", "success": "false" });
691-
}
692-
} catch (e) {
693-
try {
694-
await vscode.debug.startDebugging(folder, configuration, {noDebug: !debugModeOn});
695-
Telemetry.logDebuggerEvent(debuggerEvent, { "debugType": debugType, "folderMode": folderMode, "config": "noLaunchConfig", "success": "true" });
696-
} catch (e) {
697-
Telemetry.logDebuggerEvent(debuggerEvent, { "debugType": debugType, "folderMode": folderMode, "config": "noLaunchConfig", "success": "false" });
698-
}
699-
}
684+
const configMode: string = this.checkDebugConfigExists(configuration.name) ? "launchConfig" : "noLaunchConfig";
685+
const debuggerEvent: string = configuration.debuggerEvent ? configuration.debuggerEvent : "unknownDebuggerEvent";
686+
Telemetry.logDebuggerEvent(debuggerEvent, { "debugType": debugType, "folderMode": folderMode, "config": configMode });
700687
}
688+
701689
}
702690

703691
export interface IConfigurationAssetProvider {

Extension/src/LanguageServer/cppBuildTaskProvider.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,13 @@ export class CppBuildTaskProvider implements TaskProvider {
282282
}
283283

284284
public async runBuildTask(taskLabel: string): Promise<void> {
285-
const buildTasks: CppBuildTask[] = await this.getTasks(true);
286-
const task: CppBuildTask | undefined = buildTasks.find(task => task.name === taskLabel);
285+
let task: CppBuildTask | undefined;
286+
const configuredBuildTasks: CppBuildTask[] = await this.getJsonTasks();
287+
task = configuredBuildTasks.find(task => task.name === taskLabel);
288+
if (!task) {
289+
const detectedBuildTasks: CppBuildTask[] = await this.getTasks(true);
290+
task = detectedBuildTasks.find(task => task.name === taskLabel);
291+
}
287292
if (!task) {
288293
throw new Error("Failed to find task in runBuildTask()");
289294
} else {
@@ -304,24 +309,6 @@ export class CppBuildTaskProvider implements TaskProvider {
304309
}
305310
}
306311

307-
public async checkDebugConfigExists(configName: string): Promise<void> {
308-
const launchJsonPath: string | undefined = this.getLaunchJsonPath();
309-
if (!launchJsonPath) {
310-
throw new Error("Failed to get launchJsonPath in checkDebugConfigExists()");
311-
}
312-
313-
const rawLaunchJson: any = await this.getRawLaunchJson();
314-
// Ensure that the debug configurations exists in the user's launch.json. Config will not be found otherwise.
315-
if (!rawLaunchJson || !rawLaunchJson.configurations) {
316-
throw new Error(`Configuration '${configName}' is missing in 'launch.json'.`);
317-
}
318-
const selectedConfig: any | undefined = rawLaunchJson.configurations.find((config: any) => config.name && config.name === configName);
319-
if (!selectedConfig) {
320-
throw new Error(`Configuration '${configName}' is missing in 'launch.json'.`);
321-
}
322-
return;
323-
}
324-
325312
private getLaunchJsonPath(): string | undefined {
326313
return util.getJsonPath("launch.json");
327314
}

0 commit comments

Comments
 (0)