Skip to content

Commit 8c61615

Browse files
Add 'Build & Run with GNATemulator' task and CodeLens
When we have a cross/bare-metal project we now propose a 'Build & Run with GNATemulator' tasks/CodeLens, if GNATemulator for the given target is available. For eng/ide/ada_language_server#1375
1 parent 843915d commit 8c61615

File tree

5 files changed

+215
-73
lines changed

5 files changed

+215
-73
lines changed

integration/vscode/ada/src/AdaCodeLensProvider.ts

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ import {
1111
commands,
1212
Uri,
1313
} from 'vscode';
14-
import { CMD_BUILD_AND_DEBUG_MAIN, CMD_BUILD_AND_RUN_MAIN, CMD_SPARK_PROVE_SUBP } from './commands';
15-
import { envHasExec, getMains, getSymbols } from './helpers';
14+
import {
15+
CMD_BUILD_AND_DEBUG_MAIN,
16+
CMD_BUILD_AND_RUN_GNATEMULATOR,
17+
CMD_BUILD_AND_RUN_MAIN,
18+
CMD_SPARK_PROVE_SUBP,
19+
} from './commands';
20+
import { envHasExec, getSymbols } from './helpers';
21+
import { adaExtState } from './extension';
1622

1723
export class AdaCodeLensProvider implements CodeLensProvider {
1824
onDidChangeCodeLenses?: Event<void> | undefined;
@@ -28,46 +34,61 @@ export class AdaCodeLensProvider implements CodeLensProvider {
2834
/**
2935
* For main procedures, provide Run and Debug CodeLenses.
3036
*/
31-
const res1 = getMains().then((mains) => {
32-
if (
33-
mains.some(
34-
(m) =>
35-
// Here we go through the Uri class to benefit from the normalization
36-
// of path casing on Windows. See Uri.fsPath documentation.
37-
Uri.file(m).fsPath == document.uri.fsPath,
38-
)
39-
) {
40-
// It's a main file, so let's offer Run and Debug actions on the main subprogram
41-
return symbols.then((symbols) => {
42-
const functions = symbols.filter((s) => s.kind == SymbolKind.Function);
43-
if (functions.length > 0) {
44-
/**
45-
* We choose to provide the CodeLenses on the first
46-
* subprogram of the file. It may be possible that the
47-
* main subprogram is not the first one, but that's an
48-
* unlikely scenario that we choose not to handle for
49-
* the moment.
50-
*/
51-
return [
52-
new CodeLens(functions[0].range, {
53-
command: CMD_BUILD_AND_RUN_MAIN,
54-
title: '$(run) Run',
55-
arguments: [document.uri],
56-
}),
57-
// TODO implement this command
58-
new CodeLens(functions[0].range, {
59-
command: CMD_BUILD_AND_DEBUG_MAIN,
60-
title: '$(debug-alt-small) Debug',
61-
arguments: [document.uri],
62-
}),
63-
];
64-
} else {
65-
return [];
66-
}
67-
});
68-
} else {
69-
return [];
70-
}
37+
const res1 = adaExtState.getTargetPrefix().then((targetPrefix) => {
38+
return adaExtState.getMains().then((mains) => {
39+
if (
40+
mains.some(
41+
(m) =>
42+
// Here we go through the Uri class to benefit from the normalization
43+
// of path casing on Windows. See Uri.fsPath documentation.
44+
Uri.file(m).fsPath == document.uri.fsPath,
45+
)
46+
) {
47+
// It's a main file, so let's offer Run and Debug actions on the main subprogram
48+
return symbols.then((symbols) => {
49+
const functions = symbols.filter((s) => s.kind == SymbolKind.Function);
50+
if (functions.length > 0) {
51+
/**
52+
* We choose to provide the CodeLenses on the first
53+
* subprogram of the file. It may be possible that the
54+
* main subprogram is not the first one, but that's an
55+
* unlikely scenario that we choose not to handle for
56+
* the moment.
57+
*/
58+
let codeLenses = [
59+
new CodeLens(functions[0].range, {
60+
command: CMD_BUILD_AND_RUN_MAIN,
61+
title: '$(run) Run',
62+
arguments: [document.uri],
63+
}),
64+
new CodeLens(functions[0].range, {
65+
command: CMD_BUILD_AND_DEBUG_MAIN,
66+
title: '$(debug-alt-small) Debug',
67+
arguments: [document.uri],
68+
}),
69+
];
70+
71+
// It's not a native project: provide a 'Run with GNATemulator' CodeLens
72+
// if GNATemulator for the given target is present in the user's env.
73+
if (targetPrefix && envHasExec(targetPrefix + '-gnatemu')) {
74+
codeLenses = codeLenses.concat([
75+
new CodeLens(functions[0].range, {
76+
command: CMD_BUILD_AND_RUN_GNATEMULATOR,
77+
title: '$(run) Run with GNATemulator',
78+
arguments: [document.uri],
79+
}),
80+
]);
81+
}
82+
83+
return codeLenses;
84+
} else {
85+
return [];
86+
}
87+
});
88+
} else {
89+
return [];
90+
}
91+
});
7192
});
7293

7394
let res2;

integration/vscode/ada/src/ExtensionState.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,17 @@ export class ExtensionState {
311311
return this.cachedProjectUri;
312312
}
313313

314+
/**
315+
* Returns true if the loaded project is a native project, false if
316+
* it's a cross/bare-metal one.
317+
*
318+
* @returns a boolean indicating if the loaded project is a native one.
319+
*/
320+
public async isNativeProject(): Promise<boolean> {
321+
const targetPrefix = await this.getTargetPrefix();
322+
return targetPrefix == '' || targetPrefix === undefined;
323+
}
324+
314325
/**
315326
* Returns the target prefix that should be used when spawning tools
316327
* like gnat, gcc or gdb.
@@ -321,16 +332,29 @@ export class ExtensionState {
321332
* @returns the target prefix
322333
*/
323334
public async getTargetPrefix(): Promise<string> {
324-
if (!this.cachedTargetPrefix) {
335+
if (this.cachedTargetPrefix === undefined) {
325336
// Get the compiler driver's path from the Compiler.Driver project
326-
// attribute, and delete the last bit to get the prefix
327-
const driverPath = (await this.getProjectAttributeValue(
328-
'driver',
329-
'compiler',
330-
'ada',
331-
)) as string;
332-
const driver = path.basename(driverPath);
333-
this.cachedTargetPrefix = driver.substring(0, driver.lastIndexOf('-'));
337+
// attribute, and delete the last bit to get the prefix.
338+
// We get an exception when the attribute is not defined, which can
339+
// happen when there is no available toolchain for the project's target:
340+
// in that case, consider the project as a native one.
341+
try {
342+
const driverPath = (await this.getProjectAttributeValue(
343+
'driver',
344+
'compiler',
345+
'ada',
346+
)) as string;
347+
logger.info(`Got Project.Compiler.Driver ("ada") = ${driverPath}`);
348+
const driver = path.basename(driverPath);
349+
this.cachedTargetPrefix = driver.substring(0, driver.lastIndexOf('-'));
350+
logger.info(`Computed target prefix: ${this.cachedTargetPrefix}`);
351+
} catch (err) {
352+
const errMessage =
353+
err instanceof Error ? err.message : typeof err === 'string' ? err : '';
354+
logger.warn(`Failed to get Project.Compiler.Driver ("ada"): ${errMessage}`);
355+
logger.warn('Assuming empty target prefix');
356+
this.cachedTargetPrefix = '';
357+
}
334358
}
335359

336360
return this.cachedTargetPrefix;

integration/vscode/ada/src/commands.ts

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import {
1818
TASK_PROVE_SUPB_PLAIN_NAME,
1919
TASK_TYPE_SPARK,
2020
findBuildAndRunTask,
21-
getBuildAndRunTasks,
21+
getTasksWithPrefix,
2222
getConventionalTaskLabel,
2323
isFromWorkspace,
2424
workspaceTasksFirst,
25+
getBuildAndRunTaskName,
2526
} from './taskProviders';
2627
import { createHelloWorldProject, walkthroughStartDebugging } from './walkthrough';
2728

@@ -43,6 +44,16 @@ export const CMD_BUILD_AND_RUN_MAIN = 'ada.buildAndRunMain';
4344
*/
4445
export const CMD_BUILD_AND_DEBUG_MAIN = 'ada.buildAndDebugMain';
4546

47+
/**
48+
* Identifier for a hidden command used for building and running a project main,
49+
* using GNATemulator.
50+
* The command accepts a parameter which is the URI of the main source file.
51+
* It is triggered by CodeLenses provided by the extension.
52+
*
53+
* @see {@link buildAndRunMainWithGNATemulator}
54+
*/
55+
export const CMD_BUILD_AND_RUN_GNATEMULATOR = 'ada.buildAndRunGNATemulator';
56+
4657
/**
4758
* Identifier for a hidden command that returns an array of strings constituting
4859
* the -P and -X project and scenario arguments.
@@ -127,6 +138,12 @@ export function registerCommands(context: vscode.ExtensionContext, clients: Exte
127138
context.subscriptions.push(
128139
vscode.commands.registerCommand(CMD_BUILD_AND_DEBUG_MAIN, buildAndDebugSpecifiedMain),
129140
);
141+
context.subscriptions.push(
142+
vscode.commands.registerCommand(
143+
CMD_BUILD_AND_RUN_GNATEMULATOR,
144+
buildAndRunMainWithGNATemulator,
145+
),
146+
);
130147

131148
context.subscriptions.push(
132149
vscode.commands.registerCommand(CMD_GPR_PROJECT_ARGS, gprProjectArgs),
@@ -241,7 +258,7 @@ let lastUsedTaskInfo: { source: string; name: string } | undefined;
241258
* @returns the TaskExecution corresponding to the task.
242259
*/
243260
async function buildAndRunMainLast() {
244-
const buildAndRunTasks = await getBuildAndRunTasks();
261+
const buildAndRunTasks = await getTasksWithPrefix(getBuildAndRunTaskName());
245262
if (lastUsedTaskInfo) {
246263
const matchingTasks = buildAndRunTasks.filter(matchesLastUsedTask);
247264
assert(matchingTasks.length <= 1);
@@ -305,7 +322,7 @@ async function buildAndRunMainAsk() {
305322
],
306323
};
307324
}
308-
const adaTasksMain = await getBuildAndRunTasks();
325+
const adaTasksMain = await getTasksWithPrefix(getBuildAndRunTaskName());
309326

310327
if (adaTasksMain.length > 0) {
311328
const tasksFromWorkspace = adaTasksMain.filter(isFromWorkspace);
@@ -555,17 +572,23 @@ export async function checkSrcDirectories(atStartup = false, displayYesNoPopup =
555572
* displayed.
556573
*
557574
* @param main - a URI of a document
575+
* @param useGNATemulator - whether the main should be ran through
576+
* GNATemulator.
558577
*/
559-
async function buildAndRunSpecifiedMain(main: vscode.Uri): Promise<void> {
578+
async function buildAndRunSpecifiedMain(
579+
main: vscode.Uri,
580+
useGNATemulator: boolean = false,
581+
): Promise<void> {
560582
const adaMain = await findAdaMain(main.fsPath);
561583
if (adaMain) {
562-
const task = await findBuildAndRunTask(adaMain);
584+
const task = await findBuildAndRunTask(adaMain, useGNATemulator);
563585
if (task) {
564586
lastUsedTaskInfo = { source: task.source, name: task.name };
565587
await vscode.tasks.executeTask(task);
566588
} else {
589+
const taskLabel = useGNATemulator ? 'Build and Run GNATemulator' : 'Build and Run';
567590
void vscode.window.showErrorMessage(
568-
`Could not find the 'Build and Run' task for the project main ` +
591+
`Could not find the '${taskLabel}' task for the project main ` +
569592
`${adaMain.mainRelPath()}`,
570593
{ modal: true },
571594
);
@@ -579,6 +602,18 @@ async function buildAndRunSpecifiedMain(main: vscode.Uri): Promise<void> {
579602
}
580603
}
581604

605+
/*
606+
* This is a command handler that builds and runs the main given as parameter,
607+
* using GNATemulator to run the executable.
608+
* If the given URI does not match one of the project Mains an error is
609+
* displayed.
610+
*
611+
* @param main - a URI of a document
612+
*/
613+
async function buildAndRunMainWithGNATemulator(main: vscode.Uri): Promise<void> {
614+
return buildAndRunSpecifiedMain(main, true);
615+
}
616+
582617
/**
583618
* This is a command handler that builds the main given as parameter and starts
584619
* a debug session on that main. If the given URI does not match one of the

0 commit comments

Comments
 (0)