Skip to content

Commit e35634b

Browse files
committed
Set GDB path explicitly in VS Code debug configurations
Closes #1277
1 parent 7ce368c commit e35634b

File tree

5 files changed

+126
-25
lines changed

5 files changed

+126
-25
lines changed

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, getEvaluatedCustomEnv, 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 = getEvaluatedCustomEnv();
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/helpers.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,11 @@ export function startedInDebugMode() {
350350
}
351351
return false;
352352
}
353+
354+
/**
355+
* This constant is set to the string `.exe` on Windows, and to the empty string
356+
* otherwise. It is intended for computingk executable filenames conveniently by
357+
* simply appending the constant at the end of the name and obtaining a result
358+
* compatible with the running platform.
359+
*/
360+
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 & 2 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;

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)