Skip to content

Commit 065e198

Browse files
Add support for debugging through GNATemulator
A new 'ada.buildAndDebugGNATemulator' command has been added, with an associated CodeLens. For eng/ide/ada_language_server#1375
1 parent d1e94ee commit 065e198

File tree

5 files changed

+197
-18
lines changed

5 files changed

+197
-18
lines changed

integration/vscode/ada/src/AdaCodeLensProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Uri,
1313
} from 'vscode';
1414
import {
15+
CMD_BUILD_AND_DEBUG_GNATEMULATOR,
1516
CMD_BUILD_AND_DEBUG_MAIN,
1617
CMD_BUILD_AND_RUN_GNATEMULATOR,
1718
CMD_BUILD_AND_RUN_MAIN,
@@ -77,6 +78,11 @@ export class AdaCodeLensProvider implements CodeLensProvider {
7778
title: '$(run) Run with GNATemulator',
7879
arguments: [document.uri],
7980
}),
81+
new CodeLens(functions[0].range, {
82+
command: CMD_BUILD_AND_DEBUG_GNATEMULATOR,
83+
title: '$(debug-alt-small) Debug with GNATemulator',
84+
arguments: [document.uri],
85+
}),
8086
]);
8187
}
8288

integration/vscode/ada/src/ExtensionState.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class ExtensionState {
6969
cachedMains: string[] | undefined;
7070
cachedExecutables: string[] | undefined;
7171
cachedAlireTomls: vscode.Uri[] | undefined;
72+
cachedDebugServerAddress: string | undefined | null;
7273
cachedGdb: string | undefined | null = undefined;
7374

7475
private adaTaskProvider?: SimpleTaskProvider;
@@ -82,6 +83,7 @@ export class ExtensionState {
8283
this.cachedMains = undefined;
8384
this.cachedExecutables = undefined;
8485
this.cachedAlireTomls = undefined;
86+
this.cachedDebugServerAddress = undefined;
8587
this.cachedGdb = undefined;
8688
}
8789

@@ -370,6 +372,50 @@ export class ExtensionState {
370372
return this.cachedTargetPrefix;
371373
}
372374

375+
/**
376+
* Returns the debug server address that should be used when debugging executables
377+
* built for non-native projects.
378+
* This checks for the IDE'Program_Host project attribute if {@link forGNATemulator} is
379+
* set to false, and the Emulator'Debug_Port otherwise (GNATemulator project attribute).
380+
* @param forGNATemulator - Set it to true to retrieve the debug server address that should
381+
* be used by GNATemulator.
382+
* @returns the debug server address, as a string (e.g: 'localhost:1234')
383+
*/
384+
public async getDebugServerAddress(forGNATemulator = false): Promise<string | null> {
385+
type AttributeID = {
386+
name: string;
387+
package: string;
388+
};
389+
390+
if (this.cachedDebugServerAddress === undefined) {
391+
const attrID: AttributeID = {
392+
name: forGNATemulator ? 'Debug_Port' : 'Program_Host',
393+
package: forGNATemulator ? 'Emulator' : 'IDE',
394+
};
395+
try {
396+
const debugServerAddress = (await this.getProjectAttributeValue(
397+
attrID.name,
398+
attrID.package,
399+
)) as string;
400+
logger.info(`Got Project.${attrID.package}.${attrID.name} = ${debugServerAddress}`);
401+
this.cachedDebugServerAddress = forGNATemulator
402+
? `localhost:${debugServerAddress}`
403+
: debugServerAddress;
404+
logger.info(`Computed debug server address: ${this.cachedDebugServerAddress}`);
405+
} catch (err) {
406+
const errMessage =
407+
err instanceof Error ? err.message : typeof err === 'string' ? err : '';
408+
logger.warn(
409+
`Failed to get Project.${attrID.package}.${attrID.name}: ${errMessage}`,
410+
);
411+
logger.warn('Assuming there is no debug server address');
412+
this.cachedDebugServerAddress = null;
413+
}
414+
}
415+
416+
return this.cachedDebugServerAddress;
417+
}
418+
373419
/**
374420
* @returns the full path of the main project file from the ALS
375421
*/

integration/vscode/ada/src/commands.ts

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import {
2323
isFromWorkspace,
2424
workspaceTasksFirst,
2525
getBuildAndRunTaskName,
26+
getRunGNATemulatorTaskName,
27+
getBuildTaskName,
28+
runTaskAndGetResult,
2629
} from './taskProviders';
2730
import { createHelloWorldProject, walkthroughStartDebugging } from './walkthrough';
2831
import { loadGnatCoverageReport } from './gnattest';
@@ -55,6 +58,16 @@ export const CMD_BUILD_AND_DEBUG_MAIN = 'ada.buildAndDebugMain';
5558
*/
5659
export const CMD_BUILD_AND_RUN_GNATEMULATOR = 'ada.buildAndRunGNATemulator';
5760

61+
/**
62+
* Identifier for a hidden command used for building and debugging a project main,
63+
* using GNATemulator.
64+
* The command accepts a parameter which is the URI of the main source file.
65+
* It is triggered by CodeLenses provided by the extension.
66+
*
67+
* @see {@link buildAndDebugSpecifiedMain}
68+
*/
69+
export const CMD_BUILD_AND_DEBUG_GNATEMULATOR = 'ada.buildAndDebugGNATemulator';
70+
5871
/**
5972
* Identifier for a hidden command that returns an array of strings constituting
6073
* the -P and -X project and scenario arguments.
@@ -155,7 +168,12 @@ export function registerCommands(context: vscode.ExtensionContext, clients: Exte
155168
buildAndRunMainWithGNATemulator,
156169
),
157170
);
158-
171+
context.subscriptions.push(
172+
vscode.commands.registerCommand(
173+
CMD_BUILD_AND_DEBUG_GNATEMULATOR,
174+
buildAndDebugSpecifiedMainWithGNATemulator,
175+
),
176+
);
159177
context.subscriptions.push(
160178
vscode.commands.registerCommand(CMD_GPR_PROJECT_ARGS, gprProjectArgs),
161179
);
@@ -635,19 +653,25 @@ async function buildAndRunMainWithGNATemulator(main: vscode.Uri): Promise<void>
635653

636654
/**
637655
* This is a command handler that builds the main given as parameter and starts
638-
* a debug session on that main. If the given URI does not match one of the
639-
* project Mains an error is displayed.
656+
* a debug session on that main, running GNATemulator in debug mode before when
657+
* asked.
658+
* If the given URI does not match one of the project Mains an error is displayed.
640659
*
641660
* @param main - a URI of a document
661+
* @param useGNATemulator - whether the main should be ran through GNATemulator
642662
*/
643-
async function buildAndDebugSpecifiedMain(main: vscode.Uri): Promise<void> {
663+
async function buildAndDebugSpecifiedMain(
664+
main: vscode.Uri,
665+
useGNATemulator: boolean = false,
666+
): Promise<void> {
644667
function isMatchingConfig(cfg: vscode.DebugConfiguration, configToMatch: AdaConfig): boolean {
645668
return cfg.type == configToMatch.type && cfg.name == configToMatch.name;
646669
}
647670

648671
const wsFolder = vscode.workspace.getWorkspaceFolder(main);
649672
const adaMain = await findAdaMain(main.fsPath);
650673
const target = await adaExtState.getTargetPrefix();
674+
const debugServerAddress = await adaExtState.getDebugServerAddress(useGNATemulator);
651675
if (adaMain) {
652676
/**
653677
* The vscode API doesn't provide a way to list both automatically
@@ -657,7 +681,7 @@ async function buildAndDebugSpecifiedMain(main: vscode.Uri): Promise<void> {
657681
* the given main URI.
658682
*/
659683
// Create a launch config for this main to help with matching
660-
const configToMatch = initializeConfig(adaMain, target);
684+
const configToMatch = initializeConfig(adaMain, target, debugServerAddress);
661685
logger.debug('Debug config to match:\n' + JSON.stringify(configToMatch, null, 2));
662686

663687
let matchingConfig = undefined;
@@ -683,6 +707,38 @@ async function buildAndDebugSpecifiedMain(main: vscode.Uri): Promise<void> {
683707

684708
if (matchingConfig) {
685709
logger.debug('Found matching config: ' + JSON.stringify(matchingConfig, null, 2));
710+
711+
// Trying to debug via GNATemulator: run the main via GNATemulator in debug mode
712+
// before starting the debug session
713+
if (useGNATemulator) {
714+
const buildTaskName = getBuildTaskName(adaMain);
715+
const buildTasks = await getTasksWithPrefix(buildTaskName);
716+
if (buildTasks.length === 1) {
717+
const execStatus: number | undefined = await runTaskAndGetResult(buildTasks[0]);
718+
if (execStatus != 0) {
719+
const errorMsg = `Failed to build executable before launching GNATemulator`;
720+
logger.error(errorMsg);
721+
return;
722+
}
723+
logger.debug('Running GNATemulator before starting the debug configuration...');
724+
const runGNATemulatorTaskName = getRunGNATemulatorTaskName(adaMain, true);
725+
const runGNATemulatorForDebugTasks =
726+
await getTasksWithPrefix(runGNATemulatorTaskName);
727+
728+
if (runGNATemulatorForDebugTasks.length < 1) {
729+
const errorMsg = `Could not find '${runGNATemulatorTaskName}' task`;
730+
logger.error(errorMsg);
731+
void vscode.window.showErrorMessage(errorMsg, { modal: true });
732+
return;
733+
}
734+
await vscode.tasks.executeTask(runGNATemulatorForDebugTasks[0]);
735+
} else {
736+
const errorMsg = `Could not find '${buildTaskName}' task`;
737+
void vscode.window.showErrorMessage(errorMsg, { modal: true });
738+
logger.error(errorMsg);
739+
}
740+
}
741+
686742
const success = await vscode.debug.startDebugging(wsFolder, matchingConfig);
687743
if (!success) {
688744
void vscode.window.showErrorMessage(
@@ -706,6 +762,17 @@ async function buildAndDebugSpecifiedMain(main: vscode.Uri): Promise<void> {
706762
}
707763
}
708764

765+
/**
766+
* This is a command handler that builds the main given as parameter and starts
767+
* a debug session on that main, with GNATemulator.
768+
* If the given URI does not match one of the project Mains an error is displayed.
769+
*
770+
* @param main - a URI of a document
771+
*/
772+
async function buildAndDebugSpecifiedMainWithGNATemulator(main: vscode.Uri): Promise<void> {
773+
await buildAndDebugSpecifiedMain(main, true);
774+
}
775+
709776
/**
710777
* @returns an array of -P and -X project and scenario command lines arguments
711778
* for use with GPR-based tools.

integration/vscode/ada/src/debugConfigProvider.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import assert from 'assert';
22
import * as vscode from 'vscode';
33
import { adaExtState } from './extension';
44
import { AdaMain, getAdaMains, getProjectFile, getProjectFileRelPath } from './helpers';
5-
import { BUILD_PROJECT_TASK_NAME, getBuildTaskName } from './taskProviders';
5+
import { getBuildTaskName } from './taskProviders';
66

77
export const ADA_DEBUG_BACKEND_TYPE = 'cppdbg';
88

@@ -22,7 +22,9 @@ export interface AdaConfig extends vscode.DebugConfiguration {
2222
ignoreFailures: boolean;
2323
}[];
2424
processId?: string;
25+
preLaunchTask?: string;
2526
miDebuggerPath?: string;
27+
miDebuggerServerAddress?: string;
2628
}
2729

2830
export const adaDynamicDebugConfigProvider = {
@@ -32,8 +34,9 @@ export const adaDynamicDebugConfigProvider = {
3234
): Promise<AdaConfig[]> {
3335
const mains = await getAdaMains();
3436
const target = await adaExtState.getTargetPrefix();
37+
const debugServerAddress = await adaExtState.getDebugServerAddress();
3538
const configs: AdaConfig[] = mains.flatMap((m) => {
36-
return [initializeConfig(m, target), createAttachConfig(m, target)];
39+
return [initializeConfig(m, target, debugServerAddress), createAttachConfig(m, target)];
3740
});
3841
if (configs.length == 0) {
3942
/**
@@ -116,12 +119,22 @@ export function initializeDebugging(ctx: vscode.ExtensionContext): {
116119
* @param name - the full name of the configuration (optional). If provided,
117120
* this name short-circuits the automatic naming based on the 'main' or the
118121
* 'program' parameter.
122+
* @param debugServerAddress - the debug server address that should be used by the debugger
123+
* for remote debugging (equalivalent of 'target remote <debugServerAddress>' command for GDB).
124+
* Set it to null to debug the program directly on the host.
119125
* @returns an AdaConfig
120126
*/
121-
export function initializeConfig(main: AdaMain, target: string, name?: string): AdaConfig {
127+
export function initializeConfig(
128+
main: AdaMain,
129+
target: string,
130+
debugServerAddress: string | null,
131+
name?: string,
132+
): AdaConfig {
133+
const preLaunchTask = getBuildTaskName(main);
134+
122135
// TODO it would be nice if this and the package.json configuration snippet
123136
// were the same.
124-
const config: AdaConfig = {
137+
let config: AdaConfig = {
125138
type: ADA_DEBUG_BACKEND_TYPE,
126139
name: name ?? (main ? `Ada: Debug main - ${main.mainRelPath()}` : 'Ada: Debugger Launch'),
127140
request: 'launch',
@@ -133,11 +146,15 @@ export function initializeConfig(main: AdaMain, target: string, name?: string):
133146
externalConsole: false,
134147
args: [],
135148
MIMode: 'gdb',
136-
preLaunchTask: main ? getBuildTaskName(main) : BUILD_PROJECT_TASK_NAME,
149+
preLaunchTask: preLaunchTask,
137150
setupCommands: setupCmd,
138151
miDebuggerPath: adaExtState.getOrFindGdb(target),
139152
};
140153

154+
if (debugServerAddress) {
155+
config = { ...config, miDebuggerServerAddress: debugServerAddress };
156+
}
157+
141158
return config;
142159
}
143160

@@ -227,8 +244,9 @@ export class AdaInitialDebugConfigProvider implements vscode.DebugConfigurationP
227244
} else {
228245
const main = await getOrAskForProgram();
229246
const target = await adaExtState.getTargetPrefix();
247+
const debugServerAddress = await adaExtState.getDebugServerAddress();
230248
if (main) {
231-
return initializeConfig(main, target);
249+
return initializeConfig(main, target, debugServerAddress);
232250
}
233251
}
234252

0 commit comments

Comments
 (0)