diff --git a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts index 806cff4e..eb3028ef 100644 --- a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts +++ b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts @@ -1,13 +1,8 @@ import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser'; import { injectable, inject } from 'inversify'; -import URI from '@theia/core/lib/common/uri'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { ApplicationShell, Navigatable } from '@theia/core/lib/browser'; -import { FileStat, FileSystem } from '@theia/filesystem/lib/common'; -import { WorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution'; import { BoardsServiceClientImpl } from 'arduino-ide-extension/lib/browser/boards/boards-service-client-impl'; -import { BoardsService, ToolLocations } from 'arduino-ide-extension/lib/common/protocol/boards-service'; @injectable() export class ArduinoVariableResolver implements VariableContribution { @@ -15,179 +10,37 @@ export class ArduinoVariableResolver implements VariableContribution { @inject(BoardsServiceClientImpl) protected readonly boardsServiceClient: BoardsServiceClientImpl; - @inject(BoardsService) - protected readonly boardsService: BoardsService; - - @inject(WorkspaceVariableContribution) - protected readonly workspaceVars: WorkspaceVariableContribution; - - @inject(ApplicationShell) - protected readonly applicationShell: ApplicationShell; - - @inject(FileSystem) - protected readonly fileSystem: FileSystem; - @inject(MessageService) protected readonly messageService: MessageService registerVariables(variables: VariableRegistry): void { variables.registerVariable({ - name: 'boardTools', - description: 'Provides paths and access to board specific tooling', - resolve: this.resolveBoardTools.bind(this), - }); - variables.registerVariable({ - name: 'board', - description: 'Provides details about the currently selected board', - resolve: this.resolveBoard.bind(this), + name: 'fqbn', + description: 'Qualified name of the selected board', + resolve: this.resolveFqbn.bind(this), }); variables.registerVariable({ - name: 'sketchBinary', - description: 'Path to the sketch\'s binary file', - resolve: this.resolveSketchBinary.bind(this) + name: 'port', + description: 'Selected upload port', + resolve: this.resolvePort.bind(this) }); } - protected resolveSketchBinary(context?: URI, argument?: string, configurationSection?: string): Promise { - if (argument) { - return this.resolveBinaryWithHint(argument); - } - const resourceUri = this.workspaceVars.getResourceUri(); - if (resourceUri) { - return this.resolveBinaryWithHint(resourceUri.toString()); - } - for (const tabBar of this.applicationShell.mainAreaTabBars) { - if (tabBar.currentTitle && Navigatable.is(tabBar.currentTitle.owner)) { - const uri = tabBar.currentTitle.owner.getResourceUri(); - if (uri) { - return this.resolveBinaryWithHint(uri.toString()); - } - } - } - this.messageService.error('No sketch available. Please open a sketch to start debugging.'); - return Promise.resolve(undefined); - } - - private async resolveBinaryWithHint(hint: string): Promise { - const fileStat = await this.fileSystem.getFileStat(hint); - if (!fileStat) { - this.messageService.error('Cannot find sketch binary: ' + hint); - return undefined; - } - if (!fileStat.isDirectory && fileStat.uri.endsWith('.elf')) { - return this.fileSystem.getFsPath(fileStat.uri); - } - - let parent: FileStat | undefined; - let prefix: string | undefined; - let suffix: string; - if (fileStat.isDirectory) { - parent = fileStat; - } else { - const uri = new URI(fileStat.uri); - parent = await this.fileSystem.getFileStat(uri.parent.toString()); - prefix = uri.path.name; - } - const { boardsConfig } = this.boardsServiceClient; - if (boardsConfig && boardsConfig.selectedBoard && boardsConfig.selectedBoard.fqbn) { - suffix = boardsConfig.selectedBoard.fqbn.replace(/:/g, '.') + '.elf'; - } else { - suffix = '.elf'; - } - if (parent && parent.children) { - let bin: FileStat | undefined; - if (prefix) { - bin = parent.children.find(c => c.uri.startsWith(prefix!) && c.uri.endsWith(suffix)); - } - if (!bin) { - bin = parent.children.find(c => c.uri.endsWith(suffix)); - } - if (!bin && suffix.length > 4) { - bin = parent.children.find(c => c.uri.endsWith('.elf')); - } - if (bin) { - return this.fileSystem.getFsPath(bin.uri); - } - } - this.messageService.error('Cannot find sketch binary: ' + hint); - return undefined; - } - - protected async resolveBoard(context?: URI, argument?: string, configurationSection?: string): Promise { + protected async resolveFqbn(): Promise { const { boardsConfig } = this.boardsServiceClient; if (!boardsConfig || !boardsConfig.selectedBoard) { - this.messageService.error('No boards selected. Please select a board.'); + this.messageService.error('No board selected. Please select a board for debugging.'); return undefined; } - - if (!argument || argument === 'fqbn') { - return boardsConfig.selectedBoard.fqbn!; - } - if (argument === 'name') { - return boardsConfig.selectedBoard.name; - } - - const details = await this.boardsService.detail({ id: boardsConfig.selectedBoard.fqbn! }); - if (!details.item) { - this.messageService.error('Details of the selected boards are not available.'); - return undefined; - } - if (argument === 'openocd-debug-file') { - return details.item.locations!.debugScript; - } - - return boardsConfig.selectedBoard.fqbn!; + return boardsConfig.selectedBoard.fqbn; } - protected async resolveBoardTools(context?: URI, argument?: string, configurationSection?: string): Promise { + protected async resolvePort(): Promise { const { boardsConfig } = this.boardsServiceClient; - if (!boardsConfig || !boardsConfig.selectedBoard) { - this.messageService.error('No boards selected. Please select a board.'); + if (!boardsConfig || !boardsConfig.selectedPort) { return undefined; } - const details = await this.boardsService.detail({ id: boardsConfig.selectedBoard.fqbn! }); - if (!details.item) { - this.messageService.error('Details of the selected boards are not available.'); - return undefined; - } - - let toolLocations: { [name: string]: ToolLocations } = {}; - details.item.requiredTools.forEach(t => { - toolLocations[t.name] = t.locations!; - }) - - switch (argument) { - case 'openocd': { - const openocd = toolLocations['openocd']; - if (openocd) { - return openocd.main; - } - this.messageService.error('Unable to find debugging tool: openocd'); - return undefined; - } - case 'openocd-scripts': { - const openocd = toolLocations['openocd']; - return openocd ? openocd.scripts : undefined; - } - case 'objdump': { - const gcc = Object.keys(toolLocations).find(key => key.endsWith('gcc')); - if (gcc) { - return toolLocations[gcc].objdump; - } - this.messageService.error('Unable to find debugging tool: objdump'); - return undefined; - } - case 'gdb': { - const gcc = Object.keys(toolLocations).find(key => key.endsWith('gcc')); - if (gcc) { - return toolLocations[gcc].gdb; - } - this.messageService.error('Unable to find debugging tool: gdb'); - return undefined; - } - } - - return undefined; + return boardsConfig.selectedPort.address; } } diff --git a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts index 910a506f..6176a507 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -1,38 +1,28 @@ -import { injectable } from 'inversify'; -import { DebugAdapterContribution, DebugAdapterExecutable, DebugAdapterSessionFactory } from '@theia/debug/lib/common/debug-model'; -import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration'; -import { MaybePromise } from '@theia/core/lib/common/types'; -import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema'; import * as path from 'path'; +import { injectable, inject } from 'inversify'; +import { DebugAdapterContribution, DebugAdapterExecutable } from '@theia/debug/lib/common/debug-model'; +import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration'; +import { IJSONSchema } from '@theia/core/lib/common/json-schema'; +import { ArduinoCli } from 'arduino-ide-extension/lib/node/arduino-cli'; @injectable() export class ArduinoDebugAdapterContribution implements DebugAdapterContribution { - type = 'arduino'; + readonly type = 'arduino'; + readonly label = 'Arduino'; + readonly languages = ['c', 'cpp', 'ino']; - label = 'Arduino'; + @inject(ArduinoCli) arduinoCli: ArduinoCli; - languages = ['c', 'cpp', 'ino']; - - debugAdapterSessionFactory?: DebugAdapterSessionFactory; - - getSchemaAttributes(): MaybePromise { + getSchemaAttributes(): IJSONSchema[] { return [ { - 'required': [ - 'program' - ], 'properties': { 'sketch': { 'type': 'string', 'description': 'path to the sketch root ino file', 'default': '${file}', }, - 'fqbn': { - 'type': 'string', - 'description': 'Fully-qualified board name to debug on', - 'default': '' - }, 'runToMain': { 'description': 'If enabled the debugger will run until the start of the main function.', 'type': 'boolean', @@ -53,66 +43,39 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution ] } - getConfigurationSnippets(): MaybePromise { - return [] - } - - provideDebugAdapterExecutable(config: DebugConfiguration): MaybePromise { - let args: string[] = []; - if (!!config.debugDebugAdapter) { - args.push('--inspect-brk') + provideDebugAdapterExecutable(config: DebugConfiguration): DebugAdapterExecutable { + const debugAdapterMain = path.join(__dirname, 'debug-adapter', 'main'); + if (config.debugDebugAdapter) { + return { + command: process.execPath, + args: ['--inspect-brk', debugAdapterMain], + } } - args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]); - return { - command: process.execPath, - args: args, + modulePath: debugAdapterMain, + args: [], } } - provideDebugConfigurations(workspaceFolderUri?: string): MaybePromise { + provideDebugConfigurations(): DebugConfiguration[] { return [ { name: this.label, type: this.type, - request: 'launch', - sketch: '${file}', - }, - { - name: this.label + ' (explicit)', - type: this.type, - request: 'launch', - - program: '${sketchBinary}', - objdump: '${boardTools:objdump}', - gdb: '${boardTools:gdb}', - gdbServer: '${boardTools:openocd}', - gdbServerArguments: ['-s', '${boardTools:openocd-scripts}', '--file', '${board:openocd-debug-file}'], - - runToMain: false, - verbose: false, + request: 'launch' } ]; } - async resolveDebugConfiguration(config: DebugConfiguration, workspaceFolderUri?: string): Promise { - // if program is present we expect to have an explicit config here - if (!!config.program) { - return config; - } - - let sketchBinary = '${sketchBinary}' - if (typeof config.sketch === 'string' && config.sketch.indexOf('${') < 0) { - sketchBinary = '${sketchBinary:' + config.sketch + '}'; - } + async resolveDebugConfiguration(config: DebugConfiguration): Promise { const res: ActualDebugConfig = { ...config, - - objdump: '${boardTools:objdump}', - gdb: '${boardTools:gdb}', - gdbServer: '${boardTools:openocd}', - gdbServerArguments: ['-s', '${boardTools:openocd-scripts}', '--file', '${board:openocd-debug-file}'], - program: sketchBinary + arduinoCli: await this.arduinoCli.getExecPath(), + fqbn: '${fqbn}', + uploadPort: '${port}' + } + if (!res.sketch) { + res.sketch = '${file}'; } return res; } @@ -120,18 +83,8 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution } interface ActualDebugConfig extends DebugConfiguration { - // path to the program to be launched - program: string - // path to gdb - gdb: string - // additional arguments to pass to GDB command line - gdbArguments?: string[] - // path to the gdb server - gdbServer: string - // additional arguments to pass to GDB server - gdbServerArguments: string[] - // path to objdump executable - objdump: string - // extra gdb commands to run after initialisation - initCommands?: string[] + arduinoCli?: string; + sketch?: string; + fqbn?: string; + uploadPort?: string; } diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts new file mode 100644 index 00000000..c53246fa --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -0,0 +1,19 @@ +import { DebugProtocol } from 'vscode-debugprotocol'; +import { GDBDebugSession } from 'cdt-gdb-adapter/dist/GDBDebugSession'; +import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import { ArduinoGDBBackend } from './arduino-gdb-backend'; + +export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { + arduinoCli?: string; + sketch?: string; + fqbn?: string; + uploadPort?: string; +} + +export class ArduinoDebugSession extends GDBDebugSession { + + protected createBackend(): GDBBackend { + return new ArduinoGDBBackend(); + } + +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts new file mode 100644 index 00000000..800472ce --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -0,0 +1,37 @@ +import * as path from 'path'; +import * as fs from 'arduino-ide-extension/lib/node/fs-extra' +import * as mi from './mi'; +import { spawn } from 'child_process'; +import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import { ArduinoLaunchRequestArguments } from './arduino-debug-session'; + +export class ArduinoGDBBackend extends GDBBackend { + + public spawn(requestArgs: ArduinoLaunchRequestArguments) { + if (!requestArgs.sketch) { + throw new Error('Missing argument: sketch'); + } + if (!requestArgs.fqbn) { + throw new Error('Missing argument: fqbn') + } + const sketchFS = fs.statSync(requestArgs.sketch); + const sketchDir = sketchFS.isFile() ? path.dirname(requestArgs.sketch) : requestArgs.sketch; + const command = requestArgs.arduinoCli || 'arduino-cli'; + const args = [ + 'debug', + '-p', requestArgs.uploadPort || 'none', + '-b', requestArgs.fqbn, + sketchDir + ]; + const proc = spawn(command, args); + this.proc = proc; + this.out = proc.stdin; + return this.parser.parse(proc.stdout); + } + + public pause() { + mi.sendExecInterrupt(this); + return true; + } + +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/main.ts b/arduino-debugger-extension/src/node/debug-adapter/main.ts index d0765327..59fa0ee9 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/main.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/main.ts @@ -1,34 +1,10 @@ -/* -* CMSIS Debug Adapter -* Copyright (c) 2019 Arm Limited -* -* The MIT License (MIT) -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ - import * as process from 'process'; import { logger } from 'vscode-debugadapter/lib/logger'; -import { CmsisDebugSession } from './cmsis-debug-session'; +import { ArduinoDebugSession } from './arduino-debug-session'; +import { DebugSession } from 'vscode-debugadapter'; process.on('uncaughtException', (err: any) => { logger.error(JSON.stringify(err)); }); -CmsisDebugSession.run(CmsisDebugSession); +DebugSession.run(ArduinoDebugSession);