Skip to content

Commit 6b862ba

Browse files
committed
Merge branch 'topic/mi-debugger-path' into 'master'
Set GDB path explicitly in VS Code debug configurations Closes #1277 See merge request eng/ide/ada_language_server!1483
2 parents 7ce368c + 7469b4f commit 6b862ba

File tree

9 files changed

+161
-79
lines changed

9 files changed

+161
-79
lines changed

integration/vscode/ada/src/ExtensionState.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createClient } from './clients';
44
import { GnatTaskProvider } from './gnatTaskProvider';
55
import { GprTaskProvider } from './gprTaskProvider';
66
import { registerTaskProviders } from './taskProviders';
7-
import { getCustomEnvSettingName } from './helpers';
7+
import { TERMINAL_ENV_SETTING_NAME } from './helpers';
88

99
/**
1010
* This class encapsulates all state that should be maintained throughout the
@@ -106,9 +106,7 @@ export class ExtensionState {
106106
// React to changes made in the environment variables, showing
107107
// a popup to reload the VS Code window and thus restart the
108108
// Ada extension.
109-
const env_config_name = getCustomEnvSettingName();
110-
111-
if (e.affectsConfiguration(env_config_name)) {
109+
if (e.affectsConfiguration(TERMINAL_ENV_SETTING_NAME)) {
112110
void this.showReloadWindowPopup();
113111
}
114112
};

integration/vscode/ada/src/clients.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { existsSync } from 'fs';
22
import * as vscode from 'vscode';
33
import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';
44
import { logger } from './extension';
5-
import { logErrorAndThrow, setCustomEnvironment } from './helpers';
5+
import { logErrorAndThrow, setTerminalEnvironment } from './helpers';
66

77
export function createClient(
88
context: vscode.ExtensionContext,
@@ -63,7 +63,7 @@ export function createClient(
6363
// Copy this process's environment
6464
const serverEnv: NodeJS.ProcessEnv = { ...process.env };
6565
// Set custom environment
66-
setCustomEnvironment(serverEnv);
66+
setTerminalEnvironment(serverEnv);
6767

6868
logger.debug(`Environment for ${name}:`);
6969
for (const key in serverEnv) {

integration/vscode/ada/src/debugConfigProvider.ts

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import assert from 'assert';
22
import * as vscode from 'vscode';
33
import { adaExtState } from './extension';
4-
import { AdaMain, getAdaMains, getProjectFile } from './helpers';
4+
import { AdaMain, exe, getAdaMains, getEvaluatedTerminalEnv, getProjectFile } from './helpers';
55
import { BUILD_PROJECT_TASK_NAME, getBuildTaskName } from './taskProviders';
6+
import path from 'path';
7+
import { existsSync } from 'fs';
68

79
/**
810
* Ada Configuration for a debug session
911
*/
10-
interface AdaConfig extends vscode.DebugConfiguration {
12+
export interface AdaConfig extends vscode.DebugConfiguration {
1113
MIMode: string;
1214
program: string;
1315
cwd?: string;
@@ -20,8 +22,25 @@ interface AdaConfig extends vscode.DebugConfiguration {
2022
ignoreFailures: boolean;
2123
}[];
2224
processId?: string;
25+
miDebuggerPath?: string;
2326
}
2427

28+
export const adaDynamicDebugConfigProvider = {
29+
async provideDebugConfigurations(
30+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
31+
_folder?: vscode.WorkspaceFolder
32+
): Promise<vscode.DebugConfiguration[]> {
33+
const quickpick = await createQuickPickItems('Build & Debug');
34+
35+
const configs: AdaConfig[] = quickpick.flatMap((i) => {
36+
assert(i.adaMain);
37+
return [initializeConfig(i.adaMain), createAttachConfig(i.adaMain)];
38+
});
39+
40+
return configs;
41+
},
42+
};
43+
2544
/**
2645
* Initialize debugging support for Ada projects.
2746
*
@@ -48,35 +67,66 @@ export function initializeDebugging(ctx: vscode.ExtensionContext) {
4867
ctx.subscriptions.push(
4968
vscode.debug.registerDebugConfigurationProvider(
5069
'ada',
51-
{
52-
async provideDebugConfigurations(
53-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
54-
_folder: vscode.WorkspaceFolder | undefined
55-
): Promise<vscode.DebugConfiguration[]> {
56-
const quickpick = await createQuickPickItems('Build & Debug');
57-
58-
const configs: AdaConfig[] = quickpick.flatMap((i) => {
59-
assert(i.adaMain);
60-
return [initializeConfig(i.adaMain), createAttachConfig(i.adaMain)];
61-
});
62-
63-
return configs;
64-
},
65-
},
70+
adaDynamicDebugConfigProvider,
6671
// The 'Dynamic' trigger type only works if the package.json lists
6772
// "onDebugDynamicConfigurations:ada" as part of the
6873
// activationEvents.
6974
vscode.DebugConfigurationProviderTriggerKind.Dynamic
7075
)
7176
);
7277

73-
// TODO it is also possible to register another provider with trigger kind
74-
// 'Dynamic', however the role of such a provider is unclear. In practical
75-
// experiments it ends up never being called. The above provider is enough
76-
// to make it possible to launch debug sessions without a launch.json.
77-
7878
return provider;
7979
}
80+
81+
let cachedGdb: string | undefined | null = undefined;
82+
83+
/**
84+
*
85+
* @returns the full path to the `gdb` executable, taking into consideration the
86+
* `PATH` variable in the `terminal.integrated.env.*` setting if set. Otherwise,
87+
* the `PATH` variable of the current process environment is considered.
88+
*
89+
* The current process environment is unlikely to change during the lifetime of
90+
* the extension, and we already prompt the User to reload the window in case
91+
* the `terminal.integrated.env.*` variables change. For this reason, we compute
92+
* the value only on the first call, and cache it for subsequent calls to return
93+
* it efficiently.
94+
*/
95+
function getOrFindGdb(): string | undefined {
96+
if (cachedGdb == undefined) {
97+
/**
98+
* If undefined yet, try to compute it.
99+
*/
100+
const env = getEvaluatedTerminalEnv();
101+
let pathVal: string;
102+
if (env && 'PATH' in env) {
103+
pathVal = env.PATH;
104+
} else if ('PATH' in process.env) {
105+
pathVal = process.env.PATH ?? '';
106+
} else {
107+
pathVal = '';
108+
}
109+
110+
const gdb = pathVal
111+
.split(path.delimiter)
112+
.map<string>((v) => path.join(v, 'gdb' + exe))
113+
.find(existsSync);
114+
115+
if (gdb) {
116+
// Found
117+
cachedGdb = gdb;
118+
return cachedGdb;
119+
} else {
120+
// Not found. Assign null to cache to avoid recomputing at every call.
121+
cachedGdb = null;
122+
}
123+
}
124+
125+
// When returning, coerce null to undefined because the distinction doesn't
126+
// matter on the caller side.
127+
return cachedGdb ?? undefined;
128+
}
129+
80130
/**
81131
* Initialize a debug configuration based on 'cppdbg' for the given executable
82132
* if specified. Otherwise the program field includes
@@ -109,6 +159,7 @@ function initializeConfig(main: AdaMain, name?: string): AdaConfig {
109159
MIMode: 'gdb',
110160
preLaunchTask: main ? getBuildTaskName(main) : BUILD_PROJECT_TASK_NAME,
111161
setupCommands: setupCmd,
162+
miDebuggerPath: getOrFindGdb(),
112163
};
113164

114165
return config;
@@ -364,5 +415,6 @@ function createAttachConfig(adaMain: AdaMain): AdaConfig {
364415
* to trigger an unwanted rebuild, so we don't set a preLaunchTask.
365416
*/
366417
// preLaunchTask: adaMain ? getBuildTaskName(adaMain) : BUILD_PROJECT_TASK_NAME,
418+
miDebuggerPath: getOrFindGdb(),
367419
};
368420
}

integration/vscode/ada/src/extension.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import { initializeDebugging } from './debugConfigProvider';
3030
import { initializeTestView } from './gnattest';
3131
import {
3232
assertSupportedEnvironments,
33-
getCustomEnvSettingName,
34-
getEvaluatedCustomEnv,
33+
TERMINAL_ENV_SETTING_NAME,
34+
getEvaluatedTerminalEnv,
3535
startedInDebugMode,
3636
} from './helpers';
3737

@@ -127,15 +127,15 @@ async function activateExtension(context: vscode.ExtensionContext) {
127127
assertSupportedEnvironments(logger);
128128

129129
// Log the environment that the extension (and all VS Code) will be using
130-
const customEnv = getEvaluatedCustomEnv();
130+
const customEnv = getEvaluatedTerminalEnv();
131131
if (customEnv && Object.keys(customEnv).length > 0) {
132-
logger.info(`Custom environment variables from ${getCustomEnvSettingName()}`);
132+
logger.info(`Custom environment variables from ${TERMINAL_ENV_SETTING_NAME}`);
133133
for (const varName in customEnv) {
134134
const varValue: string = customEnv[varName];
135135
logger.info(`${varName}=${varValue}`);
136136
}
137137
} else {
138-
logger.debug('No custom environment variables set in %s', getCustomEnvSettingName());
138+
logger.debug('No custom environment variables set in %s', TERMINAL_ENV_SETTING_NAME);
139139
}
140140

141141
// Create the Ada and GPR clients.

integration/vscode/ada/src/helpers.ts

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -109,39 +109,33 @@ export function substituteVariables(str: string, recursive = false): string {
109109
return str;
110110
}
111111

112-
/*
113-
Environment setting helper functions
114-
*/
115-
116-
export function getCustomEnv() {
117-
const env_config_name = getCustomEnvSettingName();
112+
/**
113+
* Name of the `terminal.integrated.env.*` setting applicable to the current platform.
114+
*/
115+
export const TERMINAL_ENV_SETTING_NAME =
116+
'terminal.integrated.env.' +
117+
(platform() == 'darwin' ? 'osx' : platform() == 'win32' ? 'windows' : 'linux');
118118

119+
/**
120+
*
121+
* @returns the value of the applicable `terminal.integrated.env.*` setting,
122+
* without evaluation of macros such as `${env:...}`.
123+
*/
124+
export function getTerminalEnv() {
119125
const custom_env = vscode.workspace
120126
.getConfiguration()
121-
.get<{ [name: string]: string }>(env_config_name);
127+
.get<{ [name: string]: string }>(TERMINAL_ENV_SETTING_NAME);
122128

123129
return custom_env;
124130
}
125131

126-
export function getCustomEnvSettingName() {
127-
const user_platform = platform();
128-
let env_config_name = 'terminal.integrated.env';
129-
130-
switch (user_platform) {
131-
case 'darwin':
132-
env_config_name += '.osx';
133-
break;
134-
case 'win32':
135-
env_config_name += '.windows';
136-
break;
137-
default:
138-
env_config_name += '.linux';
139-
}
140-
return env_config_name;
141-
}
142-
143-
export function getEvaluatedCustomEnv() {
144-
const custom_env = getCustomEnv();
132+
/**
133+
*
134+
* @returns the value of the applicable `terminal.integrated.env.*` setting,
135+
* after evaluation of macros such as `${env:...}`.
136+
*/
137+
export function getEvaluatedTerminalEnv() {
138+
const custom_env = getTerminalEnv();
145139

146140
if (custom_env) {
147141
for (const var_name in custom_env) {
@@ -160,17 +154,13 @@ export function getEvaluatedCustomEnv() {
160154
* Read the environment variables specified in the vscode setting
161155
* `terminal.integrated.env.<os>` and set them in the given ProcessEnv object.
162156
*
163-
* If no targetEnv is given, `process.env` is used as a target environment.
157+
* The targetEnv can be `process.env` to apply the changes to the environment of
158+
* the running process.
164159
*/
165-
export function setCustomEnvironment(targetEnv?: NodeJS.ProcessEnv) {
166-
if (!targetEnv) {
167-
targetEnv = process.env;
168-
}
169-
160+
export function setTerminalEnvironment(targetEnv: NodeJS.ProcessEnv) {
170161
// Retrieve the user's custom environment variables if specified in their
171-
// settings/workspace: we'll then launch any child process with this custom
172-
// environment
173-
const custom_env = getEvaluatedCustomEnv();
162+
// settings/workspace
163+
const custom_env = getEvaluatedTerminalEnv();
174164

175165
if (custom_env) {
176166
for (const var_name in custom_env) {
@@ -183,7 +173,7 @@ export function setCustomEnvironment(targetEnv?: NodeJS.ProcessEnv) {
183173
export function assertSupportedEnvironments(mainChannel: winston.Logger) {
184174
// Get the ALS environment variable from the custom environment, or from the
185175
// process environment
186-
const customEnv = getEvaluatedCustomEnv();
176+
const customEnv = getEvaluatedTerminalEnv();
187177
const als = customEnv?.ALS ?? process.env.ALS;
188178
if (als) {
189179
// The User provided an external ALS executable. Do not perform any
@@ -350,3 +340,11 @@ export function startedInDebugMode() {
350340
}
351341
return false;
352342
}
343+
344+
/**
345+
* This constant is set to the string `.exe` on Windows, and to the empty string
346+
* otherwise. It is intended for computingk executable filenames conveniently by
347+
* simply appending the constant at the end of the name and obtaining a result
348+
* compatible with the running platform.
349+
*/
350+
export const exe: '.exe' | '' = process.platform == 'win32' ? '.exe' : '';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import assert from 'assert';
2+
import { suite, test } from 'mocha';
3+
import { AdaConfig, adaDynamicDebugConfigProvider } from '../../../src/debugConfigProvider';
4+
import { activate } from '../utils';
5+
6+
suite('Debug Configurations', function () {
7+
this.beforeAll(async () => {
8+
await activate();
9+
});
10+
11+
test('GDB path is explicitely set in offered debug config', async () => {
12+
const firstConfig = (await adaDynamicDebugConfigProvider.provideDebugConfigurations()).at(
13+
0
14+
) as AdaConfig;
15+
16+
assert.notEqual(firstConfig.miDebuggerPath, undefined);
17+
});
18+
19+
test('GDB path is the same for all configs', async () => {
20+
const configs =
21+
(await adaDynamicDebugConfigProvider.provideDebugConfigurations()) as AdaConfig[];
22+
23+
assert(configs.length > 1);
24+
const miDebuggerPath = configs.at(0)?.miDebuggerPath;
25+
assert.notEqual(miDebuggerPath, '');
26+
for (let i = 0; i < configs.length; i++) {
27+
const c = configs[i];
28+
assert.equal(c.miDebuggerPath, miDebuggerPath);
29+
}
30+
});
31+
32+
test('Two debug configs per main are proposed', async () => {
33+
const configs =
34+
(await adaDynamicDebugConfigProvider.provideDebugConfigurations()) as AdaConfig[];
35+
const expected = `
36+
Ada: Debug main - src/main1.adb
37+
Ada: Attach debugger to running process - src/main1.adb
38+
Ada: Debug main - src/test.adb
39+
Ada: Attach debugger to running process - src/test.adb`.trim();
40+
assert.equal(configs.map((v) => `${v.name}`).join('\n'), expected);
41+
});
42+
});

integration/vscode/ada/test/suite/general/tasks.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { existsSync } from 'fs';
33
import { suite, test } from 'mocha';
44
import * as vscode from 'vscode';
55
import { adaExtState } from '../../../src/extension';
6-
import { getProjectFile } from '../../../src/helpers';
6+
import { exe, getProjectFile } from '../../../src/helpers';
77
import {
88
CustomTaskDefinition,
99
PROJECT_FROM_CONFIG,
1010
createAdaTaskProvider,
1111
createSparkTaskProvider,
1212
} from '../../../src/taskProviders';
13-
import { activate, exe } from '../utils';
13+
import { activate } from '../utils';
1414

1515
suite('GPR Tasks Provider', function () {
1616
let projectPath: string;
@@ -125,7 +125,6 @@ ada: Build and run main - src/test.adb - kind: buildAndRunMain`.trim();
125125
const status = await runTaskAndGetResult(task);
126126
assert.equal(status, 0);
127127

128-
console.info(`cwd=${process.cwd()}`);
129128
/**
130129
* Check that the executable is produced. The project defines a
131130
* different name for the executable produced by main1.adb.

integration/vscode/ada/test/suite/utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,3 @@ export function runMochaTestsuite(suiteName: string, suiteDirectory: string) {
120120
}
121121
});
122122
}
123-
export const exe = process.platform == 'win32' ? '.exe' : '';

0 commit comments

Comments
 (0)