Skip to content

Commit a886a10

Browse files
committed
Improved variable resolution and error handling
1 parent 1441b68 commit a886a10

File tree

5 files changed

+210
-91
lines changed

5 files changed

+210
-91
lines changed

arduino-debugger-extension/src/browser/arduino-variable-resolver.ts

Lines changed: 126 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser';
33
import { injectable, inject } from 'inversify';
44
import URI from '@theia/core/lib/common/uri';
5+
import { MessageService } from '@theia/core/lib/common/message-service';
6+
import { ApplicationShell, Navigatable } from '@theia/core/lib/browser';
7+
import { FileStat, FileSystem } from '@theia/filesystem/lib/common';
8+
import { WorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution';
59
import { BoardsServiceClientImpl } from 'arduino-ide-extension/lib/browser/boards/boards-service-client-impl';
610
import { BoardsService, ToolLocations } from 'arduino-ide-extension/lib/common/protocol/boards-service';
7-
import { WorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution';
811

912
@injectable()
1013
export class ArduinoVariableResolver implements VariableContribution {
@@ -18,64 +21,134 @@ export class ArduinoVariableResolver implements VariableContribution {
1821
@inject(WorkspaceVariableContribution)
1922
protected readonly workspaceVars: WorkspaceVariableContribution;
2023

24+
@inject(ApplicationShell)
25+
protected readonly applicationShell: ApplicationShell;
26+
27+
@inject(FileSystem)
28+
protected readonly fileSystem: FileSystem;
29+
30+
@inject(MessageService)
31+
protected readonly messageService: MessageService
32+
2133
registerVariables(variables: VariableRegistry): void {
2234
variables.registerVariable(<Variable>{
23-
name: `boardTools`,
24-
description: "Provides paths and access to board specific tooling",
35+
name: 'boardTools',
36+
description: 'Provides paths and access to board specific tooling',
2537
resolve: this.resolveBoardTools.bind(this),
2638
});
2739
variables.registerVariable(<Variable>{
28-
name: "board",
29-
description: "Provides details about the currently selected board",
40+
name: 'board',
41+
description: 'Provides details about the currently selected board',
3042
resolve: this.resolveBoard.bind(this),
3143
});
32-
3344
variables.registerVariable({
34-
name: "sketchBinary",
35-
description: "Path to the sketch's binary file",
45+
name: 'sketchBinary',
46+
description: 'Path to the sketch\'s binary file',
3647
resolve: this.resolveSketchBinary.bind(this)
3748
});
3849
}
3950

40-
// TODO: this function is a total hack. Instead of botching around with URI's it should ask something on the backend
41-
// that properly udnerstands the filesystem.
42-
protected async resolveSketchBinary(context?: URI, argument?: string, configurationSection?: string): Promise<Object> {
43-
let sketchPath = argument || this.workspaceVars.getResourceUri()!.path.toString();
44-
return sketchPath.substring(0, sketchPath.length - 3) + "arduino.samd.arduino_zero_edbg.elf";
51+
protected resolveSketchBinary(context?: URI, argument?: string, configurationSection?: string): Promise<Object | undefined> {
52+
if (argument) {
53+
return this.resolveBinaryWithHint(argument);
54+
}
55+
const resourceUri = this.workspaceVars.getResourceUri();
56+
if (resourceUri) {
57+
return this.resolveBinaryWithHint(resourceUri.toString());
58+
}
59+
for (const tabBar of this.applicationShell.mainAreaTabBars) {
60+
if (tabBar.currentTitle && Navigatable.is(tabBar.currentTitle.owner)) {
61+
const uri = tabBar.currentTitle.owner.getResourceUri();
62+
if (uri) {
63+
return this.resolveBinaryWithHint(uri.toString());
64+
}
65+
}
66+
}
67+
this.messageService.error('No sketch available. Please open a sketch to start debugging.');
68+
return Promise.resolve(undefined);
69+
}
70+
71+
private async resolveBinaryWithHint(hint: string): Promise<string | undefined> {
72+
const fileStat = await this.fileSystem.getFileStat(hint);
73+
if (!fileStat) {
74+
this.messageService.error('Cannot find sketch binary: ' + hint);
75+
return undefined;
76+
}
77+
if (!fileStat.isDirectory && fileStat.uri.endsWith('.elf')) {
78+
return fileStat.uri;
79+
}
80+
81+
let parent: FileStat | undefined;
82+
let prefix: string | undefined;
83+
let suffix: string;
84+
if (fileStat.isDirectory) {
85+
parent = fileStat;
86+
} else {
87+
const uri = new URI(fileStat.uri);
88+
parent = await this.fileSystem.getFileStat(uri.parent.toString());
89+
prefix = uri.path.name;
90+
}
91+
const { boardsConfig } = this.boardsServiceClient;
92+
if (boardsConfig && boardsConfig.selectedBoard && boardsConfig.selectedBoard.fqbn) {
93+
suffix = boardsConfig.selectedBoard.fqbn.replace(/:/g, '.') + '.elf';
94+
} else {
95+
suffix = '.elf';
96+
}
97+
if (parent && parent.children) {
98+
let bin: FileStat | undefined;
99+
if (prefix) {
100+
bin = parent.children.find(c => c.uri.startsWith(prefix!) && c.uri.endsWith(suffix));
101+
}
102+
if (!bin) {
103+
bin = parent.children.find(c => c.uri.endsWith(suffix));
104+
}
105+
if (!bin && suffix.length > 4) {
106+
bin = parent.children.find(c => c.uri.endsWith('.elf'));
107+
}
108+
if (bin) {
109+
return bin.uri;
110+
}
111+
}
112+
this.messageService.error('Cannot find sketch binary: ' + hint);
113+
return undefined;
45114
}
46115

47-
protected async resolveBoard(context?: URI, argument?: string, configurationSection?: string): Promise<Object> {
116+
protected async resolveBoard(context?: URI, argument?: string, configurationSection?: string): Promise<string | undefined> {
48117
const { boardsConfig } = this.boardsServiceClient;
49118
if (!boardsConfig || !boardsConfig.selectedBoard) {
50-
throw new Error('No boards selected. Please select a board.');
119+
this.messageService.error('No boards selected. Please select a board.');
120+
return undefined;
51121
}
52122

53-
if (!argument || argument === "fqbn") {
123+
if (!argument || argument === 'fqbn') {
54124
return boardsConfig.selectedBoard.fqbn!;
55125
}
56-
if (argument === "name") {
126+
if (argument === 'name') {
57127
return boardsConfig.selectedBoard.name;
58128
}
59129

60-
const details = await this.boardsService.detail({id: boardsConfig.selectedBoard.fqbn!});
130+
const details = await this.boardsService.detail({ id: boardsConfig.selectedBoard.fqbn! });
61131
if (!details.item) {
62-
throw new Error("Cannot get board details");
132+
this.messageService.error('Details of the selected boards are not available.');
133+
return undefined;
63134
}
64-
if (argument === "openocd-debug-file") {
135+
if (argument === 'openocd-debug-file') {
65136
return details.item.locations!.debugScript;
66137
}
67138

68139
return boardsConfig.selectedBoard.fqbn!;
69140
}
70141

71-
protected async resolveBoardTools(context?: URI, argument?: string, configurationSection?: string): Promise<Object> {
142+
protected async resolveBoardTools(context?: URI, argument?: string, configurationSection?: string): Promise<string | undefined> {
72143
const { boardsConfig } = this.boardsServiceClient;
73144
if (!boardsConfig || !boardsConfig.selectedBoard) {
74-
throw new Error('No boards selected. Please select a board.');
145+
this.messageService.error('No boards selected. Please select a board.');
146+
return undefined;
75147
}
76-
const details = await this.boardsService.detail({id: boardsConfig.selectedBoard.fqbn!});
148+
const details = await this.boardsService.detail({ id: boardsConfig.selectedBoard.fqbn! });
77149
if (!details.item) {
78-
throw new Error("Cannot get board details")
150+
this.messageService.error('Details of the selected boards are not available.');
151+
return undefined;
79152
}
80153

81154
let toolLocations: { [name: string]: ToolLocations } = {};
@@ -84,17 +157,37 @@ export class ArduinoVariableResolver implements VariableContribution {
84157
})
85158

86159
switch (argument) {
87-
case "openocd":
88-
return toolLocations["openocd"].main;
89-
case "openocd-scripts":
90-
return toolLocations["openocd"].scripts;
91-
case "objdump":
92-
return toolLocations["arm-none-eabi-gcc"].objdump;
93-
case "gdb":
94-
return toolLocations["arm-none-eabi-gcc"].gdb;
160+
case 'openocd': {
161+
const openocd = toolLocations['openocd'];
162+
if (openocd) {
163+
return openocd.main;
164+
}
165+
this.messageService.error('Unable to find debugging tool: openocd');
166+
return undefined;
167+
}
168+
case 'openocd-scripts': {
169+
const openocd = toolLocations['openocd'];
170+
return openocd ? openocd.scripts : undefined;
171+
}
172+
case 'objdump': {
173+
const gcc = Object.keys(toolLocations).find(key => key.endsWith('gcc'));
174+
if (gcc) {
175+
return toolLocations[gcc].objdump;
176+
}
177+
this.messageService.error('Unable to find debugging tool: objdump');
178+
return undefined;
179+
}
180+
case 'gdb': {
181+
const gcc = Object.keys(toolLocations).find(key => key.endsWith('gcc'));
182+
if (gcc) {
183+
return toolLocations[gcc].gdb;
184+
}
185+
this.messageService.error('Unable to find debugging tool: gdb');
186+
return undefined;
187+
}
95188
}
96189

97190
return boardsConfig.selectedBoard.name;
98191
}
99192

100-
}
193+
}

arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,59 @@
11
import { injectable } from 'inversify';
22
import { DebugAdapterContribution, DebugAdapterExecutable, DebugAdapterSessionFactory } from '@theia/debug/lib/common/debug-model';
3-
import { DebugConfiguration } from "@theia/debug/lib/common/debug-configuration";
4-
import { MaybePromise } from "@theia/core/lib/common/types";
5-
import { IJSONSchema, IJSONSchemaSnippet } from "@theia/core/lib/common/json-schema";
3+
import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration';
4+
import { MaybePromise } from '@theia/core/lib/common/types';
5+
import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema';
66
import * as path from 'path';
77

88
@injectable()
99
export class ArduinoDebugAdapterContribution implements DebugAdapterContribution {
1010

11-
type = "arduino";
11+
type = 'arduino';
1212

13-
label = "Arduino";
13+
label = 'Arduino';
1414

15-
languages = ["c", "cpp", "ino"];
15+
languages = ['c', 'cpp', 'ino'];
1616

1717
debugAdapterSessionFactory?: DebugAdapterSessionFactory;
1818

19-
getSchemaAttributes?(): MaybePromise<IJSONSchema[]> {
19+
getSchemaAttributes(): MaybePromise<IJSONSchema[]> {
2020
return [
2121
{
22-
"required": [
23-
"program"
22+
'required': [
23+
'program'
2424
],
25-
"properties": {
26-
"sketch": {
27-
"type": "string",
28-
"description": "path to the sketch root ino file",
29-
"default": "${file}",
25+
'properties': {
26+
'sketch': {
27+
'type': 'string',
28+
'description': 'path to the sketch root ino file',
29+
'default': '${file}',
3030
},
31-
"fqbn": {
32-
"type": "string",
33-
"description": "Fully-qualified board name to debug on",
34-
"default": ""
31+
'fqbn': {
32+
'type': 'string',
33+
'description': 'Fully-qualified board name to debug on',
34+
'default': ''
3535
},
36-
"runToMain": {
37-
"description": "If enabled the debugger will run until the start of the main function.",
38-
"type": "boolean",
39-
"default": false
36+
'runToMain': {
37+
'description': 'If enabled the debugger will run until the start of the main function.',
38+
'type': 'boolean',
39+
'default': false
4040
},
41-
"verbose": {
42-
"type": "boolean",
43-
"description": "Produce verbose log output",
44-
"default": false
41+
'verbose': {
42+
'type': 'boolean',
43+
'description': 'Produce verbose log output',
44+
'default': false
4545
},
46-
"debugDebugAdapter": {
47-
"type": "boolean",
48-
"description": "Start the debug adapter in debug mode (with --inspect-brk)",
49-
"default": false
46+
'debugDebugAdapter': {
47+
'type': 'boolean',
48+
'description': 'Start the debug adapter in debug mode (with --inspect-brk)',
49+
'default': false
5050
},
5151
}
5252
}
5353
]
5454
}
5555

56-
getConfigurationSnippets?(): MaybePromise<IJSONSchemaSnippet[]> {
56+
getConfigurationSnippets(): MaybePromise<IJSONSchemaSnippet[]> {
5757
return []
5858
}
5959

@@ -65,53 +65,53 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution
6565
args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]);
6666

6767
return {
68-
command: "node",
68+
command: 'node',
6969
args: args,
7070
}
7171
}
7272

73-
provideDebugConfigurations?(workspaceFolderUri?: string): MaybePromise<DebugConfiguration[]> {
73+
provideDebugConfigurations(workspaceFolderUri?: string): MaybePromise<DebugConfiguration[]> {
7474
return [
7575
<DebugConfiguration>{
7676
name: this.label,
7777
type: this.type,
78-
request: "launch",
79-
sketch: "${file}",
78+
request: 'launch',
79+
sketch: '${file}',
8080
},
8181
<DebugConfiguration>{
82-
name: this.label + " (explicit)",
82+
name: this.label + ' (explicit)',
8383
type: this.type,
84-
request: "launch",
84+
request: 'launch',
8585

86-
program: "${sketchBinary}",
87-
objdump: "${boardTools:objdump}",
88-
gdb: "${boardTools:gdb}",
89-
gdbServer: "${boardTools:openocd}",
90-
gdbServerArguments: ["-s", "${boardTools:openocd-scripts}", "--file", "${board:openocd-debug-file}"],
86+
program: '${sketchBinary}',
87+
objdump: '${boardTools:objdump}',
88+
gdb: '${boardTools:gdb}',
89+
gdbServer: '${boardTools:openocd}',
90+
gdbServerArguments: ['-s', '${boardTools:openocd-scripts}', '--file', '${board:openocd-debug-file}'],
9191

9292
runToMain: false,
9393
verbose: false,
9494
}
9595
];
9696
}
9797

98-
async resolveDebugConfiguration?(config: DebugConfiguration, workspaceFolderUri?: string): Promise<DebugConfiguration> {
98+
async resolveDebugConfiguration(config: DebugConfiguration, workspaceFolderUri?: string): Promise<DebugConfiguration> {
9999
// if program is present we expect to have an explicit config here
100100
if (!!config.program) {
101101
return config;
102102
}
103103

104-
let sketchBinary = "${sketchBinary}"
105-
if (config.sketch !== "${file}") {
106-
sketchBinary = "${sketchBinary:" + config.sketch + "}";
104+
let sketchBinary = '${sketchBinary}'
105+
if (typeof config.sketch === 'string' && config.sketch.indexOf('${') < 0) {
106+
sketchBinary = '${sketchBinary:' + config.sketch + '}';
107107
}
108108
const res: ActualDebugConfig = {
109109
...config,
110110

111-
objdump: "${boardTools:objdump}",
112-
gdb: "${boardTools:gdb}",
113-
gdbServer: "${boardTools:openocd}",
114-
gdbServerArguments: ["-s", "${boardTools:openocd-scripts}", "--file", "${board:openocd-debug-file}"],
111+
objdump: '${boardTools:objdump}',
112+
gdb: '${boardTools:gdb}',
113+
gdbServer: '${boardTools:openocd}',
114+
gdbServerArguments: ['-s', '${boardTools:openocd-scripts}', '--file', '${board:openocd-debug-file}'],
115115
program: sketchBinary
116116
}
117117
return res;

0 commit comments

Comments
 (0)