From e189a8c33e3c5ea53f4c15d9b7722a215274c415 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Thu, 14 Nov 2019 11:17:12 +0100 Subject: [PATCH 01/44] Add theia/debug to build --- browser-app/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/browser-app/package.json b/browser-app/package.json index e6426c4f..e0d7e676 100644 --- a/browser-app/package.json +++ b/browser-app/package.json @@ -6,6 +6,7 @@ "dependencies": { "@theia/core": "next", "@theia/cpp": "next", + "@theia/debug": "next", "@theia/editor": "next", "@theia/file-search": "next", "@theia/filesystem": "next", From 3d6d2ce814567c835b7e749896180dc6104c54c1 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Fri, 15 Nov 2019 13:55:26 +0100 Subject: [PATCH 02/44] Ran first debugging session --- .vscode/arduino.json | 4 + .vscode/c_cpp_properties.json | 14 + .vscode/launch.json | 14 +- arduino-debugger-extension/package.json | 40 ++ .../src/common/index.ts | 0 .../src/common/openocd/gdb.cfg | 2 + .../src/common/openocd/openocd.cfg | 12 + .../arduino-debug-adapter-contribution.ts | 109 +++++ .../src/node/backend-module.ts | 7 + .../src/node/debug-adapter/abstract-server.ts | 151 +++++++ .../src/node/debug-adapter/cmsis-backend.ts | 39 ++ .../node/debug-adapter/cmsis-debug-session.ts | 388 ++++++++++++++++++ .../src/node/debug-adapter/index.ts | 34 ++ .../src/node/debug-adapter/mi.ts | 80 ++++ .../src/node/debug-adapter/openocd-server.ts | 51 +++ .../src/node/debug-adapter/port-scanner.ts | 192 +++++++++ .../src/node/debug-adapter/pyocd-server.ts | 75 ++++ .../src/node/debug-adapter/symbols.ts | 151 +++++++ arduino-debugger-extension/tsconfig.json | 31 ++ arduino-debugger-extension/tslint.json | 37 ++ browser-app/package.json | 5 +- package.json | 1 + 22 files changed, 1428 insertions(+), 9 deletions(-) create mode 100644 .vscode/arduino.json create mode 100644 .vscode/c_cpp_properties.json create mode 100644 arduino-debugger-extension/package.json create mode 100644 arduino-debugger-extension/src/common/index.ts create mode 100644 arduino-debugger-extension/src/common/openocd/gdb.cfg create mode 100644 arduino-debugger-extension/src/common/openocd/openocd.cfg create mode 100644 arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts create mode 100644 arduino-debugger-extension/src/node/backend-module.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/index.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/mi.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/symbols.ts create mode 100644 arduino-debugger-extension/tsconfig.json create mode 100644 arduino-debugger-extension/tslint.json diff --git a/.vscode/arduino.json b/.vscode/arduino.json new file mode 100644 index 00000000..513efb45 --- /dev/null +++ b/.vscode/arduino.json @@ -0,0 +1,4 @@ +{ + "board": "arduino:samd:arduino_zero_edbg", + "port": "/dev/cu.usbmodem141402" +} \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..3bf59290 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,14 @@ +{ + "configurations": [ + { + "name": "Mac", + "includePath": [ + "/Users/csweichel/Library/Arduino15/packages/arduino/tools/**", + "/Users/csweichel/Library/Arduino15/packages/arduino/hardware/samd/1.8.4/**" + ], + "forcedInclude": [ + "/Users/csweichel/Library/Arduino15/packages/arduino/hardware/samd/1.8.4/cores/arduino/Arduino.h" + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 2cf29886..28b2c89a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,6 +10,13 @@ "name": "Attach by Process ID", "processId": "${command:PickProcess}" }, + { + "type": "node", + "request": "launch", + "name": "Launch Electron Packager", + "program": "${workspaceRoot}/electron/packager/index.js", + "cwd": "${workspaceFolder}/electron/packager" + }, { "type": "node", "request": "launch", @@ -99,13 +106,6 @@ "smartStep": true, "internalConsoleOptions": "openOnSessionStart", "outputCapture": "std" - }, - { - "type": "node", - "request": "launch", - "name": "Packager", - "program": "${workspaceRoot}/electron/packager/index.js", - "cwd": "${workspaceFolder}/electron/packager" } ] } diff --git a/arduino-debugger-extension/package.json b/arduino-debugger-extension/package.json new file mode 100644 index 00000000..0eafe66c --- /dev/null +++ b/arduino-debugger-extension/package.json @@ -0,0 +1,40 @@ +{ + "name": "arduino-debugger-extension", + "version": "0.0.2", + "description": "An extension for debugging Arduino programs", + "license": "MIT", + "engines": { + "node": ">=10.10.0" + }, + "dependencies": { + "@theia/core": "next", + "@theia/debug": "next", + + "cdt-gdb-adapter": "^0.0.14-next.4783033.0", + "vscode-debugadapter": "^1.26.0", + "vscode-debugprotocol": "^1.26.0" + }, + "scripts": { + "prepare": "yarn run clean && yarn run build", + "clean": "rimraf lib", + "lint": "tslint -c ./tslint.json --project ./tsconfig.json", + "build": "tsc && yarn lint", + "watch": "tsc -w" + }, + "devDependencies": { + "rimraf": "^2.6.1", + "tslint": "^5.5.0", + "typescript": "3.5.1" + }, + "files": [ + "lib", + "src", + "build", + "data" + ], + "theiaExtensions": [ + { + "backend": "lib/node/backend-module" + } + ] +} \ No newline at end of file diff --git a/arduino-debugger-extension/src/common/index.ts b/arduino-debugger-extension/src/common/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/arduino-debugger-extension/src/common/openocd/gdb.cfg b/arduino-debugger-extension/src/common/openocd/gdb.cfg new file mode 100644 index 00000000..cdb6d76a --- /dev/null +++ b/arduino-debugger-extension/src/common/openocd/gdb.cfg @@ -0,0 +1,2 @@ +gdb_port 50000 +telnet_port 44444 \ No newline at end of file diff --git a/arduino-debugger-extension/src/common/openocd/openocd.cfg b/arduino-debugger-extension/src/common/openocd/openocd.cfg new file mode 100644 index 00000000..10d1382b --- /dev/null +++ b/arduino-debugger-extension/src/common/openocd/openocd.cfg @@ -0,0 +1,12 @@ +source [find interface/cmsis-dap.cfg] + +# chip name +set CHIPNAME at91samd21g18 +set ENDIAN little + +# choose a port here +set telnet_port 0 + +source [find target/at91samdXX.cfg] + +echo "GDB server started" \ No newline at end of file diff --git a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts new file mode 100644 index 00000000..7a6ad517 --- /dev/null +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -0,0 +1,109 @@ +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'; + +@injectable() +export class ArduinoDebugAdapterContribution implements DebugAdapterContribution { + type = "arduino"; + + label = "Arduino"; + + languages = ["c", "cpp", "ino"]; + + debugAdapterSessionFactory?: DebugAdapterSessionFactory; + + getSchemaAttributes?(): MaybePromise { + return [ + { + "required": [ + "program" + ], + "properties": { + "program": { + "type": "string", + "description": "Path to the program to be launched", + "default": "${workspaceFolder}/${command:askProgramPath}" + }, + "arguments": { + "type": "string", + "description": "Arguments for the program" + }, + "runToMain": { + "description": "If enabled the debugger will run until the start of the main function.", + "type": "boolean", + "default": false + }, + "gdb": { + "type": "string", + "description": "Path to gdb", + "default": "arm-none-eabi-gdb" + }, + "gdbArguments": { + "description": "Additional arguments to pass to GDB command line", + "type": "array", + "default": [] + }, + "gdbServer": { + "type": "string", + "description": "Path to gdb server", + "default": "pyocd" + }, + "gdbServerArguments": { + "description": "Additional arguments to pass to GDB server", + "type": "array", + "default": [] + }, + "objdump": { + "type": "string", + "description": "Path to objdump executable", + "default": "arm-none-eabi-objdump" + }, + "initCommands": { + "description": "Extra gdb commands to run after initialisation", + "type": "array", + "default": [] + }, + "verbose": { + "type": "boolean", + "description": "Produce verbose log output", + "default": "false" + }, + "debugDebugAdapter": { + "type": "boolean", + "description": "Start the debug adapter in debug mode (with --inspect-brk)", + "default": "false" + } + } + } + ] + } + + getConfigurationSnippets?(): MaybePromise { + return [] + } + + provideDebugAdapterExecutable?(config: DebugConfiguration): MaybePromise { + let args: string[] = []; + if (!!config.debugDebugAdapter) { + args.push('--inspect-brk') + } + args = args.concat([path.join(__dirname, 'debug-adapter', 'index.js')]); + + return { + command: "node", + args: args, + } + } + + provideDebugConfigurations?(workspaceFolderUri?: string): MaybePromise { + return []; + } + + resolveDebugConfiguration?(config: DebugConfiguration, workspaceFolderUri?: string): MaybePromise { + return config; + } + +} \ No newline at end of file diff --git a/arduino-debugger-extension/src/node/backend-module.ts b/arduino-debugger-extension/src/node/backend-module.ts new file mode 100644 index 00000000..f9e72972 --- /dev/null +++ b/arduino-debugger-extension/src/node/backend-module.ts @@ -0,0 +1,7 @@ +import { ContainerModule } from 'inversify'; +import { DebugAdapterContribution } from '@theia/debug/lib/common/debug-model'; +import { ArduinoDebugAdapterContribution } from './arduino-debug-adapter-contribution'; + +export default new ContainerModule((bind, unbind, isBound, rebind) => { + bind(DebugAdapterContribution).to(ArduinoDebugAdapterContribution).inSingletonScope(); +}); diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts new file mode 100644 index 00000000..0d8c369c --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts @@ -0,0 +1,151 @@ +/* +* CMSIS Debug Adapter +* Copyright (c) 2017-2019 Marcel Ball +* 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 { EOL } from 'os'; +import { spawn, ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; +import { dirname } from 'path'; +import { CmsisRequestArguments } from './cmsis-debug-session'; + +const TIMEOUT = 1000 * 10; // 10 seconds + +export abstract class AbstractServer extends EventEmitter { + + protected process?: ChildProcess; + protected outBuffer: string = ''; + protected errBuffer: string = ''; + protected launchResolve?: () => void; + protected launchReject?: (error: any) => void; + protected timer?: NodeJS.Timer; + + public spawn(args: CmsisRequestArguments): Promise { + return new Promise(async (resolve, reject) => { + this.launchResolve = resolve; + this.launchReject = reject; + + try { + this.timer = setTimeout(() => this.onSpawnError(new Error('Timeout waiting for gdb server to start')), TIMEOUT); + + const command = args.gdbServer || 'gdb-server'; + const serverArguments = await this.resolveServerArguments(args.gdbServerArguments); + this.process = spawn(command, serverArguments, { + cwd: dirname(command), + }); + + if (!this.process) { + throw new Error('Unable to spawn gdb server'); + } + + this.process.on('exit', this.onExit.bind(this)); + this.process.on('error', this.onSpawnError.bind(this)); + + if (this.process.stdout) { + this.process.stdout.on('data', this.onStdout.bind(this)); + } + if (this.process.stderr) { + this.process.stderr.on('data', this.onStderr.bind(this)); + } + } catch (error) { + this.onSpawnError(error); + } + }); + } + + public kill() { + if (this.process) { + this.process.kill('SIGINT'); + } + } + + protected async resolveServerArguments(serverArguments?: string[]): Promise { + return serverArguments || []; + } + + protected onExit(code: number, signal: string) { + this.emit('exit', code, signal); + + // Code can be undefined, null or 0 and we want to ignore those values + if (!!code) { + this.emit('error', `GDB server stopped unexpectedly with exit code ${code}`); + } + } + + protected onSpawnError(error: Error) { + if (this.launchReject) { + this.clearTimer(); + this.launchReject(error); + this.clearPromises(); + } + } + + protected onStdout(chunk: string | Buffer) { + this.onData(chunk, this.outBuffer, 'stdout'); + } + + protected onStderr(chunk: string | Buffer) { + this.onData(chunk, this.errBuffer, 'stderr'); + } + + protected onData(chunk: string | Buffer, buffer: string, event: string) { + buffer += typeof chunk === 'string' ? chunk + : chunk.toString('utf8'); + + const end = buffer.lastIndexOf('\n'); + if (end !== -1) { + const data = buffer.substring(0, end); + this.emit(event, data); + this.handleData(data); + buffer = buffer.substring(end + 1); + } + } + + protected handleData(data: string) { + if (this.launchResolve && this.serverStarted(data)) { + this.clearTimer(); + this.launchResolve(); + this.clearPromises(); + } + + if (this.serverError(data)) { + this.emit('error', data.split(EOL)[0]); + } + } + + protected clearTimer() { + if (this.timer) { + clearTimeout(this.timer); + this.timer = undefined; + } + } + + protected clearPromises() { + this.launchResolve = undefined; + this.launchReject = undefined; + } + + protected abstract serverStarted(data: string): boolean; + protected abstract serverError(data: string): boolean; +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts new file mode 100644 index 00000000..fd96566e --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts @@ -0,0 +1,39 @@ +/* +* 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 { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import * as mi from './mi'; + +export class CmsisBackend extends GDBBackend { + + public get isRunning(): boolean { + return !!this.out; + } + + public pause() { + mi.sendExecInterrupt(this); + return true; + } +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts new file mode 100644 index 00000000..c98879f5 --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -0,0 +1,388 @@ +/* +* 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 { normalize } from 'path'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { Logger, logger, InitializedEvent, OutputEvent, Scope, TerminatedEvent } from 'vscode-debugadapter'; +import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference, ObjectVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; +import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import { CmsisBackend } from './cmsis-backend'; +import { PyocdServer } from './pyocd-server'; +import { PortScanner } from './port-scanner'; +import { SymbolTable } from './symbols'; +import * as varMgr from 'cdt-gdb-adapter/dist/varManager'; +import * as mi from './mi'; + +export interface CmsisRequestArguments extends RequestArguments { + runToMain?: boolean; + gdbServer?: string; + gdbServerArguments?: string[]; + objdump?: string; +} + +const GLOBAL_HANDLE_ID = 0xFE; +const STATIC_HANDLES_START = 0x010000; +const STATIC_HANDLES_FINISH = 0x01FFFF; + +export class CmsisDebugSession extends GDBDebugSession { + + protected gdbServer = new PyocdServer(); + protected portScanner = new PortScanner(); + protected symbolTable!: SymbolTable; + protected globalHandle!: number; + + constructor() { + super(); + } + + protected createBackend(): GDBBackend { + return new CmsisBackend(); + } + + protected async launchRequest(response: DebugProtocol.LaunchResponse, args: CmsisRequestArguments): Promise { + try { + await this.runSession(args); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async attachRequest(response: DebugProtocol.AttachResponse, args: CmsisRequestArguments): Promise { + try { + await this.runSession(args); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise { + try { + await mi.sendExecContinue(this.gdb); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 100, err.message); + } + } + + protected async pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): Promise { + try { + await mi.sendExecInterrupt(this.gdb, args.threadId); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): Promise { + try { + this.globalHandle = this.frameHandles.create({ + threadId: -1, + frameId: -1 + }); + + return super.stackTraceRequest(response, args); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + try { + const frame: FrameVariableReference = { + type: 'frame', + frameHandle: args.frameId, + }; + const pins: ObjectVariableReference = { + type: "object", + varobjName: "__pins", + frameHandle: args.frameId, + } + + response.body = { + scopes: [ + new Scope('Pins', this.variableHandles.create(pins), false), + new Scope('Local', this.variableHandles.create(frame), false), + new Scope('Global', GLOBAL_HANDLE_ID, false), + new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false) + ], + }; + + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + try { + response.body = { + variables: new Array() + }; + + const ref = this.variableHandles.get(args.variablesReference); + + if (args.variablesReference === GLOBAL_HANDLE_ID) { + // Use hardcoded global handle to load and store global variables + response.body.variables = await this.getGlobalVariables(this.globalHandle); + } else if (args.variablesReference >= STATIC_HANDLES_START && args.variablesReference <= STATIC_HANDLES_FINISH) { + // Use STATIC_HANDLES_START to shift the framehandles back + const frameHandle = args.variablesReference - STATIC_HANDLES_START; + response.body.variables = await this.getStaticVariables(frameHandle); + } else if (ref && ref.type === 'frame') { + // List variables for current frame + response.body.variables = await this.handleVariableRequestFrame(ref); + } else if (ref && ref.type === 'object') { + // List data under any variable + response.body.variables = await this.handleVariableRequestObject(ref); + } + + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): Promise { + try { + if (args.context === 'repl') { + const command = args.expression; + const output = await mi.sendUserInput(this.gdb, command); + if (typeof output === 'undefined') { + response.body = { + result: '', + variablesReference: 0 + }; + } else { + response.body = { + result: JSON.stringify(output), + variablesReference: 0 + }; + } + + this.sendResponse(response); + } else { + return super.evaluateRequest(response, args); + } + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): Promise { + try { + this.stopSession(); + if (!args || !args.restart) { + this.sendEvent(new TerminatedEvent()); + } + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + private async runSession(args: CmsisRequestArguments): Promise { + logger.setup(args.verbose ? Logger.LogLevel.Verbose : Logger.LogLevel.Warn, args.logFile || false); + + this.gdb.on('consoleStreamOutput', (output, category) => this.sendEvent(new OutputEvent(output, category))); + this.gdb.on('execAsync', (resultClass, resultData) => this.handleGDBAsync(resultClass, resultData)); + this.gdb.on('notifyAsync', (resultClass, resultData) => this.handleGDBNotify(resultClass, resultData)); + + // gdb server has main info channel on stderr + this.gdbServer.on('stderr', data => { + this.sendEvent(new OutputEvent(data, 'stdout')) + }); + this.gdbServer.on('error', message => { + this.sendEvent(new TerminatedEvent()); + throw message; + }); + + try { + this.symbolTable = new SymbolTable(args.program, args.objdump); + await this.symbolTable.loadSymbols(); + } catch (error) { + this.sendEvent(new OutputEvent(`Unable to load debug symbols: ${error.message}`)); + } + + const port = await this.portScanner.findFreePort(); + if (!port) { + throw new Error('Unable to find a free port to use for debugging'); + } + this.sendEvent(new OutputEvent(`Selected port ${port} for debugging`)); + + const remote = `localhost:${port}`; + + // Set gdb arguments + if (!args.gdbArguments) { + args.gdbArguments = []; + } + args.gdbArguments.push('-q', args.program); + + // Set gdb server arguments + if (!args.gdbServerArguments) { + args.gdbServerArguments = []; + } + args.gdbServerArguments.push('--port', port.toString()); + + // Start gdb client and server + this.progressEvent(0, 'Starting Debugger'); + await this.gdbServer.spawn(args); + await this.spawn(args); + + // Send commands + await mi.sendTargetAsyncOn(this.gdb); + await mi.sendTargetSelectRemote(this.gdb, remote); + await mi.sendMonitorResetHalt(this.gdb); + this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`)); + + // Download image + const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image'); + progressListener(0); + this.gdbServer.on('progress', progressListener); + await mi.sendTargetDownload(this.gdb); + this.gdbServer.off('progress', progressListener); + progressListener(100); + + // Halt after image download + await mi.sendMonitorResetHalt(this.gdb); + await this.gdb.sendEnablePrettyPrint(); + + if (args.runToMain === true) { + await mi.sendBreakOnFunction(this.gdb); + } + + this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`)); + this.sendEvent(new InitializedEvent()); + } + + private async getGlobalVariables(frameHandle: number): Promise { + const frame = this.frameHandles.get(frameHandle); + const symbolInfo = this.symbolTable.getGlobalVariables(); + const variables: DebugProtocol.Variable[] = []; + + for (const symbol of symbolInfo) { + const name = `global_var_${symbol.name}`; + const variable = await this.getVariables(frame, name, symbol.name, -1); + variables.push(variable); + } + + return variables; + } + + private async getStaticVariables(frameHandle: number): Promise { + const frame = this.frameHandles.get(frameHandle); + const result = await mi.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); + const file = normalize(result.frame.file || ''); + const symbolInfo = this.symbolTable.getStaticVariables(file); + const variables: DebugProtocol.Variable[] = []; + + // Fetch stack depth to obtain frameId/threadId/depth tuple + const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 }); + const depth = parseInt(stackDepth.depth, 10); + + for (const symbol of symbolInfo) { + const name = `${file}_static_var_${symbol.name}`; + const variable = await this.getVariables(frame, name, symbol.name, depth); + variables.push(variable); + } + + return variables; + } + + private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise { + let global = varMgr.getVar(frame.frameId, frame.threadId, depth, name); + + if (global) { + // Update value if it is already loaded + const vup = await mi.sendVarUpdate(this.gdb, { name }); + const update = vup.changelist[0]; + if (update && update.in_scope === 'true' && update.name === global.varname) { + global.value = update.value; + } + } else { + // create var in GDB and store it in the varMgr + const varCreateResponse = await mi.sendVarCreate(this.gdb, { + name, + frame: 'current', + expression, + }); + + global = varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse); + } + + return { + name: expression, + value: (global.value === void 0) ? '' : global.value, + type: global.type, + variablesReference: parseInt(global.numchild, 10) > 0 + ? this.variableHandles.create({ + frameHandle: this.globalHandle, + type: 'object', + varobjName: global.varname, + }) + : 0, + }; + } + + private progressEvent(percent: number, message: string) { + this.sendEvent(new OutputEvent('progress', 'telemetry', { + percent, + message + })); + } + + protected async stopSession() { + // Pause debugging + if (this.isRunning) { + // Need to pause first + const waitPromise = new Promise(resolve => this.waitPaused = resolve); + this.gdb.pause(); + await waitPromise; + } + + // Detach + if ((this.gdb as CmsisBackend).isRunning) { + try { + await mi.sendTargetDetach(this.gdb); + } catch (e) { + // Need to catch here as the command result being returned will never exist as it's detached + } + } + + // Stop gdb client and server + try { + await this.gdb.sendGDBExit(); + } catch (e) { + // Need to catch here in case the connection has already been closed + } + this.gdbServer.kill(); + } + + public async shutdown() { + await this.stopSession(); + super.shutdown(); + } +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/index.ts b/arduino-debugger-extension/src/node/debug-adapter/index.ts new file mode 100644 index 00000000..d0765327 --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/index.ts @@ -0,0 +1,34 @@ +/* +* 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'; + +process.on('uncaughtException', (err: any) => { + logger.error(JSON.stringify(err)); +}); + +CmsisDebugSession.run(CmsisDebugSession); diff --git a/arduino-debugger-extension/src/node/debug-adapter/mi.ts b/arduino-debugger-extension/src/node/debug-adapter/mi.ts new file mode 100644 index 00000000..c9200e3e --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/mi.ts @@ -0,0 +1,80 @@ +/* +* 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 { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi'; +import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; + +export function sendTargetAsyncOn(gdb: GDBBackend) { + const set = 'target-async on'; + return gdb.sendGDBSet(set); +} + +export function sendMonitorResetHalt(gdb: GDBBackend) { + const command = '-interpreter-exec console "monitor reset halt"'; + return gdb.sendCommand(command); +} + +export function sendTargetSelectRemote(gdb: GDBBackend, remote: string) { + const command = `-target-select extended-remote ${remote}`; + return gdb.sendCommand(command); +} + +export function sendTargetDownload(gdb: GDBBackend) { + const command = '-target-download'; + return gdb.sendCommand(command); +} + +export function sendBreakOnFunction(gdb: GDBBackend, fn: string = 'main') { + const command = `-break-insert -t --function ${fn}`; + return gdb.sendCommand(command); +} + +export function sendExecInterrupt(gdb: GDBBackend, threadId?: number) { + let command = '-exec-interrupt'; + if (threadId) { + command += ` --thread ${threadId}`; + } + return gdb.sendCommand(command); +} + +export function sendStackInfoFrame(gdb: GDBBackend, threadId: number, frameId: number): Promise<{frame: MIFrameInfo}> { + const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`; + return gdb.sendCommand(command); +} + +export function sendUserInput(gdb: GDBBackend, command: string): Promise { + if (!command.startsWith('-')) { + command = `interpreter-exec console "${command}"`; + } + + return gdb.sendCommand(command); +} + +export function sendTargetDetach(gdb: GDBBackend) { + const command = '-target-detach'; + return gdb.sendCommand(command); +} + +export * from 'cdt-gdb-adapter/dist/mi'; diff --git a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts new file mode 100644 index 00000000..798b9e8f --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts @@ -0,0 +1,51 @@ +import { AbstractServer } from './abstract-server'; +import { PortScanner } from './port-scanner'; + +const LAUNCH_REGEX = /GDB server started/; +const ERROR_REGEX = /:ERROR:gdbserver:/; +const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress + +export class OpenocdServer extends AbstractServer { + + protected portScanner = new PortScanner(); + protected progress = 0; + + protected async resolveServerArguments(serverArguments?: string[]): Promise { + if (!serverArguments) { + serverArguments = []; + } + + const telnetPort = await this.portScanner.findFreePort(4444); + + if (!telnetPort) { + return serverArguments; + } + + return [ + ...serverArguments, + '--telnet-port', + telnetPort.toString() + ]; + } + + protected onStdout(chunk: string | Buffer) { + super.onStdout(chunk); + const buffer = typeof chunk === 'string' ? chunk : chunk.toString('utf8'); + const match = buffer.match(/=/g); + + if (match) { + this.progress += match.length; + const percent = Math.round(this.progress * PERCENT_MULTIPLIER); + this.emit('progress', percent); + } + } + + protected serverStarted(data: string): boolean { + return LAUNCH_REGEX.test(data); + } + + protected serverError(data: string): boolean { + return ERROR_REGEX.test(data); + } + +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts b/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts new file mode 100644 index 00000000..8e811627 --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts @@ -0,0 +1,192 @@ +/* +* CMSIS Debug Adapter +* Copyright (c) 2016 Zoujie +* 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 { exec } from 'child_process'; + +const maxBuffer = 2 * 1024 * 1024; + +export class PortScanner { + + public async findFreePort(start: number = 50000, length: number = 100): Promise { + const fn = this.getFunction().bind(this); + + for (let i = start; i <= start + length; i++) { + try { + // Try to find pid of port + await fn(i); + } catch (_e) { + // Port is free when pid not found + return i; + } + } + + return undefined; + } + + private getFunction(): (port: number) => Promise { + switch (process.platform) { + case 'darwin': + case 'freebsd': + case 'sunos': { + return this.darwin; + break; + } + case 'linux': { + return this.linux; + break; + } + case 'win32': { + return this.windows; + break; + } + } + + return () => Promise.resolve(0); + } + + private async darwin(port: number): Promise { + const result = await this.execute('netstat -anv -p TCP && netstat -anv -p UDP'); + + // Replace header + const data = this.stripLine(result.toString(), 2); + + const found = this.extractColumns(data, [0, 3, 8], 10) + .filter(row => !!String(row[0]).match(/^(udp|tcp)/)) + .find(row => { + const matches = String(row[1]).match(/\.(\d+)$/); + return (matches && matches[1] === String(port)); + }); + + if (found && found[2].length) { + return parseInt(found[2], 10); + } + + throw new Error(`pid of port (${port}) not found`); + } + + private async linux(port: number): Promise { + // netstat -p ouputs warning if user is no-root + const result = await this.execute('netstat -tunlp'); + + // Replace header + const data = this.stripLine(result.toString(), 2); + + const columns = this.extractColumns(data, [3, 6], 7) + .find(column => { + const matches = String(column[0]).match(/:(\d+)$/); + return (matches && matches[1] === String(port)); + }); + + if (columns && columns[1]) { + const pid = columns[1].split('/', 1)[0]; + + if (pid.length) { + return parseInt(pid, 10); + } + } + + throw new Error(`pid of port (${port}) not found`); + } + + private async windows(port: number): Promise { + const result = await this.execute('netstat -ano'); + + // Replace header + const data = this.stripLine(result.toString(), 4); + + const columns = this.extractColumns(data, [1, 4], 5) + .find(column => { + const matches = String(column[0]).match(/:(\d+)$/); + return (matches && matches[1] === String(port)); + }); + + if (columns && columns[1].length && parseInt(columns[1], 10) > 0) { + return parseInt(columns[1], 10); + } + + throw new Error(`pid of port (${port}) not found`); + } + + private execute(cmd: string): Promise { + return new Promise((resolve, reject) => { + exec(cmd, { + maxBuffer, + windowsHide: true + }, (error: Error | null, stdout: string) => { + if (error) { + return reject(error); + } + + return resolve(stdout); + }); + }); + } + + private stripLine(text: string, num: number): string { + let idx = 0; + + while (num-- > 0) { + const nIdx = text.indexOf('\n', idx); + if (nIdx >= 0) { + idx = nIdx + 1; + } + } + + return idx > 0 ? text.substring(idx) : text; + } + + private extractColumns(text: string, idxes: number[], max: number): string[][] { + const lines = text.split(/(\r\n|\n|\r)/); + const columns: string[][] = []; + + if (!max) { + max = Math.max.apply(null, idxes) + 1; + } + + lines.forEach(line => { + const cols = this.split(line, max); + const column: string[] = []; + + idxes.forEach(idx => { + column.push(cols[idx] || ''); + }); + + columns.push(column); + }); + + return columns; + } + + private split(line: string, max: number): string[] { + const cols = line.trim().split(/\s+/); + + if (cols.length > max) { + cols[max - 1] = cols.slice(max - 1).join(' '); + } + + return cols; + } +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts new file mode 100644 index 00000000..1450f488 --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts @@ -0,0 +1,75 @@ +/* +* 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 { AbstractServer } from './abstract-server'; +import { PortScanner } from './port-scanner'; + +const LAUNCH_REGEX = /GDB server started/; +const ERROR_REGEX = /:ERROR:gdbserver:/; +const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress + +export class PyocdServer extends AbstractServer { + + protected portScanner = new PortScanner(); + protected progress = 0; + + protected async resolveServerArguments(serverArguments?: string[]): Promise { + if (!serverArguments) { + serverArguments = []; + } + + const telnetPort = await this.portScanner.findFreePort(4444); + + if (!telnetPort) { + return serverArguments; + } + + return [ + ...serverArguments, + '--telnet-port', + telnetPort.toString() + ]; + } + + protected onStdout(chunk: string | Buffer) { + super.onStdout(chunk); + const buffer = typeof chunk === 'string' ? chunk : chunk.toString('utf8'); + const match = buffer.match(/=/g); + + if (match) { + this.progress += match.length; + const percent = Math.round(this.progress * PERCENT_MULTIPLIER); + this.emit('progress', percent); + } + } + + protected serverStarted(data: string): boolean { + return LAUNCH_REGEX.test(data); + } + + protected serverError(data: string): boolean { + return ERROR_REGEX.test(data); + } +} diff --git a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts new file mode 100644 index 00000000..1846cb2b --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts @@ -0,0 +1,151 @@ +/* +* CMSIS Debug Adapter +* Copyright (c) 2017-2019 Marcel Ball +* 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 { spawnSync } from 'child_process'; +import { platform, EOL } from 'os'; +import { dirname, normalize, basename } from 'path'; + +export enum SymbolType { + Function, + File, + Object, + Normal +} + +export enum SymbolScope { + Local, + Global, + Neither, + Both +} + +export interface SymbolInformation { + address: number; + length: number; + name: string; + section: string; + type: SymbolType; + scope: SymbolScope; + file?: string; + hidden: boolean; +} + +const DEFAULT_OBJDUMP = platform() !== 'win32' ? 'arm-none-eabi-objdump' : 'arm-none-eabi-objdump.exe'; +const SYMBOL_REGEX = /^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s([^\s]+)\s([0-9a-f]+)\s(.*)\r?$/; + +const TYPE_MAP: { [id: string]: SymbolType } = { + 'F': SymbolType.Function, + 'f': SymbolType.File, + 'O': SymbolType.Object, + ' ': SymbolType.Normal +}; + +const SCOPE_MAP: { [id: string]: SymbolScope } = { + 'l': SymbolScope.Local, + 'g': SymbolScope.Global, + ' ': SymbolScope.Neither, + '!': SymbolScope.Both +}; + +export class SymbolTable { + + private symbols: SymbolInformation[] = []; + + constructor(private program: string, private objdump: string = DEFAULT_OBJDUMP) { + } + + public async loadSymbols(): Promise { + const results = await this.execute(); + const output = results.toString(); + const lines = output.split(EOL); + let currentFile: string | undefined; + + for (const line of lines) { + const match = line.match(SYMBOL_REGEX); + if (match) { + if (match[7] === 'd' && match[8] === 'f') { + currentFile = match[11].trim(); + } + const type = TYPE_MAP[match[8]]; + const scope = SCOPE_MAP[match[2]]; + let name = match[11].trim(); + let hidden = false; + + if (name.startsWith('.hidden')) { + name = name.substring(7).trim(); + hidden = true; + } + + this.symbols.push({ + address: parseInt(match[1], 16), + type: type, + scope: scope, + section: match[9].trim(), + length: parseInt(match[10], 16), + name: name, + file: scope === SymbolScope.Local ? currentFile : undefined, + hidden: hidden + }); + } + } + } + + public getGlobalVariables(): SymbolInformation[] { + const matches = this.symbols.filter(s => s.type === SymbolType.Object && s.scope === SymbolScope.Global); + return matches; + } + + public getStaticVariables(file: string): SymbolInformation[] { + return this.symbols.filter(s => + s.type === SymbolType.Object // Only load objects + && s.scope === SymbolScope.Local // Scoped to this file + && !s.name.startsWith('.') // Ignore names beginning with '.' + && (normalize(s.file || '') === normalize(file) || normalize(s.file || '') === basename(file))); // Match full path or file name + } + + private execute(): Promise { + return new Promise((resolve, reject) => { + try { + const { stdout, stderr } = spawnSync(this.objdump, [ + '--syms', + this.program + ], { + cwd: dirname(this.objdump), + windowsHide: true + }); + + const error = stderr.toString('utf8'); + if (error) { + return reject(new Error(error)); + } + + resolve(stdout.toString('utf8')); + } catch (error) { + return reject(new Error(error)); + } + }); + } +} diff --git a/arduino-debugger-extension/tsconfig.json b/arduino-debugger-extension/tsconfig.json new file mode 100644 index 00000000..86907b52 --- /dev/null +++ b/arduino-debugger-extension/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "noImplicitAny": true, + "noEmitOnError": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "downlevelIteration": true, + "emitDecoratorMetadata": true, + "module": "commonjs", + "moduleResolution": "node", + "target": "es6", + "outDir": "lib", + "lib": [ + "es6", + "dom" + ], + "jsx": "react", + "sourceMap": true, + "skipLibCheck": true + }, + "include": [ + "src" + ], + "files": [ + "../node_modules/@theia/monaco/src/typings/monaco/index.d.ts" + ] +} \ No newline at end of file diff --git a/arduino-debugger-extension/tslint.json b/arduino-debugger-extension/tslint.json new file mode 100644 index 00000000..55b00628 --- /dev/null +++ b/arduino-debugger-extension/tslint.json @@ -0,0 +1,37 @@ +{ + "rules": { + "class-name": true, + "comment-format": [true, "check-space"], + "curly": false, + "forin": false, + "indent": [true, "spaces"], + "max-line-length": [true, 180], + "no-trailing-whitespace": false, + "no-unused-expression": true, + "no-var-keyword": true, + "one-line": [true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "radix": true, + "trailing-comma": [false], + "triple-equals": [true, "allow-null-check"], + "typedef-whitespace": [true, { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }], + "variable-name": false, + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} \ No newline at end of file diff --git a/browser-app/package.json b/browser-app/package.json index e0d7e676..7dbca598 100644 --- a/browser-app/package.json +++ b/browser-app/package.json @@ -19,14 +19,15 @@ "@theia/terminal": "next", "@theia/workspace": "next", "@theia/textmate-grammars": "next", - "arduino-ide-extension": "0.0.4" + "arduino-ide-extension": "0.0.4", + "arduino-debugger-extension": "0.0.2" }, "devDependencies": { "@theia/cli": "next" }, "scripts": { "prepare": "theia build --mode development", - "start": "theia start", + "start": "theia start --plugins=local-dir:../", "watch": "theia build --watch --mode development" }, "theia": { diff --git a/package.json b/package.json index d3418702..e68c9283 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "workspaces": [ "arduino-ide-extension", + "arduino-debugger-extension", "electron-app", "browser-app" ] From ea5f528ef0dc55d242e8dcbdc75c9270dea44512 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Fri, 15 Nov 2019 16:34:01 +0100 Subject: [PATCH 03/44] Actual debugging works --- .../arduino-debug-adapter-contribution.ts | 14 ++++++--- .../src/node/debug-adapter/abstract-server.ts | 7 ++--- .../node/debug-adapter/cmsis-debug-session.ts | 14 +++++++-- .../src/node/debug-adapter/mi.ts | 4 +-- .../src/node/debug-adapter/openocd-server.ts | 30 ++++++++++--------- .../src/node/debug-adapter/pyocd-server.ts | 22 ++++++-------- 6 files changed, 51 insertions(+), 40 deletions(-) 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 7a6ad517..17b1a3ab 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -27,9 +27,15 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution "description": "Path to the program to be launched", "default": "${workspaceFolder}/${command:askProgramPath}" }, - "arguments": { + "sketch": { "type": "string", - "description": "Arguments for the program" + "description": "Path to the sketch folder", + "default": "${workspaceFolder}" + }, + "fbqn": { + "type": "string", + "description": "Fully qualified board name of the debugging target", + "default": "unknown" }, "runToMain": { "description": "If enabled the debugger will run until the start of the main function.", @@ -75,7 +81,7 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution "type": "boolean", "description": "Start the debug adapter in debug mode (with --inspect-brk)", "default": "false" - } + }, } } ] @@ -85,7 +91,7 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution return [] } - provideDebugAdapterExecutable?(config: DebugConfiguration): MaybePromise { + provideDebugAdapterExecutable(config: DebugConfiguration): MaybePromise { let args: string[] = []; if (!!config.debugDebugAdapter) { args.push('--inspect-brk') diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts index 0d8c369c..baf053d7 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts @@ -33,7 +33,6 @@ import { CmsisRequestArguments } from './cmsis-debug-session'; const TIMEOUT = 1000 * 10; // 10 seconds export abstract class AbstractServer extends EventEmitter { - protected process?: ChildProcess; protected outBuffer: string = ''; protected errBuffer: string = ''; @@ -50,7 +49,7 @@ export abstract class AbstractServer extends EventEmitter { this.timer = setTimeout(() => this.onSpawnError(new Error('Timeout waiting for gdb server to start')), TIMEOUT); const command = args.gdbServer || 'gdb-server'; - const serverArguments = await this.resolveServerArguments(args.gdbServerArguments); + const serverArguments = await this.resolveServerArguments(args); this.process = spawn(command, serverArguments, { cwd: dirname(command), }); @@ -80,8 +79,8 @@ export abstract class AbstractServer extends EventEmitter { } } - protected async resolveServerArguments(serverArguments?: string[]): Promise { - return serverArguments || []; + protected async resolveServerArguments(req: CmsisRequestArguments): Promise { + return req.gdbServerArguments || []; } protected onExit(code: number, signal: string) { diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index c98879f5..8c746d91 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -29,16 +29,18 @@ import { Logger, logger, InitializedEvent, OutputEvent, Scope, TerminatedEvent } import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference, ObjectVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; import { CmsisBackend } from './cmsis-backend'; -import { PyocdServer } from './pyocd-server'; +// import { PyocdServer } from './pyocd-server'; import { PortScanner } from './port-scanner'; import { SymbolTable } from './symbols'; import * as varMgr from 'cdt-gdb-adapter/dist/varManager'; import * as mi from './mi'; +import { OpenocdServer } from './openocd-server'; export interface CmsisRequestArguments extends RequestArguments { runToMain?: boolean; gdbServer?: string; gdbServerArguments?: string[]; + gdbServerPort?: number; objdump?: string; } @@ -48,7 +50,7 @@ const STATIC_HANDLES_FINISH = 0x01FFFF; export class CmsisDebugSession extends GDBDebugSession { - protected gdbServer = new PyocdServer(); + protected gdbServer = new OpenocdServer(); protected portScanner = new PortScanner(); protected symbolTable!: SymbolTable; protected globalHandle!: number; @@ -110,6 +112,11 @@ export class CmsisDebugSession extends GDBDebugSession { } } + protected async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise { + await super.setBreakPointsRequest(response, args); + return; + } + protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { try { const frame: FrameVariableReference = { @@ -245,10 +252,11 @@ export class CmsisDebugSession extends GDBDebugSession { if (!args.gdbServerArguments) { args.gdbServerArguments = []; } - args.gdbServerArguments.push('--port', port.toString()); + args.gdbServerPort = port; // Start gdb client and server this.progressEvent(0, 'Starting Debugger'); + this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); await this.gdbServer.spawn(args); await this.spawn(args); diff --git a/arduino-debugger-extension/src/node/debug-adapter/mi.ts b/arduino-debugger-extension/src/node/debug-adapter/mi.ts index c9200e3e..d962e0d6 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/mi.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/mi.ts @@ -42,8 +42,8 @@ export function sendTargetSelectRemote(gdb: GDBBackend, remote: string) { } export function sendTargetDownload(gdb: GDBBackend) { - const command = '-target-download'; - return gdb.sendCommand(command); + // const command = '-target-download'; + // return gdb.sendCommand(command); } export function sendBreakOnFunction(gdb: GDBBackend, fn: string = 'main') { diff --git a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts index 798b9e8f..8a7b1f99 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts @@ -1,31 +1,33 @@ import { AbstractServer } from './abstract-server'; import { PortScanner } from './port-scanner'; +import { CmsisRequestArguments } from './cmsis-debug-session'; +import * as fs from 'fs-extra'; +import * as path from 'path'; const LAUNCH_REGEX = /GDB server started/; const ERROR_REGEX = /:ERROR:gdbserver:/; const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress export class OpenocdServer extends AbstractServer { - protected portScanner = new PortScanner(); protected progress = 0; - protected async resolveServerArguments(serverArguments?: string[]): Promise { - if (!serverArguments) { - serverArguments = []; - } - + protected async resolveServerArguments(req: CmsisRequestArguments): Promise { + let sessionConfigFile = `gdb_port ${req.gdbServerPort!}${"\n"}`; const telnetPort = await this.portScanner.findFreePort(4444); - - if (!telnetPort) { - return serverArguments; + if (!!telnetPort) { + sessionConfigFile += `telnet_port ${telnetPort}${"\n"}` } + sessionConfigFile += `echo "GDB server started"${"\n"}` + + const tmpdir = await fs.mkdtemp("arduino-debugger"); + const sessionCfgPath = path.join(tmpdir, "gdb.cfg"); + await fs.writeFile(sessionCfgPath, sessionConfigFile); - return [ - ...serverArguments, - '--telnet-port', - telnetPort.toString() - ]; + let serverArguments = req.gdbServerArguments || []; + serverArguments.push("--file", sessionCfgPath); + + return serverArguments; } protected onStdout(chunk: string | Buffer) { diff --git a/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts index 1450f488..f9570fc4 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts @@ -25,6 +25,7 @@ import { AbstractServer } from './abstract-server'; import { PortScanner } from './port-scanner'; +import { CmsisRequestArguments } from './cmsis-debug-session'; const LAUNCH_REGEX = /GDB server started/; const ERROR_REGEX = /:ERROR:gdbserver:/; @@ -35,22 +36,17 @@ export class PyocdServer extends AbstractServer { protected portScanner = new PortScanner(); protected progress = 0; - protected async resolveServerArguments(serverArguments?: string[]): Promise { - if (!serverArguments) { - serverArguments = []; - } + protected async resolveServerArguments(req: CmsisRequestArguments): Promise { + let serverArguments = req.gdbServerArguments || []; + + serverArguments.push('--port', req.gdbServerPort!.toString()) const telnetPort = await this.portScanner.findFreePort(4444); - - if (!telnetPort) { - return serverArguments; + if (!!telnetPort) { + serverArguments.push('--telnet-port', telnetPort.toString()) } - - return [ - ...serverArguments, - '--telnet-port', - telnetPort.toString() - ]; + + return serverArguments; } protected onStdout(chunk: string | Buffer) { From 8aa356bd6e5372688dab74ebf1688ee40d66287a Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Tue, 19 Nov 2019 18:24:30 +0100 Subject: [PATCH 04/44] Automated debug config setup --- arduino-debugger-extension/package.json | 5 +- .../src/browser/arduino-variable-resolver.ts | 100 ++++++++++++++ .../src/browser/frontend-module.ts | 8 ++ .../arduino-debug-adapter-contribution.ts | 126 ++++++++++++------ .../node/debug-adapter/cmsis-debug-session.ts | 27 +++- .../src/node/debug-adapter/openocd-server.ts | 3 +- .../src/common/protocol/boards-service.ts | 25 +++- .../src/common/protocol/detailable.ts | 10 ++ .../src/node/boards-service-impl.ts | 87 ++++++++++-- 9 files changed, 325 insertions(+), 66 deletions(-) create mode 100644 arduino-debugger-extension/src/browser/arduino-variable-resolver.ts create mode 100644 arduino-debugger-extension/src/browser/frontend-module.ts create mode 100644 arduino-ide-extension/src/common/protocol/detailable.ts diff --git a/arduino-debugger-extension/package.json b/arduino-debugger-extension/package.json index 0eafe66c..f986bee0 100644 --- a/arduino-debugger-extension/package.json +++ b/arduino-debugger-extension/package.json @@ -9,7 +9,7 @@ "dependencies": { "@theia/core": "next", "@theia/debug": "next", - + "arduino-ide-extension": "0.0.2", "cdt-gdb-adapter": "^0.0.14-next.4783033.0", "vscode-debugadapter": "^1.26.0", "vscode-debugprotocol": "^1.26.0" @@ -34,7 +34,8 @@ ], "theiaExtensions": [ { - "backend": "lib/node/backend-module" + "backend": "lib/node/backend-module", + "frontend": "lib/browser/frontend-module" } ] } \ No newline at end of file diff --git a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts new file mode 100644 index 00000000..8dbd05f7 --- /dev/null +++ b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts @@ -0,0 +1,100 @@ + +import { VariableContribution, VariableRegistry, Variable } from '@theia/variable-resolver/lib/browser'; +import { injectable, inject } from 'inversify'; +import URI from '@theia/core/lib/common/uri'; +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'; +import { WorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution'; + +@injectable() +export class ArduinoVariableResolver implements VariableContribution { + + @inject(BoardsServiceClientImpl) + protected readonly boardsServiceClient: BoardsServiceClientImpl; + + @inject(BoardsService) + protected readonly boardsService: BoardsService; + + @inject(WorkspaceVariableContribution) + protected readonly workspaceVars: WorkspaceVariableContribution; + + 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), + }); + + variables.registerVariable({ + name: "sketchBinary", + description: "Path to the sketch's binary file", + resolve: this.resolveSketchBinary.bind(this) + }); + } + + // TODO: this function is a total hack. Instead of botching around with URI's it should ask something on the backend + // that properly udnerstands the filesystem. + protected async resolveSketchBinary(context?: URI, argument?: string, configurationSection?: string): Promise { + let sketchPath = argument || this.workspaceVars.getResourceUri()!.path.toString(); + return sketchPath.substring(0, sketchPath.length - 3) + "arduino.samd.arduino_zero_edbg.elf"; + } + + protected async resolveBoard(context?: URI, argument?: string, configurationSection?: string): Promise { + const { boardsConfig } = this.boardsServiceClient; + if (!boardsConfig || !boardsConfig.selectedBoard) { + throw new Error('No boards selected. Please select a board.'); + } + + 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) { + throw new Error("Cannot get board details"); + } + if (argument === "openocd-debug-file") { + return details.item.locations!.debugScript; + } + + return boardsConfig.selectedBoard.fqbn!; + } + + protected async resolveBoardTools(context?: URI, argument?: string, configurationSection?: string): Promise { + const { boardsConfig } = this.boardsServiceClient; + if (!boardsConfig || !boardsConfig.selectedBoard) { + throw new Error('No boards selected. Please select a board.'); + } + const details = await this.boardsService.detail({id: boardsConfig.selectedBoard.fqbn!}); + if (!details.item) { + throw new Error("Cannot get board details") + } + + let toolLocations: { [name: string]: ToolLocations } = {}; + details.item.requiredTools.forEach(t => { + toolLocations[t.name] = t.locations!; + }) + + switch(argument) { + case "openocd": + return toolLocations["openocd"].main; + case "openocd-scripts": + return toolLocations["openocd"].scripts; + case "objdump": + return toolLocations["arm-none-eabi-gcc"].objdump; + case "gdb": + return toolLocations["arm-none-eabi-gcc"].gdb; + } + + return boardsConfig.selectedBoard.name; + } + +} \ No newline at end of file diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts new file mode 100644 index 00000000..fdd71fe9 --- /dev/null +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -0,0 +1,8 @@ +import { ContainerModule } from 'inversify'; +import { VariableContribution } from '@theia/variable-resolver/lib/browser'; +import { ArduinoVariableResolver } from './arduino-variable-resolver'; + +export default new ContainerModule((bind, unbind, isBound, rebind) => { + bind(ArduinoVariableResolver).toSelf().inSingletonScope(); + bind(VariableContribution).toService(ArduinoVariableResolver); +}); 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 17b1a3ab..b52d61b4 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -1,12 +1,24 @@ -import { injectable } from 'inversify'; +import { injectable, inject } 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 { BoardsService } from 'arduino-ide-extension/lib/common/protocol/boards-service'; +import { CoreService } from 'arduino-ide-extension/lib/common/protocol/core-service'; +import { FileSystem } from '@theia/filesystem/lib/common'; @injectable() export class ArduinoDebugAdapterContribution implements DebugAdapterContribution { + @inject(BoardsService) + protected readonly boardsService: BoardsService; + + @inject(CoreService) + protected readonly coreService: CoreService; + + @inject(FileSystem) + protected readonly fileSystem: FileSystem; + type = "arduino"; label = "Arduino"; @@ -22,56 +34,22 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution "program" ], "properties": { - "program": { - "type": "string", - "description": "Path to the program to be launched", - "default": "${workspaceFolder}/${command:askProgramPath}" - }, "sketch": { "type": "string", - "description": "Path to the sketch folder", - "default": "${workspaceFolder}" - }, - "fbqn": { - "type": "string", - "description": "Fully qualified board name of the debugging target", - "default": "unknown" + "description": "path to the sketch root ino file", + "default": "${file}", }, "runToMain": { "description": "If enabled the debugger will run until the start of the main function.", "type": "boolean", "default": false }, - "gdb": { + "fqbn": { "type": "string", - "description": "Path to gdb", - "default": "arm-none-eabi-gdb" - }, - "gdbArguments": { - "description": "Additional arguments to pass to GDB command line", - "type": "array", - "default": [] - }, - "gdbServer": { - "type": "string", - "description": "Path to gdb server", - "default": "pyocd" - }, - "gdbServerArguments": { - "description": "Additional arguments to pass to GDB server", - "type": "array", - "default": [] - }, - "objdump": { - "type": "string", - "description": "Path to objdump executable", - "default": "arm-none-eabi-objdump" - }, - "initCommands": { - "description": "Extra gdb commands to run after initialisation", - "type": "array", - "default": [] + "description": "Fully-qualified board name to debug on", + "default": "" }, + "verbose": { "type": "boolean", "description": "Produce verbose log output", @@ -105,11 +83,71 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution } provideDebugConfigurations?(workspaceFolderUri?: string): MaybePromise { - return []; + return [ + { + name: this.label, + type: this.type, + request: "launch", + + sketch: "${file}", + + verbose: true, + runToMain: true, + }, + { + 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}"], + + verbose: true, + runToMain: true, + } + ]; } - resolveDebugConfiguration?(config: DebugConfiguration, workspaceFolderUri?: string): MaybePromise { - return config; + 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 (config.sketch !== "${file}") { + sketchBinary = "${sketchBinary:" + config.sketch + "}"; + } + 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 + } + return res; } +} + +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[] } \ No newline at end of file diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 8c746d91..ceadb801 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -26,7 +26,7 @@ import { normalize } from 'path'; import { DebugProtocol } from 'vscode-debugprotocol'; import { Logger, logger, InitializedEvent, OutputEvent, Scope, TerminatedEvent } from 'vscode-debugadapter'; -import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference, ObjectVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; +import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; import { CmsisBackend } from './cmsis-backend'; // import { PyocdServer } from './pyocd-server'; @@ -123,15 +123,15 @@ export class CmsisDebugSession extends GDBDebugSession { type: 'frame', frameHandle: args.frameId, }; - const pins: ObjectVariableReference = { - type: "object", - varobjName: "__pins", - frameHandle: args.frameId, - } + // const pins: ObjectVariableReference = { + // type: "object", + // varobjName: "__pins", + // frameHandle: 42000, + // } response.body = { scopes: [ - new Scope('Pins', this.variableHandles.create(pins), false), + // new Scope('Pins', this.variableHandles.create(pins), false), new Scope('Local', this.variableHandles.create(frame), false), new Scope('Global', GLOBAL_HANDLE_ID, false), new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false) @@ -162,6 +162,8 @@ export class CmsisDebugSession extends GDBDebugSession { } else if (ref && ref.type === 'frame') { // List variables for current frame response.body.variables = await this.handleVariableRequestFrame(ref); + } else if (ref && ref.varobjName === '__pins') { + response.body.variables = await this.handlePinStatusRequest(); } else if (ref && ref.type === 'object') { // List data under any variable response.body.variables = await this.handleVariableRequestObject(ref); @@ -300,6 +302,17 @@ export class CmsisDebugSession extends GDBDebugSession { return variables; } + private async handlePinStatusRequest(): Promise { + const variables: DebugProtocol.Variable[] = []; + variables.push({ + name: "D2", + type: "gpio", + value: "0x00", + variablesReference: 0 + }) + return variables; + } + private async getStaticVariables(frameHandle: number): Promise { const frame = this.frameHandles.get(frameHandle); const result = await mi.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); diff --git a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts index 8a7b1f99..079ddcb0 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts @@ -3,6 +3,7 @@ import { PortScanner } from './port-scanner'; import { CmsisRequestArguments } from './cmsis-debug-session'; import * as fs from 'fs-extra'; import * as path from 'path'; +import * as os from 'os'; const LAUNCH_REGEX = /GDB server started/; const ERROR_REGEX = /:ERROR:gdbserver:/; @@ -20,7 +21,7 @@ export class OpenocdServer extends AbstractServer { } sessionConfigFile += `echo "GDB server started"${"\n"}` - const tmpdir = await fs.mkdtemp("arduino-debugger"); + const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "arduino-debugger")); const sessionCfgPath = path.join(tmpdir, "gdb.cfg"); await fs.writeFile(sessionCfgPath, sessionConfigFile); diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index 2a786567..cb25650e 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -2,6 +2,7 @@ import { isWindows, isOSX } from '@theia/core/lib/common/os'; import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; import { Searchable } from './searchable'; import { Installable } from './installable'; +import { Detailable } from './detailable'; import { ArduinoComponent } from './arduino-component'; const naturalCompare: (left: string, right: string) => number = require('string-natural-compare').caseInsensitive; @@ -59,7 +60,7 @@ export interface BoardsServiceClient { export const BoardsServicePath = '/services/boards-service'; export const BoardsService = Symbol('BoardsService'); -export interface BoardsService extends Installable, Searchable, JsonRpcServer { +export interface BoardsService extends Installable, Searchable, Detailable, JsonRpcServer { getAttachedBoards(): Promise<{ boards: Board[] }>; getAvailablePorts(): Promise<{ ports: Port[] }>; } @@ -181,6 +182,28 @@ export interface Board { fqbn?: string } +export interface BoardDetails extends Board { + fqbn: string; + + requiredTools: Tool[]; + locations?: BoardDetailLocations; +} + +export interface BoardDetailLocations { + debugScript: string; +} + +export interface Tool { + readonly packager: string; + readonly name: string; + readonly version: string; + readonly locations?: ToolLocations; +} +export interface ToolLocations { + main: string + [key: string]: string +} + export namespace Board { export function is(board: any): board is Board { diff --git a/arduino-ide-extension/src/common/protocol/detailable.ts b/arduino-ide-extension/src/common/protocol/detailable.ts new file mode 100644 index 00000000..456dd626 --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/detailable.ts @@ -0,0 +1,10 @@ + +export interface Detailable { + detail(options: Detailable.Options): Promise<{ item?: T }>; +} + +export namespace Detailable { + export interface Options { + readonly id: string; + } +} \ No newline at end of file diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index a79af50f..8dd110a8 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -2,22 +2,21 @@ import * as PQueue from 'p-queue'; import { injectable, inject, postConstruct, named } from 'inversify'; import { ILogger } from '@theia/core/lib/common/logger'; import { Deferred } from '@theia/core/lib/common/promise-util'; -import { BoardsService, AttachedSerialBoard, BoardPackage, Board, AttachedNetworkBoard, BoardsServiceClient, Port } from '../common/protocol/boards-service'; import { - PlatformSearchReq, - PlatformSearchResp, - PlatformInstallReq, - PlatformInstallResp, - PlatformListReq, - PlatformListResp, - Platform, - PlatformUninstallReq, - PlatformUninstallResp + BoardsService, AttachedSerialBoard, BoardPackage, Board, AttachedNetworkBoard, BoardsServiceClient, + Port, BoardDetails, Tool, ToolLocations, BoardDetailLocations +} from '../common/protocol/boards-service'; +import { + PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq, + PlatformListResp, Platform, PlatformUninstallResp, PlatformUninstallReq } from './cli-protocol/commands/core_pb'; import { CoreClientProvider } from './core-client-provider'; -import { BoardListReq, BoardListResp } from './cli-protocol/commands/board_pb'; +import { BoardListReq, BoardListResp, BoardDetailsReq, BoardDetailsResp, RequiredTool } from './cli-protocol/commands/board_pb'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; import { Installable } from '../common/protocol/installable'; +import { ConfigService } from '../common/protocol/config-service'; +import * as path from 'path'; +import URI from '@theia/core/lib/common/uri'; @injectable() export class BoardsServiceImpl implements BoardsService { @@ -35,6 +34,9 @@ export class BoardsServiceImpl implements BoardsService { @inject(ToolOutputServiceServer) protected readonly toolOutputService: ToolOutputServiceServer; + @inject(ConfigService) + protected readonly configService: ConfigService; + protected discoveryInitialized = false; protected discoveryTimer: NodeJS.Timer | undefined; /** @@ -215,6 +217,69 @@ export class BoardsServiceImpl implements BoardsService { }); } + async detail(options: { id: string }): Promise<{ item?: BoardDetails }> { + const coreClient = await this.coreClientProvider.getClient(); + if (!coreClient) { + return {}; + } + const { client, instance } = coreClient; + + const req = new BoardDetailsReq(); + req.setInstance(instance); + req.setFqbn(options.id); + const resp = await new Promise((resolve, reject) => client.boardDetails(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp))); + + + + const tools = await Promise.all(resp.getRequiredToolsList().map(async t => { + name: t.getName(), + packager: t.getPackager(), + version: t.getVersion(), + locations: await this.getToolLocations(t), + })); + + return { + item: { + name: resp.getName(), + fqbn: options.id, + requiredTools: tools, + locations: await this.getBoardLocations(resp) + } + }; + } + + // TODO: these location should come from the CLI/daemon rather than us botching them together + protected async getBoardLocations(details: BoardDetailsResp): Promise { + const config = await this.configService.getConfiguration(); + const datadir = new URI(config.dataDirUri).path.toString(); + + return { + debugScript: path.join(datadir, "packages", "arduino", "hardware", "samd", "1.8.4", "variants", "arduino_zero", "openocd_scripts", "arduino_zero.cfg") + } + } + + // TODO: these location should come from the CLI/daemon rather than us botching them together + protected async getToolLocations(t: RequiredTool) { + const config = await this.configService.getConfiguration(); + const datadir = new URI(config.dataDirUri).path.toString(); + const toolBasePath = path.join(datadir, "packages", "arduino", "tools"); + + let loc: ToolLocations = { + main: path.join(toolBasePath, t.getName(), t.getVersion()) + }; + + switch (t.getName()) { + case "openocd": + loc.scripts = path.join(loc.main, "share", "openocd", "scripts"); + loc.main = path.join(loc.main, "bin", "openocd"); + break + case "arm-none-eabi-gcc": + ["gdb", "objdump"].forEach(s => loc[s] = path.join(loc.main, "bin", `arm-none-eabi-${s}`)); + } + + return loc; + } + async search(options: { query?: string }): Promise<{ items: BoardPackage[] }> { const coreClient = await this.coreClientProvider.getClient(); if (!coreClient) { From ec18cf0dc13ca457dc578950c346df0ef1f5351c Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Tue, 19 Nov 2019 18:59:02 +0100 Subject: [PATCH 05/44] Cleanup --- arduino-debugger-extension/src/common/index.ts | 0 .../src/common/openocd/gdb.cfg | 2 -- .../src/common/openocd/openocd.cfg | 12 ------------ 3 files changed, 14 deletions(-) delete mode 100644 arduino-debugger-extension/src/common/index.ts delete mode 100644 arduino-debugger-extension/src/common/openocd/gdb.cfg delete mode 100644 arduino-debugger-extension/src/common/openocd/openocd.cfg diff --git a/arduino-debugger-extension/src/common/index.ts b/arduino-debugger-extension/src/common/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/arduino-debugger-extension/src/common/openocd/gdb.cfg b/arduino-debugger-extension/src/common/openocd/gdb.cfg deleted file mode 100644 index cdb6d76a..00000000 --- a/arduino-debugger-extension/src/common/openocd/gdb.cfg +++ /dev/null @@ -1,2 +0,0 @@ -gdb_port 50000 -telnet_port 44444 \ No newline at end of file diff --git a/arduino-debugger-extension/src/common/openocd/openocd.cfg b/arduino-debugger-extension/src/common/openocd/openocd.cfg deleted file mode 100644 index 10d1382b..00000000 --- a/arduino-debugger-extension/src/common/openocd/openocd.cfg +++ /dev/null @@ -1,12 +0,0 @@ -source [find interface/cmsis-dap.cfg] - -# chip name -set CHIPNAME at91samd21g18 -set ENDIAN little - -# choose a port here -set telnet_port 0 - -source [find target/at91samdXX.cfg] - -echo "GDB server started" \ No newline at end of file From 76f126b913765badb1ee1a5059102e2ed3832085 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Tue, 19 Nov 2019 19:09:10 +0100 Subject: [PATCH 06/44] Make openocd stop properly --- .../src/node/debug-adapter/cmsis-debug-session.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index ceadb801..b2e7c0e8 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -393,13 +393,13 @@ export class CmsisDebugSession extends GDBDebugSession { } } - // Stop gdb client and server + // Stop gdb client and server - we give GDB five seconds to exit orderly before we kill the GDB server + setTimeout(() => this.gdbServer.kill(), 5000); try { await this.gdb.sendGDBExit(); } catch (e) { // Need to catch here in case the connection has already been closed } - this.gdbServer.kill(); } public async shutdown() { From 68ff6acb6aef1d69cf258f321ebc2b50446915e0 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Tue, 19 Nov 2019 19:20:40 +0100 Subject: [PATCH 07/44] [electron] Added debugger to Electron build --- .../src/browser/arduino-variable-resolver.ts | 2 +- arduino-ide-extension/src/node/boards-service-impl.ts | 3 ++- electron-app/package.json | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts index 8dbd05f7..ab527eb3 100644 --- a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts +++ b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts @@ -83,7 +83,7 @@ export class ArduinoVariableResolver implements VariableContribution { toolLocations[t.name] = t.locations!; }) - switch(argument) { + switch (argument) { case "openocd": return toolLocations["openocd"].main; case "openocd-scripts": diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index 8dd110a8..83fa9678 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -272,9 +272,10 @@ export class BoardsServiceImpl implements BoardsService { case "openocd": loc.scripts = path.join(loc.main, "share", "openocd", "scripts"); loc.main = path.join(loc.main, "bin", "openocd"); - break + break; case "arm-none-eabi-gcc": ["gdb", "objdump"].forEach(s => loc[s] = path.join(loc.main, "bin", `arm-none-eabi-${s}`)); + break; } return loc; diff --git a/electron-app/package.json b/electron-app/package.json index 976187c0..dcd4724b 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -6,6 +6,7 @@ "dependencies": { "@theia/core": "next", "@theia/cpp": "next", + "@theia/debug": "next", "@theia/editor": "next", "@theia/electron": "next", "@theia/file-search": "next", @@ -19,7 +20,8 @@ "@theia/terminal": "next", "@theia/workspace": "next", "@theia/textmate-grammars": "next", - "arduino-ide-extension": "0.0.4" + "arduino-ide-extension": "0.0.2", + "arduino-debugger-extension": "0.0.2" }, "devDependencies": { "@theia/cli": "next" From 5baf43bf2520aa5caf8a42a0315da1df313888bb Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Tue, 19 Nov 2019 20:01:31 +0100 Subject: [PATCH 08/44] Integrate with classic mode and start debugging immediately --- .../arduino-debug-configuration-manager.ts | 16 ++++++++++ ...debug-frontend-application-contribution.ts | 30 +++++++++++++++++++ .../src/browser/frontend-module.ts | 16 ++++++++++ ...debug-frontend-application-contribution.ts | 25 ++++++++++++++++ .../arduino-debug-adapter-contribution.ts | 2 +- 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts create mode 100644 arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts create mode 100644 arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts diff --git a/arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts b/arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts new file mode 100644 index 00000000..56990444 --- /dev/null +++ b/arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts @@ -0,0 +1,16 @@ +import { DebugConfigurationManager } from "@theia/debug/lib/browser/debug-configuration-manager"; +import { injectable } from "inversify"; + +@injectable() +export class ArduinoDebugConfigurationManager extends DebugConfigurationManager { + + async addConfiguration() { + const { model } = this; + if (!model) { + return; + } + await this.doCreate(model); + await this.updateModels(); + } + +} \ No newline at end of file diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts new file mode 100644 index 00000000..37e5fb0f --- /dev/null +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -0,0 +1,30 @@ +import { injectable } from "inversify"; +import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; +import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; + +@injectable() +export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution { + + async start(noDebug?: boolean, debugSessionOptions?: DebugSessionOptions): Promise { + let current = debugSessionOptions ? debugSessionOptions : this.configurations.current; + // If no configurations are currently present, create the `launch.json` and prompt users to select the config. + if (!current) { + await this.configurations.addConfiguration(); + await this.configurations.load() + current = this.configurations.current; + } + if (current) { + if (noDebug !== undefined) { + current = { + ...current, + configuration: { + ...current.configuration, + noDebug + } + }; + } + await this.manager.start(current); + } + } + +} \ No newline at end of file diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts index fdd71fe9..2c88dcda 100644 --- a/arduino-debugger-extension/src/browser/frontend-module.ts +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -1,8 +1,24 @@ import { ContainerModule } from 'inversify'; import { VariableContribution } from '@theia/variable-resolver/lib/browser'; import { ArduinoVariableResolver } from './arduino-variable-resolver'; +import { ArduinoAdvancedMode } from 'arduino-ide-extension/lib/browser/arduino-frontend-contribution'; +import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; +import { SilentDebugFrontendApplicationContribution } from './silent-debug-frontend-application-contribution'; +import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; +import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; +import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(VariableContribution).toService(ArduinoVariableResolver); + + if (!ArduinoAdvancedMode.TOGGLED) { + unbind(DebugFrontendApplicationContribution); + bind(DebugFrontendApplicationContribution).to(SilentDebugFrontendApplicationContribution); + } else { + unbind(DebugConfigurationManager); + bind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); + unbind(DebugFrontendApplicationContribution); + bind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); + } }); diff --git a/arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts new file mode 100644 index 00000000..45deafcc --- /dev/null +++ b/arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts @@ -0,0 +1,25 @@ +import { injectable } from "inversify"; +import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; +import { MenuModelRegistry, CommandRegistry } from "@theia/core"; +import { KeybindingRegistry } from "@theia/core/lib/browser"; +import { TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar"; + +@injectable() +export class SilentDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution { + + async initializeLayout(): Promise { + } + + registerMenus(menus: MenuModelRegistry): void { + } + + registerCommands(registry: CommandRegistry): void { + } + + registerKeybindings(keybindings: KeybindingRegistry): void { + } + + registerToolbarItems(toolbar: TabBarToolbarRegistry): void { + } + +} \ No newline at end of file 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 b52d61b4..2c36bc33 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -150,4 +150,4 @@ interface ActualDebugConfig extends DebugConfiguration { objdump: string // extra gdb commands to run after initialisation initCommands?: string[] -} \ No newline at end of file +} From 2b7bceada0d5b60e79a6639d6f6c32a898875099 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Thu, 28 Nov 2019 12:04:07 +0100 Subject: [PATCH 09/44] Updated the packager, included the debug extension Note, I did not check in `/electron/packager/yarn.lock`. It should be checked in, see the `master`. Signed-off-by: Akos Kitta --- electron/build/template-package.json | 3 ++- electron/packager/index.js | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/electron/build/template-package.json b/electron/build/template-package.json index 9373471f..03a7ddf1 100644 --- a/electron/build/template-package.json +++ b/electron/build/template-package.json @@ -4,7 +4,8 @@ "main": "src-gen/frontend/electron-main.js", "author": "Arduino SA", "dependencies": { - "arduino-ide-extension": "file:../working-copy/arduino-ide-extension" + "arduino-ide-extension": "file:../working-copy/arduino-ide-extension", + "arduino-debugger-extension": "file:../working-copy/arduino-debugger-extension" }, "resolutions": { "**/fs-extra": "^4.0.3" diff --git a/electron/packager/index.js b/electron/packager/index.js index 974a2438..55f1dcec 100644 --- a/electron/packager/index.js +++ b/electron/packager/index.js @@ -42,13 +42,13 @@ // Copy the following items into the `working-copy` folder. Make sure to reuse the `yarn.lock`. | //----------------------------------------------------------------------------------------------+ mkdir('-p', path('..', workingCopy)); - for (const name of ['arduino-ide-extension', 'electron-app', 'yarn.lock', 'package.json', 'lerna.json']) { + for (const name of ['arduino-ide-extension', 'arduino-debugger-extension', 'electron-app', 'yarn.lock', 'package.json', 'lerna.json']) { cp('-rf', path(rootPath, name), path('..', workingCopy)); } - //-----------------------------------------------------+ + //---------------------------------------------+ // No need to build the `browser-app` example. | - //-----------------------------------------------------+ + //---------------------------------------------+ //@ts-ignore let pkg = require('../working-copy/package.json'); const workspaces = pkg.workspaces; @@ -72,6 +72,14 @@ // We have to do it before changing the dependencies to `local-path`. const unusedDependencies = await utils.collectUnusedDependencies('../working-copy/electron-app/'); + //-------------------------------------------------------------------------------------------------------------+ + // Change the regular NPM dependencies to `local-paths`, so that we can build them without any NPM registries. | + //-------------------------------------------------------------------------------------------------------------+ + // @ts-ignore + pkg = require('../working-copy/arduino-debugger-extension/package.json'); + pkg.dependencies['arduino-ide-extension'] = 'file:../arduino-ide-extension'; + fs.writeFileSync(path('..', workingCopy, 'arduino-debugger-extension', 'package.json'), JSON.stringify(pkg, null, 2)); + //------------------------------------------------------------------------------------+ // Merge the `working-copy/package.json` with `electron/build/template-package.json`. | //------------------------------------------------------------------------------------+ From 2ffca88c029c8ba1859d420d0d68e9bda09b5903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Tue, 17 Dec 2019 14:13:46 +0100 Subject: [PATCH 10/44] Updated versions and yarn.lock --- arduino-debugger-extension/package.json | 9 ++++----- browser-app/package.json | 2 +- electron-app/package.json | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/arduino-debugger-extension/package.json b/arduino-debugger-extension/package.json index f986bee0..282d505e 100644 --- a/arduino-debugger-extension/package.json +++ b/arduino-debugger-extension/package.json @@ -1,16 +1,15 @@ { "name": "arduino-debugger-extension", - "version": "0.0.2", + "version": "0.0.4", "description": "An extension for debugging Arduino programs", "license": "MIT", "engines": { "node": ">=10.10.0" }, "dependencies": { - "@theia/core": "next", "@theia/debug": "next", - "arduino-ide-extension": "0.0.2", - "cdt-gdb-adapter": "^0.0.14-next.4783033.0", + "arduino-ide-extension": "0.0.4", + "cdt-gdb-adapter": "^0.0.14", "vscode-debugadapter": "^1.26.0", "vscode-debugprotocol": "^1.26.0" }, @@ -38,4 +37,4 @@ "frontend": "lib/browser/frontend-module" } ] -} \ No newline at end of file +} diff --git a/browser-app/package.json b/browser-app/package.json index 7dbca598..d8c6175f 100644 --- a/browser-app/package.json +++ b/browser-app/package.json @@ -20,7 +20,7 @@ "@theia/workspace": "next", "@theia/textmate-grammars": "next", "arduino-ide-extension": "0.0.4", - "arduino-debugger-extension": "0.0.2" + "arduino-debugger-extension": "0.0.4" }, "devDependencies": { "@theia/cli": "next" diff --git a/electron-app/package.json b/electron-app/package.json index dcd4724b..434acfbc 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -20,8 +20,8 @@ "@theia/terminal": "next", "@theia/workspace": "next", "@theia/textmate-grammars": "next", - "arduino-ide-extension": "0.0.2", - "arduino-debugger-extension": "0.0.2" + "arduino-ide-extension": "0.0.4", + "arduino-debugger-extension": "0.0.4" }, "devDependencies": { "@theia/cli": "next" From 38ab95973eeedeb008d89cd0a25e1f41a95f512b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Tue, 17 Dec 2019 14:58:48 +0100 Subject: [PATCH 11/44] Updated code to current version --- ...debug-frontend-application-contribution.ts | 32 +++++++++++++++++-- .../src/browser/frontend-module.ts | 14 ++------ ...debug-frontend-application-contribution.ts | 25 --------------- .../node/debug-adapter/cmsis-debug-session.ts | 11 ++++--- 4 files changed, 39 insertions(+), 43 deletions(-) delete mode 100644 arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index 37e5fb0f..7568c72e 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -1,10 +1,17 @@ -import { injectable } from "inversify"; +import { injectable, inject } from 'inversify'; +import { MenuModelRegistry } from '@theia/core'; +import { KeybindingRegistry } from '@theia/core/lib/browser'; +import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; +import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; @injectable() export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution { + @inject(EditorMode) + protected readonly editorMode: EditorMode; + async start(noDebug?: boolean, debugSessionOptions?: DebugSessionOptions): Promise { let current = debugSessionOptions ? debugSessionOptions : this.configurations.current; // If no configurations are currently present, create the `launch.json` and prompt users to select the config. @@ -27,4 +34,25 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp } } -} \ No newline at end of file + initializeLayout(): Promise { + if (this.editorMode.proMode) + return super.initializeLayout(); + return Promise.resolve(); + } + + registerMenus(menus: MenuModelRegistry): void { + if (this.editorMode.proMode) + super.registerMenus(menus); + } + + registerKeybindings(keybindings: KeybindingRegistry): void { + if (this.editorMode.proMode) + super.registerKeybindings(keybindings); + } + + registerToolbarItems(toolbar: TabBarToolbarRegistry): void { + if (this.editorMode.proMode) + super.registerToolbarItems(toolbar); + } + +} diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts index 2c88dcda..f3223dba 100644 --- a/arduino-debugger-extension/src/browser/frontend-module.ts +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -1,9 +1,7 @@ import { ContainerModule } from 'inversify'; import { VariableContribution } from '@theia/variable-resolver/lib/browser'; import { ArduinoVariableResolver } from './arduino-variable-resolver'; -import { ArduinoAdvancedMode } from 'arduino-ide-extension/lib/browser/arduino-frontend-contribution'; import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; -import { SilentDebugFrontendApplicationContribution } from './silent-debug-frontend-application-contribution'; import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; @@ -11,14 +9,6 @@ import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-fro export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(VariableContribution).toService(ArduinoVariableResolver); - - if (!ArduinoAdvancedMode.TOGGLED) { - unbind(DebugFrontendApplicationContribution); - bind(DebugFrontendApplicationContribution).to(SilentDebugFrontendApplicationContribution); - } else { - unbind(DebugConfigurationManager); - bind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); - unbind(DebugFrontendApplicationContribution); - bind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); - } + rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); + rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); }); diff --git a/arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts deleted file mode 100644 index 45deafcc..00000000 --- a/arduino-debugger-extension/src/browser/silent-debug-frontend-application-contribution.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { injectable } from "inversify"; -import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; -import { MenuModelRegistry, CommandRegistry } from "@theia/core"; -import { KeybindingRegistry } from "@theia/core/lib/browser"; -import { TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar"; - -@injectable() -export class SilentDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution { - - async initializeLayout(): Promise { - } - - registerMenus(menus: MenuModelRegistry): void { - } - - registerCommands(registry: CommandRegistry): void { - } - - registerKeybindings(keybindings: KeybindingRegistry): void { - } - - registerToolbarItems(toolbar: TabBarToolbarRegistry): void { - } - -} \ No newline at end of file diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index b2e7c0e8..3ea4f243 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -32,7 +32,7 @@ import { CmsisBackend } from './cmsis-backend'; // import { PyocdServer } from './pyocd-server'; import { PortScanner } from './port-scanner'; import { SymbolTable } from './symbols'; -import * as varMgr from 'cdt-gdb-adapter/dist/varManager'; +import { VarManager } from 'cdt-gdb-adapter/dist/varManager'; import * as mi from './mi'; import { OpenocdServer } from './openocd-server'; @@ -54,13 +54,16 @@ export class CmsisDebugSession extends GDBDebugSession { protected portScanner = new PortScanner(); protected symbolTable!: SymbolTable; protected globalHandle!: number; + protected varMgr: VarManager; constructor() { super(); } protected createBackend(): GDBBackend { - return new CmsisBackend(); + const gdb = new CmsisBackend(); + this.varMgr = new VarManager(gdb); + return gdb; } protected async launchRequest(response: DebugProtocol.LaunchResponse, args: CmsisRequestArguments): Promise { @@ -334,7 +337,7 @@ export class CmsisDebugSession extends GDBDebugSession { } private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise { - let global = varMgr.getVar(frame.frameId, frame.threadId, depth, name); + let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name); if (global) { // Update value if it is already loaded @@ -351,7 +354,7 @@ export class CmsisDebugSession extends GDBDebugSession { expression, }); - global = varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse); + global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse); } return { From 1441b685ee8e2de8f5e06ad2d6a2e3c201fc2b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Fri, 20 Dec 2019 11:51:49 +0100 Subject: [PATCH 12/44] Improved error reporting on launch --- .../arduino-debug-adapter-contribution.ts | 42 ++++++------------- .../node/debug-adapter/cmsis-debug-session.ts | 38 +++++++++++++---- .../node/debug-adapter/{index.ts => main.ts} | 0 .../src/node/debug-adapter/openocd-server.ts | 6 +-- 4 files changed, 45 insertions(+), 41 deletions(-) rename arduino-debugger-extension/src/node/debug-adapter/{index.ts => main.ts} (100%) 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 2c36bc33..ab383bf8 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -1,23 +1,12 @@ -import { injectable, inject } from 'inversify'; +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 { BoardsService } from 'arduino-ide-extension/lib/common/protocol/boards-service'; -import { CoreService } from 'arduino-ide-extension/lib/common/protocol/core-service'; -import { FileSystem } from '@theia/filesystem/lib/common'; @injectable() export class ArduinoDebugAdapterContribution implements DebugAdapterContribution { - @inject(BoardsService) - protected readonly boardsService: BoardsService; - - @inject(CoreService) - protected readonly coreService: CoreService; - - @inject(FileSystem) - protected readonly fileSystem: FileSystem; type = "arduino"; @@ -39,26 +28,25 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution "description": "path to the sketch root ino file", "default": "${file}", }, - "runToMain": { - "description": "If enabled the debugger will run until the start of the main function.", - "type": "boolean", - "default": false - }, "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", + "default": false + }, "verbose": { "type": "boolean", "description": "Produce verbose log output", - "default": "false" + "default": false }, "debugDebugAdapter": { "type": "boolean", "description": "Start the debug adapter in debug mode (with --inspect-brk)", - "default": "false" + "default": false }, } } @@ -74,7 +62,7 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution if (!!config.debugDebugAdapter) { args.push('--inspect-brk') } - args = args.concat([path.join(__dirname, 'debug-adapter', 'index.js')]); + args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]); return { command: "node", @@ -88,25 +76,21 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution name: this.label, type: this.type, request: "launch", - sketch: "${file}", - - verbose: true, - runToMain: true, }, { 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}"], - - verbose: true, - runToMain: true, + + runToMain: false, + verbose: false, } ]; } diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 3ea4f243..0584a154 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -25,7 +25,7 @@ import { normalize } from 'path'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { Logger, logger, InitializedEvent, OutputEvent, Scope, TerminatedEvent } from 'vscode-debugadapter'; +import { Logger, logger, InitializedEvent, OutputEvent, Scope, TerminatedEvent, ErrorDestination } from 'vscode-debugadapter'; import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; import { CmsisBackend } from './cmsis-backend'; @@ -117,7 +117,6 @@ export class CmsisDebugSession extends GDBDebugSession { protected async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise { await super.setBreakPointsRequest(response, args); - return; } protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { @@ -224,13 +223,10 @@ export class CmsisDebugSession extends GDBDebugSession { this.gdb.on('notifyAsync', (resultClass, resultData) => this.handleGDBNotify(resultClass, resultData)); // gdb server has main info channel on stderr - this.gdbServer.on('stderr', data => { - this.sendEvent(new OutputEvent(data, 'stdout')) - }); - this.gdbServer.on('error', message => { - this.sendEvent(new TerminatedEvent()); - throw message; - }); + this.gdbServer.on('stderr', data => this.sendEvent(new OutputEvent(data, 'stdout'))); + const gdbServerErrors: any[] = [] + const gdbServerErrorAccumulator = (message: any) => gdbServerErrors.push(message); + this.gdbServer.on('error', gdbServerErrorAccumulator); try { this.symbolTable = new SymbolTable(args.program, args.objdump); @@ -264,12 +260,18 @@ export class CmsisDebugSession extends GDBDebugSession { this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); await this.gdbServer.spawn(args); await this.spawn(args); + if (gdbServerErrors.length > 0) { + throw new Error(gdbServerErrors.join('\n')); + } // Send commands await mi.sendTargetAsyncOn(this.gdb); await mi.sendTargetSelectRemote(this.gdb, remote); await mi.sendMonitorResetHalt(this.gdb); this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`)); + if (gdbServerErrors.length > 0) { + throw new Error(gdbServerErrors.join('\n')); + } // Download image const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image'); @@ -287,8 +289,17 @@ export class CmsisDebugSession extends GDBDebugSession { await mi.sendBreakOnFunction(this.gdb); } + if (gdbServerErrors.length > 0) { + throw new Error(gdbServerErrors.join('\n')); + } this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`)); this.sendEvent(new InitializedEvent()); + + this.gdbServer.removeListener('error', gdbServerErrorAccumulator); + this.gdbServer.on('error', message => { + logger.error(JSON.stringify(message)); + this.sendEvent(new TerminatedEvent()); + }); } private async getGlobalVariables(frameHandle: number): Promise { @@ -378,6 +389,15 @@ export class CmsisDebugSession extends GDBDebugSession { })); } + protected sendErrorResponse(response: DebugProtocol.Response, + codeOrMessage: number | DebugProtocol.Message, format?: string, + variables?: any, dest?: ErrorDestination): void { + if (!!format && (dest === undefined || dest === ErrorDestination.User)) { + format = format.replace('\n', '
'); + } + super.sendErrorResponse(response, codeOrMessage, format, variables, dest); + } + protected async stopSession() { // Pause debugging if (this.isRunning) { diff --git a/arduino-debugger-extension/src/node/debug-adapter/index.ts b/arduino-debugger-extension/src/node/debug-adapter/main.ts similarity index 100% rename from arduino-debugger-extension/src/node/debug-adapter/index.ts rename to arduino-debugger-extension/src/node/debug-adapter/main.ts diff --git a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts index 079ddcb0..e574ce98 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import * as os from 'os'; const LAUNCH_REGEX = /GDB server started/; -const ERROR_REGEX = /:ERROR:gdbserver:/; +const ERROR_REGEX = /^error:/mi; const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress export class OpenocdServer extends AbstractServer { @@ -20,14 +20,14 @@ export class OpenocdServer extends AbstractServer { sessionConfigFile += `telnet_port ${telnetPort}${"\n"}` } sessionConfigFile += `echo "GDB server started"${"\n"}` - + const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "arduino-debugger")); const sessionCfgPath = path.join(tmpdir, "gdb.cfg"); await fs.writeFile(sessionCfgPath, sessionConfigFile); let serverArguments = req.gdbServerArguments || []; serverArguments.push("--file", sessionCfgPath); - + return serverArguments; } From a886a106f5b3c9e54edb1fd2e0681e9a2b6bb61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Fri, 20 Dec 2019 15:58:13 +0100 Subject: [PATCH 13/44] Improved variable resolution and error handling --- .../src/browser/arduino-variable-resolver.ts | 163 ++++++++++++++---- .../arduino-debug-adapter-contribution.ts | 100 +++++------ .../src/node/debug-adapter/abstract-server.ts | 11 +- .../node/debug-adapter/cmsis-debug-session.ts | 16 +- .../src/node/debug-adapter/symbols.ts | 15 +- 5 files changed, 212 insertions(+), 93 deletions(-) diff --git a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts index ab527eb3..b094f1e1 100644 --- a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts +++ b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts @@ -2,9 +2,12 @@ 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'; -import { WorkspaceVariableContribution } from '@theia/workspace/lib/browser/workspace-variable-contribution'; @injectable() export class ArduinoVariableResolver implements VariableContribution { @@ -18,64 +21,134 @@ export class ArduinoVariableResolver implements VariableContribution { @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", + 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", + name: 'board', + description: 'Provides details about the currently selected board', resolve: this.resolveBoard.bind(this), }); - variables.registerVariable({ - name: "sketchBinary", - description: "Path to the sketch's binary file", + name: 'sketchBinary', + description: 'Path to the sketch\'s binary file', resolve: this.resolveSketchBinary.bind(this) }); } - // TODO: this function is a total hack. Instead of botching around with URI's it should ask something on the backend - // that properly udnerstands the filesystem. - protected async resolveSketchBinary(context?: URI, argument?: string, configurationSection?: string): Promise { - let sketchPath = argument || this.workspaceVars.getResourceUri()!.path.toString(); - return sketchPath.substring(0, sketchPath.length - 3) + "arduino.samd.arduino_zero_edbg.elf"; + 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); } - protected async resolveBoard(context?: URI, argument?: string, configurationSection?: string): Promise { - const { boardsConfig } = this.boardsServiceClient; - if (!boardsConfig || !boardsConfig.selectedBoard) { - throw new Error('No boards selected. Please select a board.'); + 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 fileStat.uri; } - if (!argument || argument === "fqbn") { + 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 bin.uri; + } + } + this.messageService.error('Cannot find sketch binary: ' + hint); + return undefined; + } + + protected async resolveBoard(context?: URI, argument?: string, configurationSection?: string): Promise { + const { boardsConfig } = this.boardsServiceClient; + if (!boardsConfig || !boardsConfig.selectedBoard) { + this.messageService.error('No boards selected. Please select a board.'); + return undefined; + } + + if (!argument || argument === 'fqbn') { return boardsConfig.selectedBoard.fqbn!; } - if (argument === "name") { + if (argument === 'name') { return boardsConfig.selectedBoard.name; } - const details = await this.boardsService.detail({id: boardsConfig.selectedBoard.fqbn!}); + const details = await this.boardsService.detail({ id: boardsConfig.selectedBoard.fqbn! }); if (!details.item) { - throw new Error("Cannot get board details"); + this.messageService.error('Details of the selected boards are not available.'); + return undefined; } - if (argument === "openocd-debug-file") { + if (argument === 'openocd-debug-file') { return details.item.locations!.debugScript; } return boardsConfig.selectedBoard.fqbn!; } - protected async resolveBoardTools(context?: URI, argument?: string, configurationSection?: string): Promise { + protected async resolveBoardTools(context?: URI, argument?: string, configurationSection?: string): Promise { const { boardsConfig } = this.boardsServiceClient; if (!boardsConfig || !boardsConfig.selectedBoard) { - throw new Error('No boards selected. Please select a board.'); + this.messageService.error('No boards selected. Please select a board.'); + return undefined; } - const details = await this.boardsService.detail({id: boardsConfig.selectedBoard.fqbn!}); + const details = await this.boardsService.detail({ id: boardsConfig.selectedBoard.fqbn! }); if (!details.item) { - throw new Error("Cannot get board details") + this.messageService.error('Details of the selected boards are not available.'); + return undefined; } let toolLocations: { [name: string]: ToolLocations } = {}; @@ -84,17 +157,37 @@ export class ArduinoVariableResolver implements VariableContribution { }) switch (argument) { - case "openocd": - return toolLocations["openocd"].main; - case "openocd-scripts": - return toolLocations["openocd"].scripts; - case "objdump": - return toolLocations["arm-none-eabi-gcc"].objdump; - case "gdb": - return toolLocations["arm-none-eabi-gcc"].gdb; + 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 boardsConfig.selectedBoard.name; } -} \ No newline at end of file +} 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 ab383bf8..5b1f62ad 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -1,59 +1,59 @@ 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 { 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'; @injectable() export class ArduinoDebugAdapterContribution implements DebugAdapterContribution { - type = "arduino"; + type = 'arduino'; - label = "Arduino"; + label = 'Arduino'; - languages = ["c", "cpp", "ino"]; + languages = ['c', 'cpp', 'ino']; debugAdapterSessionFactory?: DebugAdapterSessionFactory; - getSchemaAttributes?(): MaybePromise { + getSchemaAttributes(): MaybePromise { return [ { - "required": [ - "program" + 'required': [ + 'program' ], - "properties": { - "sketch": { - "type": "string", - "description": "path to the sketch root ino file", - "default": "${file}", + '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": "" + '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", - "default": false + 'runToMain': { + 'description': 'If enabled the debugger will run until the start of the main function.', + 'type': 'boolean', + 'default': false }, - "verbose": { - "type": "boolean", - "description": "Produce verbose log output", - "default": false + 'verbose': { + 'type': 'boolean', + 'description': 'Produce verbose log output', + 'default': false }, - "debugDebugAdapter": { - "type": "boolean", - "description": "Start the debug adapter in debug mode (with --inspect-brk)", - "default": false + 'debugDebugAdapter': { + 'type': 'boolean', + 'description': 'Start the debug adapter in debug mode (with --inspect-brk)', + 'default': false }, } } ] } - getConfigurationSnippets?(): MaybePromise { + getConfigurationSnippets(): MaybePromise { return [] } @@ -65,29 +65,29 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]); return { - command: "node", + command: 'node', args: args, } } - provideDebugConfigurations?(workspaceFolderUri?: string): MaybePromise { + provideDebugConfigurations(workspaceFolderUri?: string): MaybePromise { return [ { name: this.label, type: this.type, - request: "launch", - sketch: "${file}", + request: 'launch', + sketch: '${file}', }, { - name: this.label + " (explicit)", + name: this.label + ' (explicit)', type: this.type, - request: "launch", + request: 'launch', - program: "${sketchBinary}", - objdump: "${boardTools:objdump}", - gdb: "${boardTools:gdb}", - gdbServer: "${boardTools:openocd}", - gdbServerArguments: ["-s", "${boardTools:openocd-scripts}", "--file", "${board:openocd-debug-file}"], + 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, @@ -95,23 +95,23 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution ]; } - async resolveDebugConfiguration?(config: DebugConfiguration, workspaceFolderUri?: string): Promise { + 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 (config.sketch !== "${file}") { - sketchBinary = "${sketchBinary:" + config.sketch + "}"; + let sketchBinary = '${sketchBinary}' + if (typeof config.sketch === 'string' && config.sketch.indexOf('${') < 0) { + sketchBinary = '${sketchBinary:' + config.sketch + '}'; } const res: ActualDebugConfig = { ...config, - objdump: "${boardTools:objdump}", - gdb: "${boardTools:gdb}", - gdbServer: "${boardTools:openocd}", - gdbServerArguments: ["-s", "${boardTools:openocd-scripts}", "--file", "${board:openocd-debug-file}"], + objdump: '${boardTools:objdump}', + gdb: '${boardTools:gdb}', + gdbServer: '${boardTools:openocd}', + gdbServerArguments: ['-s', '${boardTools:openocd-scripts}', '--file', '${board:openocd-debug-file}'], program: sketchBinary } return res; diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts index baf053d7..0a839038 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts @@ -48,7 +48,14 @@ export abstract class AbstractServer extends EventEmitter { try { this.timer = setTimeout(() => this.onSpawnError(new Error('Timeout waiting for gdb server to start')), TIMEOUT); - const command = args.gdbServer || 'gdb-server'; + const command = args.gdbServer; + if (!command) { + throw new Error('Missing parameter: gdbServer'); + } + const varRegexp = /\$\{.*\}/; + if (varRegexp.test(command)) { + throw new Error(`Unresolved variable: ${command}`) + } const serverArguments = await this.resolveServerArguments(args); this.process = spawn(command, serverArguments, { cwd: dirname(command), @@ -110,7 +117,7 @@ export abstract class AbstractServer extends EventEmitter { protected onData(chunk: string | Buffer, buffer: string, event: string) { buffer += typeof chunk === 'string' ? chunk - : chunk.toString('utf8'); + : chunk.toString('utf8'); const end = buffer.lastIndexOf('\n'); if (end !== -1) { diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 0584a154..a22e8d0d 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -71,7 +71,8 @@ export class CmsisDebugSession extends GDBDebugSession { await this.runSession(args); this.sendResponse(response); } catch (err) { - this.sendErrorResponse(response, 1, err.message); + const message = `Failed to launch the debugger:\n${err.message}`; + this.sendErrorResponse(response, 1, message); } } @@ -80,7 +81,8 @@ export class CmsisDebugSession extends GDBDebugSession { await this.runSession(args); this.sendResponse(response); } catch (err) { - this.sendErrorResponse(response, 1, err.message); + const message = `Failed to attach the debugger:\n${err.message}`; + this.sendErrorResponse(response, 1, message); } } @@ -302,6 +304,14 @@ export class CmsisDebugSession extends GDBDebugSession { }); } + protected spawn(args: CmsisRequestArguments): Promise { + const varRegexp = /\$\{.*\}/; + if (args.gdb && varRegexp.test(args.gdb)) { + throw new Error(`Unresolved variable: ${args.gdb}`) + } + return super.spawn(args); + } + private async getGlobalVariables(frameHandle: number): Promise { const frame = this.frameHandles.get(frameHandle); const symbolInfo = this.symbolTable.getGlobalVariables(); @@ -393,7 +403,7 @@ export class CmsisDebugSession extends GDBDebugSession { codeOrMessage: number | DebugProtocol.Message, format?: string, variables?: any, dest?: ErrorDestination): void { if (!!format && (dest === undefined || dest === ErrorDestination.User)) { - format = format.replace('\n', '
'); + format = format.replace(/\n/g, '
'); } super.sendErrorResponse(response, codeOrMessage, format, variables, dest); } diff --git a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts index 1846cb2b..716f596d 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts @@ -25,7 +25,7 @@ */ import { spawnSync } from 'child_process'; -import { platform, EOL } from 'os'; +import { EOL } from 'os'; import { dirname, normalize, basename } from 'path'; export enum SymbolType { @@ -53,7 +53,6 @@ export interface SymbolInformation { hidden: boolean; } -const DEFAULT_OBJDUMP = platform() !== 'win32' ? 'arm-none-eabi-objdump' : 'arm-none-eabi-objdump.exe'; const SYMBOL_REGEX = /^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s([^\s]+)\s([0-9a-f]+)\s(.*)\r?$/; const TYPE_MAP: { [id: string]: SymbolType } = { @@ -74,7 +73,14 @@ export class SymbolTable { private symbols: SymbolInformation[] = []; - constructor(private program: string, private objdump: string = DEFAULT_OBJDUMP) { + constructor(private program: string, private objdump?: string) { + const varRegexp = /\$\{.*\}/; + if (varRegexp.test(program)) { + throw new Error(`Unresolved variable: ${program}`); + } + if (objdump && varRegexp.test(objdump)) { + throw new Error(`Unresolved variable: ${objdump}`); + } } public async loadSymbols(): Promise { @@ -128,6 +134,9 @@ export class SymbolTable { private execute(): Promise { return new Promise((resolve, reject) => { + if (!this.objdump) { + return reject(new Error('Missing parameter: objdump')); + } try { const { stdout, stderr } = spawnSync(this.objdump, [ '--syms', From 36ac97d95d75a903038a361ba80e29e5fed76de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 15 Jan 2020 16:43:11 +0100 Subject: [PATCH 14/44] Improved creation of default debug configurations --- .../arduino-debug-configuration-manager.ts | 39 +++++++++++++++---- ...debug-frontend-application-contribution.ts | 30 +++++++++----- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts b/arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts index 56990444..5e15f1cd 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-configuration-manager.ts @@ -4,13 +4,36 @@ import { injectable } from "inversify"; @injectable() export class ArduinoDebugConfigurationManager extends DebugConfigurationManager { - async addConfiguration() { - const { model } = this; - if (!model) { - return; - } - await this.doCreate(model); - await this.updateModels(); + get defaultDebugger(): Promise { + return this.debug.getDebuggersForLanguage('ino').then(debuggers => { + if (debuggers.length === 0) + return undefined; + return debuggers[0].type; + }); } -} \ No newline at end of file + protected async selectDebugType(): Promise { + const widget = this.editorManager.currentEditor; + if (!widget) { + return this.defaultDebugger; + } + const { languageId } = widget.editor.document; + const debuggers = await this.debug.getDebuggersForLanguage(languageId); + if (debuggers.length === 0) { + return this.defaultDebugger; + } + return this.quickPick.show(debuggers.map( + ({ label, type }) => ({ label, value: type }), + { placeholder: 'Select Environment' }) + ); + } + + async createDefaultConfiguration(): Promise { + const { model } = this; + if (model) { + await this.doCreate(model); + await this.updateModels(); + } + } + +} diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index 7568c72e..b4d61668 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -2,9 +2,10 @@ import { injectable, inject } from 'inversify'; import { MenuModelRegistry } from '@theia/core'; import { KeybindingRegistry } from '@theia/core/lib/browser'; import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; -import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; +import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; +import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; @injectable() export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution { @@ -13,12 +14,12 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp protected readonly editorMode: EditorMode; async start(noDebug?: boolean, debugSessionOptions?: DebugSessionOptions): Promise { - let current = debugSessionOptions ? debugSessionOptions : this.configurations.current; - // If no configurations are currently present, create the `launch.json` and prompt users to select the config. + const configurations = this.configurations as ArduinoDebugConfigurationManager; + let current = debugSessionOptions ? debugSessionOptions : configurations.current; + // If no configurations are currently present, create them if (!current) { - await this.configurations.addConfiguration(); - await this.configurations.load() - current = this.configurations.current; + await configurations.createDefaultConfiguration(); + current = configurations.current; } if (current) { if (noDebug !== undefined) { @@ -35,24 +36,33 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp } initializeLayout(): Promise { - if (this.editorMode.proMode) + if (this.editorMode.proMode) { return super.initializeLayout(); + } return Promise.resolve(); } registerMenus(menus: MenuModelRegistry): void { - if (this.editorMode.proMode) + if (this.editorMode.proMode) { super.registerMenus(menus); + menus.unregisterMenuAction(DebugCommands.START_NO_DEBUG); + } } registerKeybindings(keybindings: KeybindingRegistry): void { - if (this.editorMode.proMode) + if (this.editorMode.proMode) { super.registerKeybindings(keybindings); + keybindings.unregisterKeybinding({ + command: DebugCommands.START_NO_DEBUG.id, + keybinding: 'ctrl+f5' + }); + } } registerToolbarItems(toolbar: TabBarToolbarRegistry): void { - if (this.editorMode.proMode) + if (this.editorMode.proMode) { super.registerToolbarItems(toolbar); + } } } From 68db44fa4989f52889e87424e03a4bb2aa45e559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 16 Jan 2020 14:43:03 +0100 Subject: [PATCH 15/44] [debugger] Convert URIs to paths --- .../src/browser/arduino-variable-resolver.ts | 4 ++-- .../src/node/debug-adapter/cmsis-debug-session.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts index b094f1e1..be5c0434 100644 --- a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts +++ b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts @@ -75,7 +75,7 @@ export class ArduinoVariableResolver implements VariableContribution { return undefined; } if (!fileStat.isDirectory && fileStat.uri.endsWith('.elf')) { - return fileStat.uri; + return new URI(fileStat.uri).path.toString(); } let parent: FileStat | undefined; @@ -106,7 +106,7 @@ export class ArduinoVariableResolver implements VariableContribution { bin = parent.children.find(c => c.uri.endsWith('.elf')); } if (bin) { - return bin.uri; + return new URI(bin.uri).path.toString(); } } this.messageService.error('Cannot find sketch binary: ' + hint); diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index a22e8d0d..5dffc940 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -234,7 +234,7 @@ export class CmsisDebugSession extends GDBDebugSession { this.symbolTable = new SymbolTable(args.program, args.objdump); await this.symbolTable.loadSymbols(); } catch (error) { - this.sendEvent(new OutputEvent(`Unable to load debug symbols: ${error.message}`)); + throw new Error(`Unable to load debug symbols: ${error.message}`); } const port = await this.portScanner.findFreePort(); From 2ba95947dee63c9d86f163ae48e3398ab63983dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Tue, 21 Jan 2020 15:26:33 +0100 Subject: [PATCH 16/44] [debug] Shut down previous session properly before starting a new one --- .../browser/arduino-debug-session-manager.ts | 55 +++++++++++++++++++ .../src/browser/frontend-module.ts | 3 + .../arduino-debug-adapter-contribution.ts | 2 +- .../src/node/debug-adapter/abstract-server.ts | 8 ++- .../node/debug-adapter/cmsis-debug-session.ts | 36 +++++++----- 5 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts diff --git a/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts b/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts new file mode 100644 index 00000000..027ee692 --- /dev/null +++ b/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts @@ -0,0 +1,55 @@ +import { DebugSessionManager } from "@theia/debug/lib/browser/debug-session-manager"; +import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; +import { DebugSession } from "@theia/debug/lib/browser/debug-session"; + +export class ArduinoDebugSessionManager extends DebugSessionManager { + + static readonly COOL_DOWN_TIME = 5000; + + protected arduinoSession?: Promise; + protected lastSessionStopTime?: DOMHighResTimeStamp; + + start(options: DebugSessionOptions) { + if (options.configuration.type === 'arduino') { + if (this.arduinoSession) { + this.messageService.info('A debug session is already running. You must stop the running session before starting a new one.') + return Promise.resolve(undefined); + } + const superStart = super.start.bind(this); + const promise = (async resolve => { + if (this.lastSessionStopTime) { + const now = performance.now(); + if (now - this.lastSessionStopTime < ArduinoDebugSessionManager.COOL_DOWN_TIME) { + const waitTime = ArduinoDebugSessionManager.COOL_DOWN_TIME - Math.max(now - this.lastSessionStopTime, 0); + if (waitTime > 2000) { + const userWaitTime = Math.round(waitTime / 100) / 10; + this.messageService.info(`The previous debug session is cooling down. Waiting ${userWaitTime} seconds before starting a new session...`) + } + await new Promise(resolve => setTimeout(resolve, waitTime)); + } + } + return superStart(options); + })(); + this.arduinoSession = promise; + promise.then(session => { + if (!session) + this.arduinoSession = undefined; + }); + return promise; + } + return super.start(options); + } + + destroy(sessionId?: string): void { + if (this.arduinoSession) { + this.arduinoSession.then(session => { + if (session && sessionId === session.id) { + this.arduinoSession = undefined; + this.lastSessionStopTime = performance.now(); + } + }) + } + super.destroy(sessionId); + } + +} diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts index f3223dba..d02ff16e 100644 --- a/arduino-debugger-extension/src/browser/frontend-module.ts +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -1,14 +1,17 @@ import { ContainerModule } from 'inversify'; import { VariableContribution } from '@theia/variable-resolver/lib/browser'; import { ArduinoVariableResolver } from './arduino-variable-resolver'; +import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; +import { ArduinoDebugSessionManager } from './arduino-debug-session-manager'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(VariableContribution).toService(ArduinoVariableResolver); + rebind(DebugSessionManager).to(ArduinoDebugSessionManager).inSingletonScope(); rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); }); 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 5b1f62ad..910a506f 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -65,7 +65,7 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution args = args.concat([path.join(__dirname, 'debug-adapter', 'main')]); return { - command: 'node', + command: process.execPath, args: args, } } diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts index 0a839038..09a98331 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts @@ -40,7 +40,11 @@ export abstract class AbstractServer extends EventEmitter { protected launchReject?: (error: any) => void; protected timer?: NodeJS.Timer; - public spawn(args: CmsisRequestArguments): Promise { + get isRunning(): boolean { + return !!this.process && !this.process.killed; + } + + spawn(args: CmsisRequestArguments): Promise { return new Promise(async (resolve, reject) => { this.launchResolve = resolve; this.launchReject = reject; @@ -80,7 +84,7 @@ export abstract class AbstractServer extends EventEmitter { }); } - public kill() { + kill() { if (this.process) { this.process.kill('SIGINT'); } diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 5dffc940..095b7ecc 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -262,18 +262,14 @@ export class CmsisDebugSession extends GDBDebugSession { this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); await this.gdbServer.spawn(args); await this.spawn(args); - if (gdbServerErrors.length > 0) { - throw new Error(gdbServerErrors.join('\n')); - } + this.checkServerErrors(gdbServerErrors); // Send commands await mi.sendTargetAsyncOn(this.gdb); await mi.sendTargetSelectRemote(this.gdb, remote); await mi.sendMonitorResetHalt(this.gdb); this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`)); - if (gdbServerErrors.length > 0) { - throw new Error(gdbServerErrors.join('\n')); - } + this.checkServerErrors(gdbServerErrors); // Download image const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image'); @@ -291,9 +287,7 @@ export class CmsisDebugSession extends GDBDebugSession { await mi.sendBreakOnFunction(this.gdb); } - if (gdbServerErrors.length > 0) { - throw new Error(gdbServerErrors.join('\n')); - } + this.checkServerErrors(gdbServerErrors); this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`)); this.sendEvent(new InitializedEvent()); @@ -304,6 +298,12 @@ export class CmsisDebugSession extends GDBDebugSession { }); } + private checkServerErrors(errors: any[]): void { + if (errors.length > 0) { + throw new Error(errors.join('\n')); + } + } + protected spawn(args: CmsisRequestArguments): Promise { const varRegexp = /\$\{.*\}/; if (args.gdb && varRegexp.test(args.gdb)) { @@ -427,11 +427,19 @@ export class CmsisDebugSession extends GDBDebugSession { } // Stop gdb client and server - we give GDB five seconds to exit orderly before we kill the GDB server - setTimeout(() => this.gdbServer.kill(), 5000); - try { - await this.gdb.sendGDBExit(); - } catch (e) { - // Need to catch here in case the connection has already been closed + if (this.gdbServer.isRunning) { + const killPromise = new Promise(resolve => { + setTimeout(() => { + this.gdbServer.kill(); + resolve(); + }, 5000); + }); + try { + await this.gdb.sendGDBExit(); + } catch (e) { + // Need to catch here in case the connection has already been closed + } + await killPromise; } } From 576f96a5023610256c87262790f844326b45df4a Mon Sep 17 00:00:00 2001 From: jbicker Date: Wed, 22 Jan 2020 11:35:28 +0100 Subject: [PATCH 17/44] Validate workspace/current editor and open file on debug start Tests whether the current workspace is a sketchfolder and opens the respective ino file or whether the current editor is a ino file. Signed-off-by: jbicker --- ...debug-frontend-application-contribution.ts | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index b4d61668..68737b00 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -1,11 +1,16 @@ import { injectable, inject } from 'inversify'; -import { MenuModelRegistry } from '@theia/core'; +import { MenuModelRegistry, Path, MessageService } from '@theia/core'; import { KeybindingRegistry } from '@theia/core/lib/browser'; import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; +import { ArduinoWorkspaceService } from 'arduino-ide-extension/lib/browser/arduino-workspace-service'; +import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service'; +import { FileSystem } from '@theia/filesystem/lib/common'; +import URI from '@theia/core/lib/common/uri'; +import { EditorManager } from '@theia/editor/lib/browser'; @injectable() export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution { @@ -13,6 +18,21 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp @inject(EditorMode) protected readonly editorMode: EditorMode; + @inject(ArduinoWorkspaceService) + protected readonly workspaceService: ArduinoWorkspaceService; + + @inject(SketchesService) + protected readonly sketchesService: SketchesService; + + @inject(FileSystem) + protected readonly fileSystem: FileSystem; + + @inject(EditorManager) + protected readonly editorManager: EditorManager; + + @inject(MessageService) + protected readonly messageService: MessageService; + async start(noDebug?: boolean, debugSessionOptions?: DebugSessionOptions): Promise { const configurations = this.configurations as ArduinoDebugConfigurationManager; let current = debugSessionOptions ? debugSessionOptions : configurations.current; @@ -31,7 +51,28 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp } }; } - await this.manager.start(current); + if (current.configuration.type === 'arduino') { + const wsUri = await this.workspaceService.getDefaultWorkspaceUri(); + let sketchFileURI: URI | undefined; + if (wsUri && await this.sketchesService.isSketchFolder(wsUri)) { + const wsPath = new Path(wsUri); + const sketchFilePath = wsPath.join(wsPath.name + '.ino').toString(); + sketchFileURI = new URI(sketchFilePath); + } else if (this.editorManager.currentEditor) { + const editorURI = this.editorManager.currentEditor.getResourceUri(); + if (editorURI && editorURI.path && editorURI.path.ext === '.ino') { + sketchFileURI = editorURI; + } + } + if (sketchFileURI) { + await this.editorManager.open(sketchFileURI); + await this.manager.start(current); + } else { + this.messageService.error('Please open a valid INO file.') + } + } else { + await this.manager.start(current); + } } } From 828379cdc97c2f43b7566e8a180fbf30486b15fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 22 Jan 2020 13:43:56 +0100 Subject: [PATCH 18/44] Don't send 'terminated' on 'disconnect' (arduino/arduino-pro-ide#183) --- .../src/node/debug-adapter/abstract-server.ts | 5 ++++- .../src/node/debug-adapter/cmsis-debug-session.ts | 7 +++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts index 09a98331..6ae6fc2b 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts @@ -40,6 +40,8 @@ export abstract class AbstractServer extends EventEmitter { protected launchReject?: (error: any) => void; protected timer?: NodeJS.Timer; + serverErrorEmitted = false; + get isRunning(): boolean { return !!this.process && !this.process.killed; } @@ -98,7 +100,7 @@ export abstract class AbstractServer extends EventEmitter { this.emit('exit', code, signal); // Code can be undefined, null or 0 and we want to ignore those values - if (!!code) { + if (!!code && !this.serverErrorEmitted) { this.emit('error', `GDB server stopped unexpectedly with exit code ${code}`); } } @@ -141,6 +143,7 @@ export abstract class AbstractServer extends EventEmitter { if (this.serverError(data)) { this.emit('error', data.split(EOL)[0]); + this.serverErrorEmitted = true; } } diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 095b7ecc..076e4122 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -208,9 +208,6 @@ export class CmsisDebugSession extends GDBDebugSession { protected async disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): Promise { try { this.stopSession(); - if (!args || !args.restart) { - this.sendEvent(new TerminatedEvent()); - } this.sendResponse(response); } catch (err) { this.sendErrorResponse(response, 1, err.message); @@ -294,7 +291,9 @@ export class CmsisDebugSession extends GDBDebugSession { this.gdbServer.removeListener('error', gdbServerErrorAccumulator); this.gdbServer.on('error', message => { logger.error(JSON.stringify(message)); - this.sendEvent(new TerminatedEvent()); + if (!this.gdbServer.serverErrorEmitted) { + this.sendEvent(new TerminatedEvent()); + } }); } From fe3cc1904c6d7f04c678ac83a4fa5cb7298f9dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 22 Jan 2020 13:44:57 +0100 Subject: [PATCH 19/44] Don't use EventEmitter method 'off' introduced in NodeJS 10 (arduino/arduino-pro-ide#182) --- .../src/node/debug-adapter/cmsis-debug-session.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 076e4122..97e2bda1 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -273,7 +273,7 @@ export class CmsisDebugSession extends GDBDebugSession { progressListener(0); this.gdbServer.on('progress', progressListener); await mi.sendTargetDownload(this.gdb); - this.gdbServer.off('progress', progressListener); + this.gdbServer.removeListener('progress', progressListener); progressListener(100); // Halt after image download From 8a78e09c6dee4251f98058ee53efa17f36531ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 22 Jan 2020 16:02:00 +0100 Subject: [PATCH 20/44] Improved error message when spawning commands fails with stderr output --- arduino-ide-extension/src/node/exec-util.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/arduino-ide-extension/src/node/exec-util.ts b/arduino-ide-extension/src/node/exec-util.ts index cb865602..ab384a88 100644 --- a/arduino-ide-extension/src/node/exec-util.ts +++ b/arduino-ide-extension/src/node/exec-util.ts @@ -33,25 +33,33 @@ export async function getExecPath(commandName: string, logger: ILogger, versionA export function spawnCommand(command: string, args: string[], logger: ILogger): Promise { return new Promise((resolve, reject) => { const cp = spawn(command, args, { windowsHide: true, shell: true }); - const buffers: Buffer[] = []; - cp.stdout.on('data', (b: Buffer) => buffers.push(b)); + const outBuffers: Buffer[] = []; + const errBuffers: Buffer[] = []; + cp.stdout.on('data', (b: Buffer) => outBuffers.push(b)); + cp.stderr.on('data', (b: Buffer) => errBuffers.push(b)); cp.on('error', error => { - logger.error(`Error executing ${command} with args: ${JSON.stringify(args)}.`, error); + logger.error(`Error executing ${command} ${args.join(' ')}`, error); reject(error); }); cp.on('exit', (code, signal) => { if (code === 0) { - const result = Buffer.concat(buffers).toString('utf8').trim() + const result = Buffer.concat(outBuffers).toString('utf8').trim() resolve(result); return; } + if (errBuffers.length > 0) { + const message = Buffer.concat(errBuffers).toString('utf8').trim(); + logger.error(`Error executing ${command} ${args.join(' ')}: ${message}`); + reject(new Error(`Process failed with error: ${message}`)); + return; + } if (signal) { - logger.error(`Unexpected signal '${signal}' when executing ${command} with args: ${JSON.stringify(args)}.`); + logger.error(`Unexpected signal '${signal}' when executing ${command} ${args.join(' ')}`); reject(new Error(`Process exited with signal: ${signal}`)); return; } if (code) { - logger.error(`Unexpected exit code '${code}' when executing ${command} with args: ${JSON.stringify(args)}.`); + logger.error(`Unexpected exit code '${code}' when executing ${command} ${args.join(' ')}`); reject(new Error(`Process exited with exit code: ${code}`)); return; } From fb50244a292932ab6114914af5784d57b9bd87e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 22 Jan 2020 16:52:05 +0100 Subject: [PATCH 21/44] [debugger] Kill the gdb server on debug adapter process exit --- ...debug-frontend-application-contribution.ts | 2 +- .../browser/arduino-debug-session-manager.ts | 55 ------------------- .../src/browser/frontend-module.ts | 3 - .../node/debug-adapter/cmsis-debug-session.ts | 24 ++++---- 4 files changed, 11 insertions(+), 73 deletions(-) delete mode 100644 arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index 68737b00..490fa159 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -68,7 +68,7 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp await this.editorManager.open(sketchFileURI); await this.manager.start(current); } else { - this.messageService.error('Please open a valid INO file.') + this.messageService.error('Please open a sketch file to start debugging.') } } else { await this.manager.start(current); diff --git a/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts b/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts deleted file mode 100644 index 027ee692..00000000 --- a/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { DebugSessionManager } from "@theia/debug/lib/browser/debug-session-manager"; -import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; -import { DebugSession } from "@theia/debug/lib/browser/debug-session"; - -export class ArduinoDebugSessionManager extends DebugSessionManager { - - static readonly COOL_DOWN_TIME = 5000; - - protected arduinoSession?: Promise; - protected lastSessionStopTime?: DOMHighResTimeStamp; - - start(options: DebugSessionOptions) { - if (options.configuration.type === 'arduino') { - if (this.arduinoSession) { - this.messageService.info('A debug session is already running. You must stop the running session before starting a new one.') - return Promise.resolve(undefined); - } - const superStart = super.start.bind(this); - const promise = (async resolve => { - if (this.lastSessionStopTime) { - const now = performance.now(); - if (now - this.lastSessionStopTime < ArduinoDebugSessionManager.COOL_DOWN_TIME) { - const waitTime = ArduinoDebugSessionManager.COOL_DOWN_TIME - Math.max(now - this.lastSessionStopTime, 0); - if (waitTime > 2000) { - const userWaitTime = Math.round(waitTime / 100) / 10; - this.messageService.info(`The previous debug session is cooling down. Waiting ${userWaitTime} seconds before starting a new session...`) - } - await new Promise(resolve => setTimeout(resolve, waitTime)); - } - } - return superStart(options); - })(); - this.arduinoSession = promise; - promise.then(session => { - if (!session) - this.arduinoSession = undefined; - }); - return promise; - } - return super.start(options); - } - - destroy(sessionId?: string): void { - if (this.arduinoSession) { - this.arduinoSession.then(session => { - if (session && sessionId === session.id) { - this.arduinoSession = undefined; - this.lastSessionStopTime = performance.now(); - } - }) - } - super.destroy(sessionId); - } - -} diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts index d02ff16e..f3223dba 100644 --- a/arduino-debugger-extension/src/browser/frontend-module.ts +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -1,17 +1,14 @@ import { ContainerModule } from 'inversify'; import { VariableContribution } from '@theia/variable-resolver/lib/browser'; import { ArduinoVariableResolver } from './arduino-variable-resolver'; -import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; -import { ArduinoDebugSessionManager } from './arduino-debug-session-manager'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(VariableContribution).toService(ArduinoVariableResolver); - rebind(DebugSessionManager).to(ArduinoDebugSessionManager).inSingletonScope(); rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); }); diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 97e2bda1..21c5f317 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -258,6 +258,11 @@ export class CmsisDebugSession extends GDBDebugSession { this.progressEvent(0, 'Starting Debugger'); this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); await this.gdbServer.spawn(args); + process.on('exit', () => { + if (this.gdbServer.isRunning) { + this.gdbServer.kill(); + } + }); await this.spawn(args); this.checkServerErrors(gdbServerErrors); @@ -425,20 +430,11 @@ export class CmsisDebugSession extends GDBDebugSession { } } - // Stop gdb client and server - we give GDB five seconds to exit orderly before we kill the GDB server - if (this.gdbServer.isRunning) { - const killPromise = new Promise(resolve => { - setTimeout(() => { - this.gdbServer.kill(); - resolve(); - }, 5000); - }); - try { - await this.gdb.sendGDBExit(); - } catch (e) { - // Need to catch here in case the connection has already been closed - } - await killPromise; + // Stop gdb client + try { + await this.gdb.sendGDBExit(); + } catch (e) { + // Need to catch here in case the connection has already been closed } } From 0f35821d142f2150ec577f78b36f87fea04d54f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 23 Jan 2020 11:37:55 +0100 Subject: [PATCH 22/44] Reuse `spawnCommand` util for more robust command execution --- .../src/node/debug-adapter/symbols.ts | 31 ++++--------------- arduino-ide-extension/src/node/exec-util.ts | 18 ++++++++--- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts index 716f596d..244d9f52 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts @@ -24,9 +24,9 @@ * SOFTWARE. */ -import { spawnSync } from 'child_process'; import { EOL } from 'os'; -import { dirname, normalize, basename } from 'path'; +import { normalize, basename } from 'path'; +import { spawnCommand } from 'arduino-ide-extension/lib/node/exec-util'; export enum SymbolType { Function, @@ -133,28 +133,9 @@ export class SymbolTable { } private execute(): Promise { - return new Promise((resolve, reject) => { - if (!this.objdump) { - return reject(new Error('Missing parameter: objdump')); - } - try { - const { stdout, stderr } = spawnSync(this.objdump, [ - '--syms', - this.program - ], { - cwd: dirname(this.objdump), - windowsHide: true - }); - - const error = stderr.toString('utf8'); - if (error) { - return reject(new Error(error)); - } - - resolve(stdout.toString('utf8')); - } catch (error) { - return reject(new Error(error)); - } - }); + if (!this.objdump) { + return Promise.reject(new Error('Missing parameter: objdump')); + } + return spawnCommand(this.objdump, ['--syms', this.program]); } } diff --git a/arduino-ide-extension/src/node/exec-util.ts b/arduino-ide-extension/src/node/exec-util.ts index ab384a88..6c1368fe 100644 --- a/arduino-ide-extension/src/node/exec-util.ts +++ b/arduino-ide-extension/src/node/exec-util.ts @@ -30,7 +30,7 @@ export async function getExecPath(commandName: string, logger: ILogger, versionA return buildCommand; } -export function spawnCommand(command: string, args: string[], logger: ILogger): Promise { +export function spawnCommand(command: string, args: string[], logger?: ILogger): Promise { return new Promise((resolve, reject) => { const cp = spawn(command, args, { windowsHide: true, shell: true }); const outBuffers: Buffer[] = []; @@ -38,7 +38,9 @@ export function spawnCommand(command: string, args: string[], logger: ILogger): cp.stdout.on('data', (b: Buffer) => outBuffers.push(b)); cp.stderr.on('data', (b: Buffer) => errBuffers.push(b)); cp.on('error', error => { - logger.error(`Error executing ${command} ${args.join(' ')}`, error); + if (logger) { + logger.error(`Error executing ${command} ${args.join(' ')}`, error); + } reject(error); }); cp.on('exit', (code, signal) => { @@ -49,17 +51,23 @@ export function spawnCommand(command: string, args: string[], logger: ILogger): } if (errBuffers.length > 0) { const message = Buffer.concat(errBuffers).toString('utf8').trim(); - logger.error(`Error executing ${command} ${args.join(' ')}: ${message}`); + if (logger) { + logger.error(`Error executing ${command} ${args.join(' ')}: ${message}`); + } reject(new Error(`Process failed with error: ${message}`)); return; } if (signal) { - logger.error(`Unexpected signal '${signal}' when executing ${command} ${args.join(' ')}`); + if (logger) { + logger.error(`Unexpected signal '${signal}' when executing ${command} ${args.join(' ')}`); + } reject(new Error(`Process exited with signal: ${signal}`)); return; } if (code) { - logger.error(`Unexpected exit code '${code}' when executing ${command} ${args.join(' ')}`); + if (logger) { + logger.error(`Unexpected exit code '${code}' when executing ${command} ${args.join(' ')}`); + } reject(new Error(`Process exited with exit code: ${code}`)); return; } From 8c3fab824fccd85f642059c0ebf2914f6f7e0212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 23 Jan 2020 11:38:43 +0100 Subject: [PATCH 23/44] [debugger] Resolve URIs through FileSystem --- .../src/browser/arduino-variable-resolver.ts | 6 +++--- .../src/node/boards-service-impl.ts | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts index be5c0434..806cff4e 100644 --- a/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts +++ b/arduino-debugger-extension/src/browser/arduino-variable-resolver.ts @@ -75,7 +75,7 @@ export class ArduinoVariableResolver implements VariableContribution { return undefined; } if (!fileStat.isDirectory && fileStat.uri.endsWith('.elf')) { - return new URI(fileStat.uri).path.toString(); + return this.fileSystem.getFsPath(fileStat.uri); } let parent: FileStat | undefined; @@ -106,7 +106,7 @@ export class ArduinoVariableResolver implements VariableContribution { bin = parent.children.find(c => c.uri.endsWith('.elf')); } if (bin) { - return new URI(bin.uri).path.toString(); + return this.fileSystem.getFsPath(bin.uri); } } this.messageService.error('Cannot find sketch binary: ' + hint); @@ -187,7 +187,7 @@ export class ArduinoVariableResolver implements VariableContribution { } } - return boardsConfig.selectedBoard.name; + return undefined; } } diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index 83fa9678..fd2bfbf2 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -2,6 +2,7 @@ import * as PQueue from 'p-queue'; import { injectable, inject, postConstruct, named } from 'inversify'; import { ILogger } from '@theia/core/lib/common/logger'; import { Deferred } from '@theia/core/lib/common/promise-util'; +import { FileSystem } from '@theia/filesystem/lib/common'; import { BoardsService, AttachedSerialBoard, BoardPackage, Board, AttachedNetworkBoard, BoardsServiceClient, Port, BoardDetails, Tool, ToolLocations, BoardDetailLocations @@ -16,7 +17,6 @@ import { ToolOutputServiceServer } from '../common/protocol/tool-output-service' import { Installable } from '../common/protocol/installable'; import { ConfigService } from '../common/protocol/config-service'; import * as path from 'path'; -import URI from '@theia/core/lib/common/uri'; @injectable() export class BoardsServiceImpl implements BoardsService { @@ -37,6 +37,9 @@ export class BoardsServiceImpl implements BoardsService { @inject(ConfigService) protected readonly configService: ConfigService; + @inject(FileSystem) + protected readonly fileSystem: FileSystem; + protected discoveryInitialized = false; protected discoveryTimer: NodeJS.Timer | undefined; /** @@ -251,7 +254,10 @@ export class BoardsServiceImpl implements BoardsService { // TODO: these location should come from the CLI/daemon rather than us botching them together protected async getBoardLocations(details: BoardDetailsResp): Promise { const config = await this.configService.getConfiguration(); - const datadir = new URI(config.dataDirUri).path.toString(); + const datadir = await this.fileSystem.getFsPath(config.dataDirUri); + if (!datadir) { + return undefined; + } return { debugScript: path.join(datadir, "packages", "arduino", "hardware", "samd", "1.8.4", "variants", "arduino_zero", "openocd_scripts", "arduino_zero.cfg") @@ -259,11 +265,14 @@ export class BoardsServiceImpl implements BoardsService { } // TODO: these location should come from the CLI/daemon rather than us botching them together - protected async getToolLocations(t: RequiredTool) { + protected async getToolLocations(t: RequiredTool): Promise { const config = await this.configService.getConfiguration(); - const datadir = new URI(config.dataDirUri).path.toString(); - const toolBasePath = path.join(datadir, "packages", "arduino", "tools"); + const datadir = await this.fileSystem.getFsPath(config.dataDirUri); + if (!datadir) { + return undefined; + } + const toolBasePath = path.join(datadir, "packages", "arduino", "tools"); let loc: ToolLocations = { main: path.join(toolBasePath, t.getName(), t.getVersion()) }; From 879d5c7cc9a36fa23c15899fe80610698ad00911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 23 Jan 2020 13:19:05 +0100 Subject: [PATCH 24/44] Fixed compile error --- ...rduino-debug-frontend-application-contribution.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index 490fa159..ade15c39 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -4,9 +4,9 @@ import { KeybindingRegistry } from '@theia/core/lib/browser'; import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; +import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; -import { ArduinoWorkspaceService } from 'arduino-ide-extension/lib/browser/arduino-workspace-service'; import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service'; import { FileSystem } from '@theia/filesystem/lib/common'; import URI from '@theia/core/lib/common/uri'; @@ -18,8 +18,8 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp @inject(EditorMode) protected readonly editorMode: EditorMode; - @inject(ArduinoWorkspaceService) - protected readonly workspaceService: ArduinoWorkspaceService; + @inject(WorkspaceService) + protected readonly workspaceService: WorkspaceService; @inject(SketchesService) protected readonly sketchesService: SketchesService; @@ -52,10 +52,10 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp }; } if (current.configuration.type === 'arduino') { - const wsUri = await this.workspaceService.getDefaultWorkspaceUri(); + const wsStat = this.workspaceService.workspace; let sketchFileURI: URI | undefined; - if (wsUri && await this.sketchesService.isSketchFolder(wsUri)) { - const wsPath = new Path(wsUri); + if (wsStat && await this.sketchesService.isSketchFolder(wsStat.uri)) { + const wsPath = new Path(wsStat.uri); const sketchFilePath = wsPath.join(wsPath.name + '.ino').toString(); sketchFileURI = new URI(sketchFilePath); } else if (this.editorManager.currentEditor) { From 2855026cec1d85a47d875cd56c710decbfda9dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 23 Jan 2020 13:42:30 +0100 Subject: [PATCH 25/44] [debugger] Don't allow running more than one debug session --- .../src/browser/arduino-debug-session-manager.ts | 14 ++++++++++++++ .../src/browser/frontend-module.ts | 3 +++ 2 files changed, 17 insertions(+) create mode 100644 arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts diff --git a/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts b/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts new file mode 100644 index 00000000..79f81d58 --- /dev/null +++ b/arduino-debugger-extension/src/browser/arduino-debug-session-manager.ts @@ -0,0 +1,14 @@ +import { DebugSessionManager } from "@theia/debug/lib/browser/debug-session-manager"; +import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; + +export class ArduinoDebugSessionManager extends DebugSessionManager { + + start(options: DebugSessionOptions) { + if (options.configuration.type === 'arduino' && this.sessions.find(s => s.configuration.type === 'arduino')) { + this.messageService.info('A debug session is already running. You must stop the running session before starting a new one.') + return Promise.resolve(undefined); + } + return super.start(options); + } + +} diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts index f3223dba..d02ff16e 100644 --- a/arduino-debugger-extension/src/browser/frontend-module.ts +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -1,14 +1,17 @@ import { ContainerModule } from 'inversify'; import { VariableContribution } from '@theia/variable-resolver/lib/browser'; import { ArduinoVariableResolver } from './arduino-variable-resolver'; +import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager'; import { DebugFrontendApplicationContribution } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugConfigurationManager } from '@theia/debug/lib/browser/debug-configuration-manager'; import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; +import { ArduinoDebugSessionManager } from './arduino-debug-session-manager'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(VariableContribution).toService(ArduinoVariableResolver); + rebind(DebugSessionManager).to(ArduinoDebugSessionManager).inSingletonScope(); rebind(DebugConfigurationManager).to(ArduinoDebugConfigurationManager).inSingletonScope(); rebind(DebugFrontendApplicationContribution).to(ArduinoDebugFrontendApplicationContribution); }); From 40ddd3714b0bb7a8b739aa63920461be19e98903 Mon Sep 17 00:00:00 2001 From: jbicker Date: Thu, 23 Jan 2020 14:42:58 +0100 Subject: [PATCH 26/44] Added signal handlers to kill gdb server. Signed-off-by: jbicker --- .../node/debug-adapter/cmsis-debug-session.ts | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts index 21c5f317..5b9d3f77 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts @@ -50,14 +50,32 @@ const STATIC_HANDLES_FINISH = 0x01FFFF; export class CmsisDebugSession extends GDBDebugSession { - protected gdbServer = new OpenocdServer(); - protected portScanner = new PortScanner(); + protected readonly gdbServer = new OpenocdServer(); + protected readonly portScanner = new PortScanner(); protected symbolTable!: SymbolTable; protected globalHandle!: number; protected varMgr: VarManager; constructor() { super(); + this.addProcessListeners(); + } + + protected addProcessListeners(): void { + process.on('exit', () => { + if (this.gdbServer.isRunning) { + this.gdbServer.kill(); + } + }); + const signalHandler = () => { + if (this.gdbServer.isRunning) { + this.gdbServer.kill(); + } + process.exit(); + } + process.on('SIGINT', signalHandler); + process.on('SIGTERM', signalHandler); + process.on('SIGHUP', signalHandler); } protected createBackend(): GDBBackend { @@ -258,11 +276,6 @@ export class CmsisDebugSession extends GDBDebugSession { this.progressEvent(0, 'Starting Debugger'); this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); await this.gdbServer.spawn(args); - process.on('exit', () => { - if (this.gdbServer.isRunning) { - this.gdbServer.kill(); - } - }); await this.spawn(args); this.checkServerErrors(gdbServerErrors); From 4c63af572ee9a2b7e0f7b4be18b454666ed47a02 Mon Sep 17 00:00:00 2001 From: jbicker Date: Fri, 24 Jan 2020 10:04:23 +0100 Subject: [PATCH 27/44] Added debug button to toolbar. Signed-off-by: jbicker --- ...debug-frontend-application-contribution.ts | 30 +++++++++++++++++-- .../src/browser/frontend-module.ts | 3 ++ .../src/browser/style/debug-dark.svg | 4 +++ .../src/browser/style/index.css | 16 ++++++++++ .../browser/arduino-frontend-contribution.tsx | 9 ++++-- .../src/browser/toolbar/arduino-toolbar.tsx | 2 +- 6 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 arduino-debugger-extension/src/browser/style/debug-dark.svg create mode 100644 arduino-debugger-extension/src/browser/style/index.css diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index ade15c39..8f9b6ea0 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -1,6 +1,6 @@ import { injectable, inject } from 'inversify'; -import { MenuModelRegistry, Path, MessageService } from '@theia/core'; -import { KeybindingRegistry } from '@theia/core/lib/browser'; +import { MenuModelRegistry, Path, MessageService, Command, CommandRegistry } from '@theia/core'; +import { KeybindingRegistry, Widget } from '@theia/core/lib/browser'; import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; @@ -11,6 +11,14 @@ import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketc import { FileSystem } from '@theia/filesystem/lib/common'; import URI from '@theia/core/lib/common/uri'; import { EditorManager } from '@theia/editor/lib/browser'; +import { ArduinoToolbar } from 'arduino-ide-extension/lib/browser/toolbar/arduino-toolbar'; + +export namespace ArduinoDebugCommands { + export const START_DEBUG: Command = { + id: 'arduino-start-debug', + label: 'Start Debugging' + } +} @injectable() export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendApplicationContribution { @@ -104,6 +112,24 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp if (this.editorMode.proMode) { super.registerToolbarItems(toolbar); } + toolbar.registerItem({ + id: ArduinoDebugCommands.START_DEBUG.id, + command: ArduinoDebugCommands.START_DEBUG.id, + tooltip: 'Start Debugging', + priority: 1 + }); } + registerCommands(registry: CommandRegistry): void { + super.registerCommands(registry); + registry.registerCommand(ArduinoDebugCommands.START_DEBUG, { + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', + isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left', + execute: async (widget: Widget, target: EventTarget) => { + registry.executeCommand(DebugCommands.START.id); + } + }); + } + + } diff --git a/arduino-debugger-extension/src/browser/frontend-module.ts b/arduino-debugger-extension/src/browser/frontend-module.ts index d02ff16e..1270812a 100644 --- a/arduino-debugger-extension/src/browser/frontend-module.ts +++ b/arduino-debugger-extension/src/browser/frontend-module.ts @@ -8,6 +8,9 @@ import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration- import { ArduinoDebugFrontendApplicationContribution } from './arduino-debug-frontend-application-contribution'; import { ArduinoDebugSessionManager } from './arduino-debug-session-manager'; +import '../../src/browser/style/index.css'; + + export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ArduinoVariableResolver).toSelf().inSingletonScope(); bind(VariableContribution).toService(ArduinoVariableResolver); diff --git a/arduino-debugger-extension/src/browser/style/debug-dark.svg b/arduino-debugger-extension/src/browser/style/debug-dark.svg new file mode 100644 index 00000000..5c414170 --- /dev/null +++ b/arduino-debugger-extension/src/browser/style/debug-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/arduino-debugger-extension/src/browser/style/index.css b/arduino-debugger-extension/src/browser/style/index.css new file mode 100644 index 00000000..1e3d51a6 --- /dev/null +++ b/arduino-debugger-extension/src/browser/style/index.css @@ -0,0 +1,16 @@ +.arduino-start-debug-icon { + -webkit-mask: url('debug-dark.svg') 50%; + mask: url('debug-dark.svg') 50%; + -webkit-mask-size: 100%; + mask-size: 100%; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + display: flex; + justify-content: center; + align-items: center; + color: var(--theia-ui-button-font-color); +} + +.arduino-start-debug { + border-radius: 12px; +} diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index bcdd133a..2bafe514 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -192,12 +192,14 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut registry.registerItem({ id: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id, command: ArduinoCommands.SHOW_OPEN_CONTEXT_MENU.id, - tooltip: 'Open' + tooltip: 'Open', + priority: 2 }); registry.registerItem({ id: ArduinoCommands.SAVE_SKETCH.id, command: ArduinoCommands.SAVE_SKETCH.id, - tooltip: 'Save' + tooltip: 'Save', + priority: 2 }); registry.registerItem({ id: BoardsToolBarItem.TOOLBAR_ID, @@ -206,7 +208,8 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut commands={this.commandRegistry} boardsServiceClient={this.boardsServiceClient} boardService={this.boardsService} />, - isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left' + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', + priority: 2 }); registry.registerItem({ id: 'toggle-serial-monitor', diff --git a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx index 88a6ed3a..f9b6b5ab 100644 --- a/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx +++ b/arduino-ide-extension/src/browser/toolbar/arduino-toolbar.tsx @@ -91,7 +91,7 @@ export class ArduinoToolbar extends ReactWidget { protected updateItems(items: Array): void { this.items.clear(); - const revItems = items.reverse(); + const revItems = items.sort(TabBarToolbarItem.PRIORITY_COMPARATOR).reverse(); for (const item of revItems) { this.items.set(item.id, item); } From 887682b9c3be84f05451a11140e08ffbfc011a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 3 Feb 2020 10:08:03 +0100 Subject: [PATCH 28/44] Updated yarn.lock --- yarn.lock | 96 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 18 deletions(-) diff --git a/yarn.lock b/yarn.lock index a9161a10..ad260e98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1880,6 +1880,15 @@ serialize-javascript "^1.4.0" webpack-sources "^1.0.1" +"@theia/console@0.16.0-next.46a2d510": + version "0.16.0-next.46a2d510" + resolved "https://registry.yarnpkg.com/@theia/console/-/console-0.16.0-next.46a2d510.tgz#ba5926258cf58b4fbe64460239f4af38a98ff883" + integrity sha512-yJk6+H6xzNO+fmJuz7k47cMjZsA2b4wgEp9FVaHCkn3fmTjMo/Qv3PCH78S8bSMQcKmfqFDCACj/X96cTmMAfg== + dependencies: + "@theia/core" "0.16.0-next.46a2d510" + "@theia/monaco" "0.16.0-next.46a2d510" + anser "^1.4.7" + "@theia/core@0.16.0-next.46a2d510", "@theia/core@next": version "0.16.0-next.46a2d510" resolved "https://registry.yarnpkg.com/@theia/core/-/core-0.16.0-next.46a2d510.tgz#2ff298bf34721a2dc7a3ae1c261f0f01b2d6b0be" @@ -1942,6 +1951,36 @@ "@theia/workspace" next string-argv "^0.1.1" +"@theia/debug@next": + version "0.16.0-next.46a2d510" + resolved "https://registry.yarnpkg.com/@theia/debug/-/debug-0.16.0-next.46a2d510.tgz#702b7c9a44cab46f368fbafe2776c5ec703f97fb" + integrity sha512-iUvNV3UGFGBFEE0lpBlcLQoklr86Az2G36ORpVw9VPWTHkm21RKdmwY2M0rF1+JnaENz0iFJEVayD2rzNQGTdA== + dependencies: + "@theia/application-package" "0.16.0-next.46a2d510" + "@theia/console" "0.16.0-next.46a2d510" + "@theia/core" "0.16.0-next.46a2d510" + "@theia/editor" "0.16.0-next.46a2d510" + "@theia/filesystem" "0.16.0-next.46a2d510" + "@theia/languages" "0.16.0-next.46a2d510" + "@theia/markers" "0.16.0-next.46a2d510" + "@theia/monaco" "0.16.0-next.46a2d510" + "@theia/output" "0.16.0-next.46a2d510" + "@theia/preferences" "0.16.0-next.46a2d510" + "@theia/process" "0.16.0-next.46a2d510" + "@theia/task" "0.16.0-next.46a2d510" + "@theia/terminal" "0.16.0-next.46a2d510" + "@theia/userstorage" "0.16.0-next.46a2d510" + "@theia/variable-resolver" "0.16.0-next.46a2d510" + "@theia/workspace" "0.16.0-next.46a2d510" + "@types/p-debounce" "^1.0.1" + jsonc-parser "^2.0.2" + mkdirp "^0.5.0" + p-debounce "^2.1.0" + requestretry "^3.1.0" + tar "^4.0.0" + unzip-stream "^0.3.0" + vscode-debugprotocol "^1.32.0" + "@theia/editor@0.16.0-next.46a2d510", "@theia/editor@next": version "0.16.0-next.46a2d510" resolved "https://registry.yarnpkg.com/@theia/editor/-/editor-0.16.0-next.46a2d510.tgz#8c49de165b37d64f480626c07bced71572f86182" @@ -2169,7 +2208,7 @@ "@theia/workspace" "0.16.0-next.46a2d510" vscode-ripgrep "^1.2.4" -"@theia/task@next": +"@theia/task@0.16.0-next.46a2d510", "@theia/task@next": version "0.16.0-next.46a2d510" resolved "https://registry.yarnpkg.com/@theia/task/-/task-0.16.0-next.46a2d510.tgz#004ca4f4256351c63602332798dd6d98e3c387cf" integrity sha512-390Meof3StbHNBNfQY8f/opNBj/IZN8z/ZjhRNf/0vtu1b6aZGMo1z/9e8eLcCwDyAdFvSCHRk1bny+rhB4//w== @@ -2863,6 +2902,11 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= +anser@^1.4.7: + version "1.4.9" + resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760" + integrity sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA== + ansi-bgblack@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ansi-bgblack/-/ansi-bgblack-0.1.1.tgz#a68ba5007887701b6aafbe3fa0dadfdfa8ee3ca2" @@ -4666,6 +4710,15 @@ caw@^2.0.1: tunnel-agent "^0.6.0" url-to-options "^1.0.1" +cdt-gdb-adapter@^0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/cdt-gdb-adapter/-/cdt-gdb-adapter-0.0.14.tgz#0b4cc8650699572003e52a9f8265e9c65055339c" + integrity sha512-SDRVECyEkgePj0AJNVp8HUOw/ObXSlakQNxMasC1wlCn6eE8HcLAdYqdIgwRtnkp2Ct96/U5mXHFHWQDan6ckw== + dependencies: + node-addon-api "^1.6.2" + vscode-debugadapter "^1.37.1" + vscode-debugprotocol "^1.37.0" + chai-string@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/chai-string/-/chai-string-1.5.0.tgz#0bdb2d8a5f1dbe90bc78ec493c1c1c180dd4d3d2" @@ -9719,6 +9772,11 @@ node-abi@^2.11.0, node-abi@^2.2.0: dependencies: semver "^5.4.1" +node-addon-api@^1.6.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" + integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== + node-dir@0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.8.tgz#55fb8deb699070707fb67f91a460f0448294c77d" @@ -9827,22 +9885,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-pre-gyp@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" @@ -12850,7 +12892,7 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" -tar@^4, tar@^4.0.0, tar@^4.0.2, tar@^4.4.10, tar@^4.4.12, tar@^4.4.2, tar@^4.4.8: +tar@^4, tar@^4.0.0, tar@^4.0.2, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== @@ -13227,6 +13269,11 @@ typeof-article@^0.1.1: dependencies: kind-of "^3.1.0" +typescript@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" + integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== + typescript@3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" @@ -13577,6 +13624,19 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +vscode-debugadapter@^1.26.0, vscode-debugadapter@^1.37.1: + version "1.38.0" + resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.38.0.tgz#764a2cef6634cf17c35e1ca97fa1faa253ac87c1" + integrity sha512-rm4qmbqj8aAaE8sUt4hX2HZUi7Nmtmf10fiGPqbLZWSFPrBi6myxhrQ0HPeG6Xep5rEgrGzVwCJ/lSGPz2ja1A== + dependencies: + mkdirp "^0.5.1" + vscode-debugprotocol "1.38.0" + +vscode-debugprotocol@1.38.0, vscode-debugprotocol@^1.26.0, vscode-debugprotocol@^1.32.0, vscode-debugprotocol@^1.37.0: + version "1.38.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.38.0.tgz#7a9bcd457e6642f48fabef114c0fa1c25a2fb1e7" + integrity sha512-oam9iSjNfXSn71a8bmNsXv8k/rIKSOcllIPrFnNgxd1EMBpfnum+gb7lmRpcH0zSjGb+OH8Ncn8B5tv8srWbNQ== + vscode-jsonrpc@^4.1.0-next: version "4.1.0-next.3" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.1.0-next.3.tgz#05fe742959a2726020d4d0bfbc3d3c97873c7fde" From bd6a6382f6729aa0d646f50250a4c366672e311e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 3 Feb 2020 10:16:08 +0100 Subject: [PATCH 29/44] Removed C/C++ config with hard-coded paths --- .vscode/c_cpp_properties.json | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 .vscode/c_cpp_properties.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 3bf59290..00000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "configurations": [ - { - "name": "Mac", - "includePath": [ - "/Users/csweichel/Library/Arduino15/packages/arduino/tools/**", - "/Users/csweichel/Library/Arduino15/packages/arduino/hardware/samd/1.8.4/**" - ], - "forcedInclude": [ - "/Users/csweichel/Library/Arduino15/packages/arduino/hardware/samd/1.8.4/cores/arduino/Arduino.h" - ] - } - ] -} \ No newline at end of file From a427cf94f1e6251f8a98891036425f8b188fd47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 20 Feb 2020 11:39:59 +0100 Subject: [PATCH 30/44] Created new debug adapter that uses the arduino-cli debug command --- .../src/browser/arduino-variable-resolver.ts | 171 ++---------------- .../arduino-debug-adapter-contribution.ts | 111 ++++-------- .../debug-adapter/arduino-debug-session.ts | 19 ++ .../node/debug-adapter/arduino-gdb-backend.ts | 37 ++++ .../src/node/debug-adapter/main.ts | 30 +-- 5 files changed, 103 insertions(+), 265 deletions(-) create mode 100644 arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts create mode 100644 arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts 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); From 486f110c8048ec1274161ffa4ef7542d6782e58a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Thu, 20 Feb 2020 16:53:50 +0100 Subject: [PATCH 31/44] Added initial commands --- .../arduino-debug-adapter-contribution.ts | 15 ++++++------ .../debug-adapter/arduino-debug-session.ts | 24 ++++++++++++++++++- .../node/debug-adapter/arduino-gdb-backend.ts | 12 ++++++---- 3 files changed, 38 insertions(+), 13 deletions(-) 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 6176a507..9f6ee487 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -23,16 +23,11 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution 'description': 'path to the sketch root ino file', 'default': '${file}', }, - 'runToMain': { - 'description': 'If enabled the debugger will run until the start of the main function.', + 'pauseAtMain': { + 'description': 'If enabled the debugger will pause at the start of the main function.', 'type': 'boolean', 'default': false }, - 'verbose': { - 'type': 'boolean', - 'description': 'Produce verbose log output', - 'default': false - }, 'debugDebugAdapter': { 'type': 'boolean', 'description': 'Start the debug adapter in debug mode (with --inspect-brk)', @@ -68,11 +63,15 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution } async resolveDebugConfiguration(config: DebugConfiguration): Promise { + const startFunction = config.pauseAtMain ? 'main' : 'setup'; const res: ActualDebugConfig = { ...config, arduinoCli: await this.arduinoCli.getExecPath(), fqbn: '${fqbn}', - uploadPort: '${port}' + uploadPort: '${port}', + initCommands: [ + `-break-insert -t --function ${startFunction}` + ] } if (!res.sketch) { res.sketch = '${file}'; 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 index c53246fa..7c305bdc 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -1,5 +1,6 @@ +import * as mi from 'cdt-gdb-adapter/dist/mi'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { GDBDebugSession } from 'cdt-gdb-adapter/dist/GDBDebugSession'; +import { GDBDebugSession, LaunchRequestArguments } from 'cdt-gdb-adapter/dist/GDBDebugSession'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; import { ArduinoGDBBackend } from './arduino-gdb-backend'; @@ -16,4 +17,25 @@ export class ArduinoDebugSession extends GDBDebugSession { return new ArduinoGDBBackend(); } + protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): Promise { + const additionalCommands = [ + '-interpreter-exec console "monitor reset halt"' + ]; + if (!args.initCommands) { + args.initCommands = additionalCommands; + } else { + args.initCommands.push(...additionalCommands); + } + return super.launchRequest(response, args); + } + + protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise { + try { + await mi.sendExecContinue(this.gdb); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 100, err.message); + } + } + } 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 index 800472ce..7af3c61a 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -1,13 +1,12 @@ 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) { + public spawn(requestArgs: ArduinoLaunchRequestArguments): Promise { if (!requestArgs.sketch) { throw new Error('Missing argument: sketch'); } @@ -29,8 +28,13 @@ export class ArduinoGDBBackend extends GDBBackend { return this.parser.parse(proc.stdout); } - public pause() { - mi.sendExecInterrupt(this); + public sendFileExecAndSymbols(): Promise { + // The program file is already sent by `arduino-cli` + return Promise.resolve(); + } + + public pause(): boolean { + this.sendCommand('-exec-interrupt'); return true; } From 0445700088aa21a076303abcd88ead41676e566a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 10:33:39 +0100 Subject: [PATCH 32/44] Added 'optimize for debug' option --- .../debug-adapter/arduino-debug-session.ts | 24 + .../node/debug-adapter/arduino-gdb-backend.ts | 10 +- .../src/browser/arduino-commands.ts | 4 + .../browser/arduino-frontend-contribution.tsx | 27 +- .../src/browser/editor-mode.ts | 14 +- .../src/common/protocol/boards-service.ts | 10 - .../src/common/protocol/core-service.ts | 4 +- .../src/node/boards-service-impl.ts | 50 +- .../cli-protocol/commands/commands_grpc_pb.js | 30 +- .../cli-protocol/commands/commands_pb.d.ts | 42 - .../node/cli-protocol/commands/commands_pb.js | 289 ------ .../cli-protocol/commands/compile_pb.d.ts | 10 + .../node/cli-protocol/commands/compile_pb.js | 74 +- .../cli-protocol/debug/debug_grpc_pb.d.ts | 40 + .../node/cli-protocol/debug/debug_grpc_pb.js | 61 ++ .../src/node/cli-protocol/debug/debug_pb.d.ts | 129 +++ .../src/node/cli-protocol/debug/debug_pb.js | 859 ++++++++++++++++++ .../cli-protocol/monitor/monitor_grpc_pb.js | 2 +- .../settings/settings_grpc_pb.d.ts | 92 ++ .../cli-protocol/settings/settings_grpc_pb.js | 137 +++ .../cli-protocol/settings/settings_pb.d.ts | 125 +++ .../node/cli-protocol/settings/settings_pb.js | 821 +++++++++++++++++ .../src/node/core-client-provider-impl.ts | 8 - .../src/node/core-service-impl.ts | 3 +- 24 files changed, 2441 insertions(+), 424 deletions(-) create mode 100644 arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.d.ts create mode 100644 arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.js create mode 100644 arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.d.ts create mode 100644 arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.js create mode 100644 arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.d.ts create mode 100644 arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.js create mode 100644 arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.d.ts create mode 100644 arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.js 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 index 7c305bdc..670a9e93 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -13,6 +13,10 @@ export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchReque export class ArduinoDebugSession extends GDBDebugSession { + protected get arduinoBackend(): ArduinoGDBBackend { + return this.gdb as ArduinoGDBBackend; + } + protected createBackend(): GDBBackend { return new ArduinoGDBBackend(); } @@ -38,4 +42,24 @@ export class ArduinoDebugSession extends GDBDebugSession { } } + protected async disconnectRequest(response: DebugProtocol.DisconnectResponse): Promise { + try { + if (this.isRunning) { + // Need to pause first + const waitPromise = new Promise(resolve => this.waitPaused = resolve); + this.gdb.pause(); + await waitPromise; + } + try { + await this.arduinoBackend.sendTargetDetach(); + } catch (e) { + // Need to catch here as the command result being returned will never exist as it's detached + } + await this.gdb.sendGDBExit(); + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + } 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 index 7af3c61a..2630eeb4 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -6,7 +6,7 @@ import { ArduinoLaunchRequestArguments } from './arduino-debug-session'; export class ArduinoGDBBackend extends GDBBackend { - public spawn(requestArgs: ArduinoLaunchRequestArguments): Promise { + spawn(requestArgs: ArduinoLaunchRequestArguments): Promise { if (!requestArgs.sketch) { throw new Error('Missing argument: sketch'); } @@ -28,14 +28,18 @@ export class ArduinoGDBBackend extends GDBBackend { return this.parser.parse(proc.stdout); } - public sendFileExecAndSymbols(): Promise { + sendFileExecAndSymbols(): Promise { // The program file is already sent by `arduino-cli` return Promise.resolve(); } - public pause(): boolean { + pause(): boolean { this.sendCommand('-exec-interrupt'); return true; } + sendTargetDetach(): Promise { + return this.sendCommand('-target-detach'); + } + } diff --git a/arduino-ide-extension/src/browser/arduino-commands.ts b/arduino-ide-extension/src/browser/arduino-commands.ts index d0b3dcb7..34026f34 100644 --- a/arduino-ide-extension/src/browser/arduino-commands.ts +++ b/arduino-ide-extension/src/browser/arduino-commands.ts @@ -12,6 +12,10 @@ export namespace ArduinoCommands { label: 'Upload Sketch' } + export const TOGGLE_COMPILE_FOR_DEBUG: Command = { + id: "arduino-toggle-compile-for-debug" + } + export const SHOW_OPEN_CONTEXT_MENU: Command = { id: 'arduino-show-open-context-menu', label: 'Open Sketch' diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 2bafe514..c89e2b96 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -289,13 +289,22 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut } // Reveal the Output view asynchronously (don't await it) this.outputContribution.openView({ reveal: true }); - await this.coreService.compile({ uri: uri.toString(), board: boardsConfig.selectedBoard }); + await this.coreService.compile({ + uri: uri.toString(), + board: boardsConfig.selectedBoard, + optimizeForDebug: this.editorMode.compileForDebug + }); } catch (e) { await this.messageService.error(e.toString()); } } }); + registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, { + execute: () => this.editorMode.toggleCompileForDebug(), + isToggled: () => this.editorMode.compileForDebug + }); + registry.registerCommand(ArduinoCommands.UPLOAD, { isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', isEnabled: widget => true, @@ -326,7 +335,12 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut } // Reveal the Output view asynchronously (don't await it) this.outputContribution.openView({ reveal: true }); - await this.coreService.upload({ uri: uri.toString(), board: boardsConfig.selectedBoard, port: selectedPort.address }); + await this.coreService.upload({ + uri: uri.toString(), + board: boardsConfig.selectedBoard, + port: selectedPort.address, + optimizeForDebug: this.editorMode.compileForDebug + }); } catch (e) { await this.messageService.error(e.toString()); } finally { @@ -402,7 +416,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, { - execute: () => this.editorMode.toggle(), + execute: () => this.editorMode.toggleProMode(), isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right', isToggled: () => this.editorMode.proMode }); @@ -445,10 +459,15 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut label: 'Verify/Compile', order: '1' }); + registry.registerMenuAction(ArduinoMenus.SKETCH, { + commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, + label: 'Optimize for Debug', + order: '2' + }); registry.registerMenuAction(ArduinoMenus.SKETCH, { commandId: ArduinoCommands.UPLOAD.id, label: 'Upload', - order: '2' + order: '3' }); registry.registerMenuAction(ArduinoToolbarContextMenu.OPEN_GROUP, { commandId: ArduinoCommands.OPEN_FILE_NAVIGATOR.id, diff --git a/arduino-ide-extension/src/browser/editor-mode.ts b/arduino-ide-extension/src/browser/editor-mode.ts index 872b4648..857ee75b 100644 --- a/arduino-ide-extension/src/browser/editor-mode.ts +++ b/arduino-ide-extension/src/browser/editor-mode.ts @@ -22,7 +22,7 @@ export class EditorMode implements FrontendApplicationContribution { return value === 'true'; } - async toggle(): Promise { + async toggleProMode(): Promise { const oldState = this.proMode; const inAdvancedMode = !oldState; window.localStorage.setItem(EditorMode.PRO_MODE_KEY, String(inAdvancedMode)); @@ -41,8 +41,20 @@ export class EditorMode implements FrontendApplicationContribution { window.location.reload(true); } + get compileForDebug(): boolean { + const value = window.localStorage.getItem(EditorMode.COMPILE_FOR_DEBUG_KEY); + return value === 'true'; + } + + async toggleCompileForDebug(): Promise { + const oldState = this.compileForDebug; + const newState = !oldState; + window.localStorage.setItem(EditorMode.COMPILE_FOR_DEBUG_KEY, String(newState)); + } + } export namespace EditorMode { export const PRO_MODE_KEY = 'arduino-advanced-mode'; + export const COMPILE_FOR_DEBUG_KEY = 'arduino-compile-for-debug'; } diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index cb25650e..d7f0b631 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -186,22 +186,12 @@ export interface BoardDetails extends Board { fqbn: string; requiredTools: Tool[]; - locations?: BoardDetailLocations; -} - -export interface BoardDetailLocations { - debugScript: string; } export interface Tool { readonly packager: string; readonly name: string; readonly version: string; - readonly locations?: ToolLocations; -} -export interface ToolLocations { - main: string - [key: string]: string } export namespace Board { diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts index a6318b55..9b6b2b90 100644 --- a/arduino-ide-extension/src/common/protocol/core-service.ts +++ b/arduino-ide-extension/src/common/protocol/core-service.ts @@ -14,6 +14,7 @@ export namespace CoreService { readonly uri: string; readonly board: Board; readonly port: string; + readonly optimizeForDebug: boolean; } } @@ -21,6 +22,7 @@ export namespace CoreService { export interface Options { readonly uri: string; readonly board: Board; + readonly optimizeForDebug: boolean; } } -} \ No newline at end of file +} diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index fd2bfbf2..8fcc8260 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -5,18 +5,17 @@ import { Deferred } from '@theia/core/lib/common/promise-util'; import { FileSystem } from '@theia/filesystem/lib/common'; import { BoardsService, AttachedSerialBoard, BoardPackage, Board, AttachedNetworkBoard, BoardsServiceClient, - Port, BoardDetails, Tool, ToolLocations, BoardDetailLocations + Port, BoardDetails, Tool } from '../common/protocol/boards-service'; import { PlatformSearchReq, PlatformSearchResp, PlatformInstallReq, PlatformInstallResp, PlatformListReq, PlatformListResp, Platform, PlatformUninstallResp, PlatformUninstallReq } from './cli-protocol/commands/core_pb'; import { CoreClientProvider } from './core-client-provider'; -import { BoardListReq, BoardListResp, BoardDetailsReq, BoardDetailsResp, RequiredTool } from './cli-protocol/commands/board_pb'; +import { BoardListReq, BoardListResp, BoardDetailsReq, BoardDetailsResp } from './cli-protocol/commands/board_pb'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; import { Installable } from '../common/protocol/installable'; import { ConfigService } from '../common/protocol/config-service'; -import * as path from 'path'; @injectable() export class BoardsServiceImpl implements BoardsService { @@ -237,59 +236,18 @@ export class BoardsServiceImpl implements BoardsService { const tools = await Promise.all(resp.getRequiredToolsList().map(async t => { name: t.getName(), packager: t.getPackager(), - version: t.getVersion(), - locations: await this.getToolLocations(t), + version: t.getVersion() })); return { item: { name: resp.getName(), fqbn: options.id, - requiredTools: tools, - locations: await this.getBoardLocations(resp) + requiredTools: tools } }; } - // TODO: these location should come from the CLI/daemon rather than us botching them together - protected async getBoardLocations(details: BoardDetailsResp): Promise { - const config = await this.configService.getConfiguration(); - const datadir = await this.fileSystem.getFsPath(config.dataDirUri); - if (!datadir) { - return undefined; - } - - return { - debugScript: path.join(datadir, "packages", "arduino", "hardware", "samd", "1.8.4", "variants", "arduino_zero", "openocd_scripts", "arduino_zero.cfg") - } - } - - // TODO: these location should come from the CLI/daemon rather than us botching them together - protected async getToolLocations(t: RequiredTool): Promise { - const config = await this.configService.getConfiguration(); - const datadir = await this.fileSystem.getFsPath(config.dataDirUri); - if (!datadir) { - return undefined; - } - - const toolBasePath = path.join(datadir, "packages", "arduino", "tools"); - let loc: ToolLocations = { - main: path.join(toolBasePath, t.getName(), t.getVersion()) - }; - - switch (t.getName()) { - case "openocd": - loc.scripts = path.join(loc.main, "share", "openocd", "scripts"); - loc.main = path.join(loc.main, "bin", "openocd"); - break; - case "arm-none-eabi-gcc": - ["gdb", "objdump"].forEach(s => loc[s] = path.join(loc.main, "bin", `arm-none-eabi-${s}`)); - break; - } - - return loc; - } - async search(options: { query?: string }): Promise<{ items: BoardPackage[] }> { const coreClient = await this.coreClientProvider.getClient(); if (!coreClient) { diff --git a/arduino-ide-extension/src/node/cli-protocol/commands/commands_grpc_pb.js b/arduino-ide-extension/src/node/cli-protocol/commands/commands_grpc_pb.js index 71be95be..615964a5 100644 --- a/arduino-ide-extension/src/node/cli-protocol/commands/commands_grpc_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/commands/commands_grpc_pb.js @@ -1,10 +1,9 @@ // GENERATED CODE -- DO NOT EDIT! // Original file comments: -// // This file is part of arduino-cli. // -// Copyright 2018 ARDUINO SA (http://www.arduino.cc/) +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, // which covers the main part of arduino-cli. @@ -12,11 +11,10 @@ // https://www.gnu.org/licenses/gpl-3.0.en.html // // You can be released from the requirements of the above licenses by purchasing -// a commercial license. Buying such a license is mandatory if you want to modify or -// otherwise use the software for commercial activities involving the Arduino -// software without disclosing the source code of your own applications. To purchase -// a commercial license, send an email to license@arduino.cc. -// +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. // 'use strict'; var grpc = require('@grpc/grpc-js'); @@ -582,7 +580,7 @@ function deserialize_cc_arduino_cli_commands_VersionResp(buffer_arg) { // The main Arduino Platform Service var ArduinoCoreService = exports.ArduinoCoreService = { // Start a new instance of the Arduino Core Service - init: { +init: { path: '/cc.arduino.cli.commands.ArduinoCore/Init', requestStream: false, responseStream: true, @@ -594,7 +592,7 @@ var ArduinoCoreService = exports.ArduinoCoreService = { responseDeserialize: deserialize_cc_arduino_cli_commands_InitResp, }, // Destroy an instance of the Arduino Core Service - destroy: { +destroy: { path: '/cc.arduino.cli.commands.ArduinoCore/Destroy', requestStream: false, responseStream: false, @@ -606,7 +604,7 @@ var ArduinoCoreService = exports.ArduinoCoreService = { responseDeserialize: deserialize_cc_arduino_cli_commands_DestroyResp, }, // Rescan instance of the Arduino Core Service - rescan: { +rescan: { path: '/cc.arduino.cli.commands.ArduinoCore/Rescan', requestStream: false, responseStream: false, @@ -618,7 +616,7 @@ var ArduinoCoreService = exports.ArduinoCoreService = { responseDeserialize: deserialize_cc_arduino_cli_commands_RescanResp, }, // Update package index of the Arduino Core Service - updateIndex: { +updateIndex: { path: '/cc.arduino.cli.commands.ArduinoCore/UpdateIndex', requestStream: false, responseStream: true, @@ -630,7 +628,7 @@ var ArduinoCoreService = exports.ArduinoCoreService = { responseDeserialize: deserialize_cc_arduino_cli_commands_UpdateIndexResp, }, // Update libraries index - updateLibrariesIndex: { +updateLibrariesIndex: { path: '/cc.arduino.cli.commands.ArduinoCore/UpdateLibrariesIndex', requestStream: false, responseStream: true, @@ -653,10 +651,10 @@ var ArduinoCoreService = exports.ArduinoCoreService = { responseDeserialize: deserialize_cc_arduino_cli_commands_VersionResp, }, // BOARD COMMANDS - // -------------- - // - // Requests details about a board - boardDetails: { +// -------------- +// +// Requests details about a board +boardDetails: { path: '/cc.arduino.cli.commands.ArduinoCore/BoardDetails', requestStream: false, responseStream: false, diff --git a/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.d.ts index f012e4c6..c414a765 100644 --- a/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.d.ts @@ -12,48 +12,7 @@ import * as commands_core_pb from "../commands/core_pb"; import * as commands_upload_pb from "../commands/upload_pb"; import * as commands_lib_pb from "../commands/lib_pb"; -export class Configuration extends jspb.Message { - getDatadir(): string; - setDatadir(value: string): void; - - getSketchbookdir(): string; - setSketchbookdir(value: string): void; - - getDownloadsdir(): string; - setDownloadsdir(value: string): void; - - clearBoardmanageradditionalurlsList(): void; - getBoardmanageradditionalurlsList(): Array; - setBoardmanageradditionalurlsList(value: Array): void; - addBoardmanageradditionalurls(value: string, index?: number): string; - - - serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): Configuration.AsObject; - static toObject(includeInstance: boolean, msg: Configuration): Configuration.AsObject; - static extensions: {[key: number]: jspb.ExtensionFieldInfo}; - static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; - static serializeBinaryToWriter(message: Configuration, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): Configuration; - static deserializeBinaryFromReader(message: Configuration, reader: jspb.BinaryReader): Configuration; -} - -export namespace Configuration { - export type AsObject = { - datadir: string, - sketchbookdir: string, - downloadsdir: string, - boardmanageradditionalurlsList: Array, - } -} - export class InitReq extends jspb.Message { - - hasConfiguration(): boolean; - clearConfiguration(): void; - getConfiguration(): Configuration | undefined; - setConfiguration(value?: Configuration): void; - getLibraryManagerOnly(): boolean; setLibraryManagerOnly(value: boolean): void; @@ -70,7 +29,6 @@ export class InitReq extends jspb.Message { export namespace InitReq { export type AsObject = { - configuration?: Configuration.AsObject, libraryManagerOnly: boolean, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.js b/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.js index 1fc0068b..16849da5 100644 --- a/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/commands/commands_pb.js @@ -23,7 +23,6 @@ var commands_upload_pb = require('../commands/upload_pb.js'); goog.object.extend(proto, commands_upload_pb); var commands_lib_pb = require('../commands/lib_pb.js'); goog.object.extend(proto, commands_lib_pb); -goog.exportSymbol('proto.cc.arduino.cli.commands.Configuration', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.DestroyReq', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.DestroyResp', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.InitReq', null, global); @@ -37,250 +36,6 @@ goog.exportSymbol('proto.cc.arduino.cli.commands.UpdateLibrariesIndexResp', null goog.exportSymbol('proto.cc.arduino.cli.commands.VersionReq', null, global); goog.exportSymbol('proto.cc.arduino.cli.commands.VersionResp', null, global); -/** - * Generated by JsPbCodeGenerator. - * @param {Array=} opt_data Optional initial data array, typically from a - * server response, or constructed directly in Javascript. The array is used - * in place and becomes part of the constructed object. It is not cloned. - * If no data is provided, the constructed object will be empty, but still - * valid. - * @extends {jspb.Message} - * @constructor - */ -proto.cc.arduino.cli.commands.Configuration = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, proto.cc.arduino.cli.commands.Configuration.repeatedFields_, null); -}; -goog.inherits(proto.cc.arduino.cli.commands.Configuration, jspb.Message); -if (goog.DEBUG && !COMPILED) { - proto.cc.arduino.cli.commands.Configuration.displayName = 'proto.cc.arduino.cli.commands.Configuration'; -} -/** - * List of repeated fields within this message type. - * @private {!Array} - * @const - */ -proto.cc.arduino.cli.commands.Configuration.repeatedFields_ = [4]; - - - -if (jspb.Message.GENERATE_TO_OBJECT) { -/** - * Creates an object representation of this proto suitable for use in Soy templates. - * Field names that are reserved in JavaScript and will be renamed to pb_name. - * To access a reserved field use, foo.pb_, eg, foo.pb_default. - * For the list of reserved names please see: - * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. - * @param {boolean=} opt_includeInstance Whether to include the JSPB instance - * for transitional soy proto support: http://goto/soy-param-migration - * @return {!Object} - */ -proto.cc.arduino.cli.commands.Configuration.prototype.toObject = function(opt_includeInstance) { - return proto.cc.arduino.cli.commands.Configuration.toObject(opt_includeInstance, this); -}; - - -/** - * Static version of the {@see toObject} method. - * @param {boolean|undefined} includeInstance Whether to include the JSPB - * instance for transitional soy proto support: - * http://goto/soy-param-migration - * @param {!proto.cc.arduino.cli.commands.Configuration} msg The msg instance to transform. - * @return {!Object} - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.cc.arduino.cli.commands.Configuration.toObject = function(includeInstance, msg) { - var f, obj = { - datadir: jspb.Message.getFieldWithDefault(msg, 1, ""), - sketchbookdir: jspb.Message.getFieldWithDefault(msg, 2, ""), - downloadsdir: jspb.Message.getFieldWithDefault(msg, 3, ""), - boardmanageradditionalurlsList: jspb.Message.getRepeatedField(msg, 4) - }; - - if (includeInstance) { - obj.$jspbMessageInstance = msg; - } - return obj; -}; -} - - -/** - * Deserializes binary data (in protobuf wire format). - * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.cc.arduino.cli.commands.Configuration} - */ -proto.cc.arduino.cli.commands.Configuration.deserializeBinary = function(bytes) { - var reader = new jspb.BinaryReader(bytes); - var msg = new proto.cc.arduino.cli.commands.Configuration; - return proto.cc.arduino.cli.commands.Configuration.deserializeBinaryFromReader(msg, reader); -}; - - -/** - * Deserializes binary data (in protobuf wire format) from the - * given reader into the given message object. - * @param {!proto.cc.arduino.cli.commands.Configuration} msg The message object to deserialize into. - * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.cc.arduino.cli.commands.Configuration} - */ -proto.cc.arduino.cli.commands.Configuration.deserializeBinaryFromReader = function(msg, reader) { - while (reader.nextField()) { - if (reader.isEndGroup()) { - break; - } - var field = reader.getFieldNumber(); - switch (field) { - case 1: - var value = /** @type {string} */ (reader.readString()); - msg.setDatadir(value); - break; - case 2: - var value = /** @type {string} */ (reader.readString()); - msg.setSketchbookdir(value); - break; - case 3: - var value = /** @type {string} */ (reader.readString()); - msg.setDownloadsdir(value); - break; - case 4: - var value = /** @type {string} */ (reader.readString()); - msg.addBoardmanageradditionalurls(value); - break; - default: - reader.skipField(); - break; - } - } - return msg; -}; - - -/** - * Serializes the message to binary data (in protobuf wire format). - * @return {!Uint8Array} - */ -proto.cc.arduino.cli.commands.Configuration.prototype.serializeBinary = function() { - var writer = new jspb.BinaryWriter(); - proto.cc.arduino.cli.commands.Configuration.serializeBinaryToWriter(this, writer); - return writer.getResultBuffer(); -}; - - -/** - * Serializes the given message to binary data (in protobuf wire - * format), writing to the given BinaryWriter. - * @param {!proto.cc.arduino.cli.commands.Configuration} message - * @param {!jspb.BinaryWriter} writer - * @suppress {unusedLocalVariables} f is only used for nested messages - */ -proto.cc.arduino.cli.commands.Configuration.serializeBinaryToWriter = function(message, writer) { - var f = undefined; - f = message.getDatadir(); - if (f.length > 0) { - writer.writeString( - 1, - f - ); - } - f = message.getSketchbookdir(); - if (f.length > 0) { - writer.writeString( - 2, - f - ); - } - f = message.getDownloadsdir(); - if (f.length > 0) { - writer.writeString( - 3, - f - ); - } - f = message.getBoardmanageradditionalurlsList(); - if (f.length > 0) { - writer.writeRepeatedString( - 4, - f - ); - } -}; - - -/** - * optional string dataDir = 1; - * @return {string} - */ -proto.cc.arduino.cli.commands.Configuration.prototype.getDatadir = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); -}; - - -/** @param {string} value */ -proto.cc.arduino.cli.commands.Configuration.prototype.setDatadir = function(value) { - jspb.Message.setProto3StringField(this, 1, value); -}; - - -/** - * optional string sketchbookDir = 2; - * @return {string} - */ -proto.cc.arduino.cli.commands.Configuration.prototype.getSketchbookdir = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); -}; - - -/** @param {string} value */ -proto.cc.arduino.cli.commands.Configuration.prototype.setSketchbookdir = function(value) { - jspb.Message.setProto3StringField(this, 2, value); -}; - - -/** - * optional string downloadsDir = 3; - * @return {string} - */ -proto.cc.arduino.cli.commands.Configuration.prototype.getDownloadsdir = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); -}; - - -/** @param {string} value */ -proto.cc.arduino.cli.commands.Configuration.prototype.setDownloadsdir = function(value) { - jspb.Message.setProto3StringField(this, 3, value); -}; - - -/** - * repeated string boardManagerAdditionalUrls = 4; - * @return {!Array} - */ -proto.cc.arduino.cli.commands.Configuration.prototype.getBoardmanageradditionalurlsList = function() { - return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 4)); -}; - - -/** @param {!Array} value */ -proto.cc.arduino.cli.commands.Configuration.prototype.setBoardmanageradditionalurlsList = function(value) { - jspb.Message.setField(this, 4, value || []); -}; - - -/** - * @param {string} value - * @param {number=} opt_index - */ -proto.cc.arduino.cli.commands.Configuration.prototype.addBoardmanageradditionalurls = function(value, opt_index) { - jspb.Message.addToRepeatedField(this, 4, value, opt_index); -}; - - -proto.cc.arduino.cli.commands.Configuration.prototype.clearBoardmanageradditionalurlsList = function() { - this.setBoardmanageradditionalurlsList([]); -}; - - - /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -327,7 +82,6 @@ proto.cc.arduino.cli.commands.InitReq.prototype.toObject = function(opt_includeI */ proto.cc.arduino.cli.commands.InitReq.toObject = function(includeInstance, msg) { var f, obj = { - configuration: (f = msg.getConfiguration()) && proto.cc.arduino.cli.commands.Configuration.toObject(includeInstance, f), libraryManagerOnly: jspb.Message.getFieldWithDefault(msg, 2, false) }; @@ -365,11 +119,6 @@ proto.cc.arduino.cli.commands.InitReq.deserializeBinaryFromReader = function(msg } var field = reader.getFieldNumber(); switch (field) { - case 1: - var value = new proto.cc.arduino.cli.commands.Configuration; - reader.readMessage(value,proto.cc.arduino.cli.commands.Configuration.deserializeBinaryFromReader); - msg.setConfiguration(value); - break; case 2: var value = /** @type {boolean} */ (reader.readBool()); msg.setLibraryManagerOnly(value); @@ -403,14 +152,6 @@ proto.cc.arduino.cli.commands.InitReq.prototype.serializeBinary = function() { */ proto.cc.arduino.cli.commands.InitReq.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getConfiguration(); - if (f != null) { - writer.writeMessage( - 1, - f, - proto.cc.arduino.cli.commands.Configuration.serializeBinaryToWriter - ); - } f = message.getLibraryManagerOnly(); if (f) { writer.writeBool( @@ -421,36 +162,6 @@ proto.cc.arduino.cli.commands.InitReq.serializeBinaryToWriter = function(message }; -/** - * optional Configuration configuration = 1; - * @return {?proto.cc.arduino.cli.commands.Configuration} - */ -proto.cc.arduino.cli.commands.InitReq.prototype.getConfiguration = function() { - return /** @type{?proto.cc.arduino.cli.commands.Configuration} */ ( - jspb.Message.getWrapperField(this, proto.cc.arduino.cli.commands.Configuration, 1)); -}; - - -/** @param {?proto.cc.arduino.cli.commands.Configuration|undefined} value */ -proto.cc.arduino.cli.commands.InitReq.prototype.setConfiguration = function(value) { - jspb.Message.setWrapperField(this, 1, value); -}; - - -proto.cc.arduino.cli.commands.InitReq.prototype.clearConfiguration = function() { - this.setConfiguration(undefined); -}; - - -/** - * Returns whether this field is set. - * @return {boolean} - */ -proto.cc.arduino.cli.commands.InitReq.prototype.hasConfiguration = function() { - return jspb.Message.getField(this, 1) != null; -}; - - /** * optional bool library_manager_only = 2; * Note that Boolean fields may be set to 0/1 when serialized from a Java server. diff --git a/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.d.ts index 3045037d..6c1c79d5 100644 --- a/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.d.ts +++ b/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.d.ts @@ -55,6 +55,14 @@ export class CompileReq extends jspb.Message { getJobs(): number; setJobs(value: number): void; + clearLibrariesList(): void; + getLibrariesList(): Array; + setLibrariesList(value: Array): void; + addLibraries(value: string, index?: number): string; + + getOptimizefordebug(): boolean; + setOptimizefordebug(value: boolean): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): CompileReq.AsObject; @@ -82,6 +90,8 @@ export namespace CompileReq { vidpid: string, exportfile: string, jobs: number, + librariesList: Array, + optimizefordebug: boolean, } } diff --git a/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.js b/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.js index 1b3f213f..6563fd7d 100644 --- a/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/commands/compile_pb.js @@ -38,7 +38,7 @@ if (goog.DEBUG && !COMPILED) { * @private {!Array} * @const */ -proto.cc.arduino.cli.commands.CompileReq.repeatedFields_ = [8]; +proto.cc.arduino.cli.commands.CompileReq.repeatedFields_ = [8,15]; @@ -82,7 +82,9 @@ proto.cc.arduino.cli.commands.CompileReq.toObject = function(includeInstance, ms quiet: jspb.Message.getFieldWithDefault(msg, 11, false), vidpid: jspb.Message.getFieldWithDefault(msg, 12, ""), exportfile: jspb.Message.getFieldWithDefault(msg, 13, ""), - jobs: jspb.Message.getFieldWithDefault(msg, 14, 0) + jobs: jspb.Message.getFieldWithDefault(msg, 14, 0), + librariesList: jspb.Message.getRepeatedField(msg, 15), + optimizefordebug: jspb.Message.getFieldWithDefault(msg, 16, false) }; if (includeInstance) { @@ -176,6 +178,14 @@ proto.cc.arduino.cli.commands.CompileReq.deserializeBinaryFromReader = function( var value = /** @type {number} */ (reader.readInt32()); msg.setJobs(value); break; + case 15: + var value = /** @type {string} */ (reader.readString()); + msg.addLibraries(value); + break; + case 16: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setOptimizefordebug(value); + break; default: reader.skipField(); break; @@ -304,6 +314,20 @@ proto.cc.arduino.cli.commands.CompileReq.serializeBinaryToWriter = function(mess f ); } + f = message.getLibrariesList(); + if (f.length > 0) { + writer.writeRepeatedString( + 15, + f + ); + } + f = message.getOptimizefordebug(); + if (f) { + writer.writeBool( + 16, + f + ); + } }; @@ -554,6 +578,52 @@ proto.cc.arduino.cli.commands.CompileReq.prototype.setJobs = function(value) { }; +/** + * repeated string libraries = 15; + * @return {!Array} + */ +proto.cc.arduino.cli.commands.CompileReq.prototype.getLibrariesList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 15)); +}; + + +/** @param {!Array} value */ +proto.cc.arduino.cli.commands.CompileReq.prototype.setLibrariesList = function(value) { + jspb.Message.setField(this, 15, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + */ +proto.cc.arduino.cli.commands.CompileReq.prototype.addLibraries = function(value, opt_index) { + jspb.Message.addToRepeatedField(this, 15, value, opt_index); +}; + + +proto.cc.arduino.cli.commands.CompileReq.prototype.clearLibrariesList = function() { + this.setLibrariesList([]); +}; + + +/** + * optional bool optimizeForDebug = 16; + * Note that Boolean fields may be set to 0/1 when serialized from a Java server. + * You should avoid comparisons like {@code val === true/false} in those cases. + * @return {boolean} + */ +proto.cc.arduino.cli.commands.CompileReq.prototype.getOptimizefordebug = function() { + return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 16, false)); +}; + + +/** @param {boolean} value */ +proto.cc.arduino.cli.commands.CompileReq.prototype.setOptimizefordebug = function(value) { + jspb.Message.setProto3BooleanField(this, 16, value); +}; + + /** * Generated by JsPbCodeGenerator. diff --git a/arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.d.ts new file mode 100644 index 00000000..8df745f1 --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.d.ts @@ -0,0 +1,40 @@ +// package: cc.arduino.cli.debug +// file: debug/debug.proto + +/* tslint:disable */ +/* eslint-disable */ + +import * as grpc from "@grpc/grpc-js"; +import * as debug_debug_pb from "../debug/debug_pb"; + +interface IDebugService extends grpc.ServiceDefinition { + debug: IDebugService_IDebug; +} + +interface IDebugService_IDebug extends grpc.MethodDefinition { + path: string; // "/cc.arduino.cli.debug.Debug/Debug" + requestStream: boolean; // true + responseStream: boolean; // true + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} + +export const DebugService: IDebugService; + +export interface IDebugServer { + debug: grpc.handleBidiStreamingCall; +} + +export interface IDebugClient { + debug(): grpc.ClientDuplexStream; + debug(options: Partial): grpc.ClientDuplexStream; + debug(metadata: grpc.Metadata, options?: Partial): grpc.ClientDuplexStream; +} + +export class DebugClient extends grpc.Client implements IDebugClient { + constructor(address: string, credentials: grpc.ChannelCredentials, options?: object); + public debug(options?: Partial): grpc.ClientDuplexStream; + public debug(metadata?: grpc.Metadata, options?: Partial): grpc.ClientDuplexStream; +} diff --git a/arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.js b/arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.js new file mode 100644 index 00000000..9dd14a05 --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/debug/debug_grpc_pb.js @@ -0,0 +1,61 @@ +// GENERATED CODE -- DO NOT EDIT! + +// Original file comments: +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. +// +'use strict'; +var grpc = require('@grpc/grpc-js'); +var debug_debug_pb = require('../debug/debug_pb.js'); + +function serialize_cc_arduino_cli_debug_DebugReq(arg) { + if (!(arg instanceof debug_debug_pb.DebugReq)) { + throw new Error('Expected argument of type cc.arduino.cli.debug.DebugReq'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_debug_DebugReq(buffer_arg) { + return debug_debug_pb.DebugReq.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_debug_DebugResp(arg) { + if (!(arg instanceof debug_debug_pb.DebugResp)) { + throw new Error('Expected argument of type cc.arduino.cli.debug.DebugResp'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_debug_DebugResp(buffer_arg) { + return debug_debug_pb.DebugResp.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +// Service that abstract a debug Session usage +var DebugService = exports.DebugService = { + debug: { + path: '/cc.arduino.cli.debug.Debug/Debug', + requestStream: true, + responseStream: true, + requestType: debug_debug_pb.DebugReq, + responseType: debug_debug_pb.DebugResp, + requestSerialize: serialize_cc_arduino_cli_debug_DebugReq, + requestDeserialize: deserialize_cc_arduino_cli_debug_DebugReq, + responseSerialize: serialize_cc_arduino_cli_debug_DebugResp, + responseDeserialize: deserialize_cc_arduino_cli_debug_DebugResp, + }, +}; + +exports.DebugClient = grpc.makeGenericClientConstructor(DebugService); diff --git a/arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.d.ts new file mode 100644 index 00000000..98e4aaf1 --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.d.ts @@ -0,0 +1,129 @@ +// package: cc.arduino.cli.debug +// file: debug/debug.proto + +/* tslint:disable */ +/* eslint-disable */ + +import * as jspb from "google-protobuf"; + +export class DebugReq extends jspb.Message { + + hasDebugreq(): boolean; + clearDebugreq(): void; + getDebugreq(): DebugConfigReq | undefined; + setDebugreq(value?: DebugConfigReq): void; + + getData(): Uint8Array | string; + getData_asU8(): Uint8Array; + getData_asB64(): string; + setData(value: Uint8Array | string): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): DebugReq.AsObject; + static toObject(includeInstance: boolean, msg: DebugReq): DebugReq.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: DebugReq, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): DebugReq; + static deserializeBinaryFromReader(message: DebugReq, reader: jspb.BinaryReader): DebugReq; +} + +export namespace DebugReq { + export type AsObject = { + debugreq?: DebugConfigReq.AsObject, + data: Uint8Array | string, + } +} + +export class DebugConfigReq extends jspb.Message { + + hasInstance(): boolean; + clearInstance(): void; + getInstance(): Instance | undefined; + setInstance(value?: Instance): void; + + getFqbn(): string; + setFqbn(value: string): void; + + getSketchPath(): string; + setSketchPath(value: string): void; + + getPort(): string; + setPort(value: string): void; + + getVerbose(): boolean; + setVerbose(value: boolean): void; + + getImportFile(): string; + setImportFile(value: string): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): DebugConfigReq.AsObject; + static toObject(includeInstance: boolean, msg: DebugConfigReq): DebugConfigReq.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: DebugConfigReq, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): DebugConfigReq; + static deserializeBinaryFromReader(message: DebugConfigReq, reader: jspb.BinaryReader): DebugConfigReq; +} + +export namespace DebugConfigReq { + export type AsObject = { + instance?: Instance.AsObject, + fqbn: string, + sketchPath: string, + port: string, + verbose: boolean, + importFile: string, + } +} + +export class DebugResp extends jspb.Message { + getData(): Uint8Array | string; + getData_asU8(): Uint8Array; + getData_asB64(): string; + setData(value: Uint8Array | string): void; + + getError(): string; + setError(value: string): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): DebugResp.AsObject; + static toObject(includeInstance: boolean, msg: DebugResp): DebugResp.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: DebugResp, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): DebugResp; + static deserializeBinaryFromReader(message: DebugResp, reader: jspb.BinaryReader): DebugResp; +} + +export namespace DebugResp { + export type AsObject = { + data: Uint8Array | string, + error: string, + } +} + +export class Instance extends jspb.Message { + getId(): number; + setId(value: number): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): Instance.AsObject; + static toObject(includeInstance: boolean, msg: Instance): Instance.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: Instance, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): Instance; + static deserializeBinaryFromReader(message: Instance, reader: jspb.BinaryReader): Instance; +} + +export namespace Instance { + export type AsObject = { + id: number, + } +} diff --git a/arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.js b/arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.js new file mode 100644 index 00000000..c2b7d41d --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/debug/debug_pb.js @@ -0,0 +1,859 @@ +/** + * @fileoverview + * @enhanceable + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = Function('return this')(); + +goog.exportSymbol('proto.cc.arduino.cli.debug.DebugConfigReq', null, global); +goog.exportSymbol('proto.cc.arduino.cli.debug.DebugReq', null, global); +goog.exportSymbol('proto.cc.arduino.cli.debug.DebugResp', null, global); +goog.exportSymbol('proto.cc.arduino.cli.debug.Instance', null, global); + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.debug.DebugReq = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.debug.DebugReq, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.debug.DebugReq.displayName = 'proto.cc.arduino.cli.debug.DebugReq'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.debug.DebugReq.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.debug.DebugReq.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.debug.DebugReq} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.DebugReq.toObject = function(includeInstance, msg) { + var f, obj = { + debugreq: (f = msg.getDebugreq()) && proto.cc.arduino.cli.debug.DebugConfigReq.toObject(includeInstance, f), + data: msg.getData_asB64() + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.debug.DebugReq} + */ +proto.cc.arduino.cli.debug.DebugReq.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.debug.DebugReq; + return proto.cc.arduino.cli.debug.DebugReq.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.debug.DebugReq} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.debug.DebugReq} + */ +proto.cc.arduino.cli.debug.DebugReq.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.cc.arduino.cli.debug.DebugConfigReq; + reader.readMessage(value,proto.cc.arduino.cli.debug.DebugConfigReq.deserializeBinaryFromReader); + msg.setDebugreq(value); + break; + case 2: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setData(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.debug.DebugReq.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.debug.DebugReq.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.debug.DebugReq} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.DebugReq.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getDebugreq(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.cc.arduino.cli.debug.DebugConfigReq.serializeBinaryToWriter + ); + } + f = message.getData_asU8(); + if (f.length > 0) { + writer.writeBytes( + 2, + f + ); + } +}; + + +/** + * optional DebugConfigReq debugReq = 1; + * @return {?proto.cc.arduino.cli.debug.DebugConfigReq} + */ +proto.cc.arduino.cli.debug.DebugReq.prototype.getDebugreq = function() { + return /** @type{?proto.cc.arduino.cli.debug.DebugConfigReq} */ ( + jspb.Message.getWrapperField(this, proto.cc.arduino.cli.debug.DebugConfigReq, 1)); +}; + + +/** @param {?proto.cc.arduino.cli.debug.DebugConfigReq|undefined} value */ +proto.cc.arduino.cli.debug.DebugReq.prototype.setDebugreq = function(value) { + jspb.Message.setWrapperField(this, 1, value); +}; + + +proto.cc.arduino.cli.debug.DebugReq.prototype.clearDebugreq = function() { + this.setDebugreq(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.debug.DebugReq.prototype.hasDebugreq = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional bytes data = 2; + * @return {!(string|Uint8Array)} + */ +proto.cc.arduino.cli.debug.DebugReq.prototype.getData = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * optional bytes data = 2; + * This is a type-conversion wrapper around `getData()` + * @return {string} + */ +proto.cc.arduino.cli.debug.DebugReq.prototype.getData_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getData())); +}; + + +/** + * optional bytes data = 2; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getData()` + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.debug.DebugReq.prototype.getData_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getData())); +}; + + +/** @param {!(string|Uint8Array)} value */ +proto.cc.arduino.cli.debug.DebugReq.prototype.setData = function(value) { + jspb.Message.setProto3BytesField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.debug.DebugConfigReq = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.debug.DebugConfigReq, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.debug.DebugConfigReq.displayName = 'proto.cc.arduino.cli.debug.DebugConfigReq'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.debug.DebugConfigReq.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.debug.DebugConfigReq} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.DebugConfigReq.toObject = function(includeInstance, msg) { + var f, obj = { + instance: (f = msg.getInstance()) && proto.cc.arduino.cli.debug.Instance.toObject(includeInstance, f), + fqbn: jspb.Message.getFieldWithDefault(msg, 2, ""), + sketchPath: jspb.Message.getFieldWithDefault(msg, 3, ""), + port: jspb.Message.getFieldWithDefault(msg, 4, ""), + verbose: jspb.Message.getFieldWithDefault(msg, 5, false), + importFile: jspb.Message.getFieldWithDefault(msg, 7, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.debug.DebugConfigReq} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.debug.DebugConfigReq; + return proto.cc.arduino.cli.debug.DebugConfigReq.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.debug.DebugConfigReq} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.debug.DebugConfigReq} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.cc.arduino.cli.debug.Instance; + reader.readMessage(value,proto.cc.arduino.cli.debug.Instance.deserializeBinaryFromReader); + msg.setInstance(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setFqbn(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSketchPath(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setPort(value); + break; + case 5: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setVerbose(value); + break; + case 7: + var value = /** @type {string} */ (reader.readString()); + msg.setImportFile(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.debug.DebugConfigReq.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.debug.DebugConfigReq} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.DebugConfigReq.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getInstance(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.cc.arduino.cli.debug.Instance.serializeBinaryToWriter + ); + } + f = message.getFqbn(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSketchPath(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getPort(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } + f = message.getVerbose(); + if (f) { + writer.writeBool( + 5, + f + ); + } + f = message.getImportFile(); + if (f.length > 0) { + writer.writeString( + 7, + f + ); + } +}; + + +/** + * optional Instance instance = 1; + * @return {?proto.cc.arduino.cli.debug.Instance} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.getInstance = function() { + return /** @type{?proto.cc.arduino.cli.debug.Instance} */ ( + jspb.Message.getWrapperField(this, proto.cc.arduino.cli.debug.Instance, 1)); +}; + + +/** @param {?proto.cc.arduino.cli.debug.Instance|undefined} value */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.setInstance = function(value) { + jspb.Message.setWrapperField(this, 1, value); +}; + + +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.clearInstance = function() { + this.setInstance(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.hasInstance = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string fqbn = 2; + * @return {string} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.getFqbn = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.setFqbn = function(value) { + jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string sketch_path = 3; + * @return {string} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.getSketchPath = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.setSketchPath = function(value) { + jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string port = 4; + * @return {string} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.getPort = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.setPort = function(value) { + jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional bool verbose = 5; + * Note that Boolean fields may be set to 0/1 when serialized from a Java server. + * You should avoid comparisons like {@code val === true/false} in those cases. + * @return {boolean} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.getVerbose = function() { + return /** @type {boolean} */ (jspb.Message.getFieldWithDefault(this, 5, false)); +}; + + +/** @param {boolean} value */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.setVerbose = function(value) { + jspb.Message.setProto3BooleanField(this, 5, value); +}; + + +/** + * optional string import_file = 7; + * @return {string} + */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.getImportFile = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 7, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.debug.DebugConfigReq.prototype.setImportFile = function(value) { + jspb.Message.setProto3StringField(this, 7, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.debug.DebugResp = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.debug.DebugResp, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.debug.DebugResp.displayName = 'proto.cc.arduino.cli.debug.DebugResp'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.debug.DebugResp.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.debug.DebugResp.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.debug.DebugResp} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.DebugResp.toObject = function(includeInstance, msg) { + var f, obj = { + data: msg.getData_asB64(), + error: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.debug.DebugResp} + */ +proto.cc.arduino.cli.debug.DebugResp.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.debug.DebugResp; + return proto.cc.arduino.cli.debug.DebugResp.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.debug.DebugResp} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.debug.DebugResp} + */ +proto.cc.arduino.cli.debug.DebugResp.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setData(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setError(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.debug.DebugResp.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.debug.DebugResp.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.debug.DebugResp} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.DebugResp.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getData_asU8(); + if (f.length > 0) { + writer.writeBytes( + 1, + f + ); + } + f = message.getError(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional bytes data = 1; + * @return {!(string|Uint8Array)} + */ +proto.cc.arduino.cli.debug.DebugResp.prototype.getData = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * optional bytes data = 1; + * This is a type-conversion wrapper around `getData()` + * @return {string} + */ +proto.cc.arduino.cli.debug.DebugResp.prototype.getData_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getData())); +}; + + +/** + * optional bytes data = 1; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getData()` + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.debug.DebugResp.prototype.getData_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getData())); +}; + + +/** @param {!(string|Uint8Array)} value */ +proto.cc.arduino.cli.debug.DebugResp.prototype.setData = function(value) { + jspb.Message.setProto3BytesField(this, 1, value); +}; + + +/** + * optional string error = 2; + * @return {string} + */ +proto.cc.arduino.cli.debug.DebugResp.prototype.getError = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.debug.DebugResp.prototype.setError = function(value) { + jspb.Message.setProto3StringField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.debug.Instance = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.debug.Instance, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.debug.Instance.displayName = 'proto.cc.arduino.cli.debug.Instance'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.debug.Instance.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.debug.Instance.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.debug.Instance} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.Instance.toObject = function(includeInstance, msg) { + var f, obj = { + id: jspb.Message.getFieldWithDefault(msg, 1, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.debug.Instance} + */ +proto.cc.arduino.cli.debug.Instance.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.debug.Instance; + return proto.cc.arduino.cli.debug.Instance.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.debug.Instance} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.debug.Instance} + */ +proto.cc.arduino.cli.debug.Instance.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readInt32()); + msg.setId(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.debug.Instance.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.debug.Instance.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.debug.Instance} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.debug.Instance.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getId(); + if (f !== 0) { + writer.writeInt32( + 1, + f + ); + } +}; + + +/** + * optional int32 id = 1; + * @return {number} + */ +proto.cc.arduino.cli.debug.Instance.prototype.getId = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** @param {number} value */ +proto.cc.arduino.cli.debug.Instance.prototype.setId = function(value) { + jspb.Message.setProto3IntField(this, 1, value); +}; + + +goog.object.extend(exports, proto.cc.arduino.cli.debug); diff --git a/arduino-ide-extension/src/node/cli-protocol/monitor/monitor_grpc_pb.js b/arduino-ide-extension/src/node/cli-protocol/monitor/monitor_grpc_pb.js index 6caca263..c9821121 100644 --- a/arduino-ide-extension/src/node/cli-protocol/monitor/monitor_grpc_pb.js +++ b/arduino-ide-extension/src/node/cli-protocol/monitor/monitor_grpc_pb.js @@ -3,7 +3,7 @@ // Original file comments: // This file is part of arduino-cli. // -// Copyright 2019 ARDUINO SA (http://www.arduino.cc/) +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, // which covers the main part of arduino-cli. diff --git a/arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.d.ts new file mode 100644 index 00000000..0c251539 --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.d.ts @@ -0,0 +1,92 @@ +// package: cc.arduino.cli.settings +// file: settings/settings.proto + +/* tslint:disable */ +/* eslint-disable */ + +import * as grpc from "@grpc/grpc-js"; +import * as settings_settings_pb from "../settings/settings_pb"; + +interface ISettingsService extends grpc.ServiceDefinition { + getAll: ISettingsService_IGetAll; + merge: ISettingsService_IMerge; + getValue: ISettingsService_IGetValue; + setValue: ISettingsService_ISetValue; +} + +interface ISettingsService_IGetAll extends grpc.MethodDefinition { + path: string; // "/cc.arduino.cli.settings.Settings/GetAll" + requestStream: boolean; // false + responseStream: boolean; // false + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} +interface ISettingsService_IMerge extends grpc.MethodDefinition { + path: string; // "/cc.arduino.cli.settings.Settings/Merge" + requestStream: boolean; // false + responseStream: boolean; // false + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} +interface ISettingsService_IGetValue extends grpc.MethodDefinition { + path: string; // "/cc.arduino.cli.settings.Settings/GetValue" + requestStream: boolean; // false + responseStream: boolean; // false + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} +interface ISettingsService_ISetValue extends grpc.MethodDefinition { + path: string; // "/cc.arduino.cli.settings.Settings/SetValue" + requestStream: boolean; // false + responseStream: boolean; // false + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} + +export const SettingsService: ISettingsService; + +export interface ISettingsServer { + getAll: grpc.handleUnaryCall; + merge: grpc.handleUnaryCall; + getValue: grpc.handleUnaryCall; + setValue: grpc.handleUnaryCall; +} + +export interface ISettingsClient { + getAll(request: settings_settings_pb.GetAllRequest, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.RawData) => void): grpc.ClientUnaryCall; + getAll(request: settings_settings_pb.GetAllRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.RawData) => void): grpc.ClientUnaryCall; + getAll(request: settings_settings_pb.GetAllRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.RawData) => void): grpc.ClientUnaryCall; + merge(request: settings_settings_pb.RawData, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.MergeResponse) => void): grpc.ClientUnaryCall; + merge(request: settings_settings_pb.RawData, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.MergeResponse) => void): grpc.ClientUnaryCall; + merge(request: settings_settings_pb.RawData, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.MergeResponse) => void): grpc.ClientUnaryCall; + getValue(request: settings_settings_pb.GetValueRequest, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.Value) => void): grpc.ClientUnaryCall; + getValue(request: settings_settings_pb.GetValueRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.Value) => void): grpc.ClientUnaryCall; + getValue(request: settings_settings_pb.GetValueRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.Value) => void): grpc.ClientUnaryCall; + setValue(request: settings_settings_pb.Value, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall; + setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall; + setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall; +} + +export class SettingsClient extends grpc.Client implements ISettingsClient { + constructor(address: string, credentials: grpc.ChannelCredentials, options?: object); + public getAll(request: settings_settings_pb.GetAllRequest, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.RawData) => void): grpc.ClientUnaryCall; + public getAll(request: settings_settings_pb.GetAllRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.RawData) => void): grpc.ClientUnaryCall; + public getAll(request: settings_settings_pb.GetAllRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.RawData) => void): grpc.ClientUnaryCall; + public merge(request: settings_settings_pb.RawData, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.MergeResponse) => void): grpc.ClientUnaryCall; + public merge(request: settings_settings_pb.RawData, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.MergeResponse) => void): grpc.ClientUnaryCall; + public merge(request: settings_settings_pb.RawData, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.MergeResponse) => void): grpc.ClientUnaryCall; + public getValue(request: settings_settings_pb.GetValueRequest, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.Value) => void): grpc.ClientUnaryCall; + public getValue(request: settings_settings_pb.GetValueRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.Value) => void): grpc.ClientUnaryCall; + public getValue(request: settings_settings_pb.GetValueRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.Value) => void): grpc.ClientUnaryCall; + public setValue(request: settings_settings_pb.Value, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall; + public setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall; + public setValue(request: settings_settings_pb.Value, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: settings_settings_pb.SetValueResponse) => void): grpc.ClientUnaryCall; +} diff --git a/arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.js b/arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.js new file mode 100644 index 00000000..0e42e729 --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/settings/settings_grpc_pb.js @@ -0,0 +1,137 @@ +// GENERATED CODE -- DO NOT EDIT! + +// Original file comments: +// This file is part of arduino-cli. +// +// Copyright 2020 ARDUINO SA (http://www.arduino.cc/) +// +// This software is released under the GNU General Public License version 3, +// which covers the main part of arduino-cli. +// The terms of this license can be found at: +// https://www.gnu.org/licenses/gpl-3.0.en.html +// +// You can be released from the requirements of the above licenses by purchasing +// a commercial license. Buying such a license is mandatory if you want to +// modify or otherwise use the software for commercial activities involving the +// Arduino software without disclosing the source code of your own applications. +// To purchase a commercial license, send an email to license@arduino.cc. +// +'use strict'; +var grpc = require('@grpc/grpc-js'); +var settings_settings_pb = require('../settings/settings_pb.js'); + +function serialize_cc_arduino_cli_settings_GetAllRequest(arg) { + if (!(arg instanceof settings_settings_pb.GetAllRequest)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.GetAllRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_GetAllRequest(buffer_arg) { + return settings_settings_pb.GetAllRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_settings_GetValueRequest(arg) { + if (!(arg instanceof settings_settings_pb.GetValueRequest)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.GetValueRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_GetValueRequest(buffer_arg) { + return settings_settings_pb.GetValueRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_settings_MergeResponse(arg) { + if (!(arg instanceof settings_settings_pb.MergeResponse)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.MergeResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_MergeResponse(buffer_arg) { + return settings_settings_pb.MergeResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_settings_RawData(arg) { + if (!(arg instanceof settings_settings_pb.RawData)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.RawData'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_RawData(buffer_arg) { + return settings_settings_pb.RawData.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_settings_SetValueResponse(arg) { + if (!(arg instanceof settings_settings_pb.SetValueResponse)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.SetValueResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_SetValueResponse(buffer_arg) { + return settings_settings_pb.SetValueResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_cc_arduino_cli_settings_Value(arg) { + if (!(arg instanceof settings_settings_pb.Value)) { + throw new Error('Expected argument of type cc.arduino.cli.settings.Value'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_cc_arduino_cli_settings_Value(buffer_arg) { + return settings_settings_pb.Value.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +var SettingsService = exports.SettingsService = { + getAll: { + path: '/cc.arduino.cli.settings.Settings/GetAll', + requestStream: false, + responseStream: false, + requestType: settings_settings_pb.GetAllRequest, + responseType: settings_settings_pb.RawData, + requestSerialize: serialize_cc_arduino_cli_settings_GetAllRequest, + requestDeserialize: deserialize_cc_arduino_cli_settings_GetAllRequest, + responseSerialize: serialize_cc_arduino_cli_settings_RawData, + responseDeserialize: deserialize_cc_arduino_cli_settings_RawData, + }, + merge: { + path: '/cc.arduino.cli.settings.Settings/Merge', + requestStream: false, + responseStream: false, + requestType: settings_settings_pb.RawData, + responseType: settings_settings_pb.MergeResponse, + requestSerialize: serialize_cc_arduino_cli_settings_RawData, + requestDeserialize: deserialize_cc_arduino_cli_settings_RawData, + responseSerialize: serialize_cc_arduino_cli_settings_MergeResponse, + responseDeserialize: deserialize_cc_arduino_cli_settings_MergeResponse, + }, + getValue: { + path: '/cc.arduino.cli.settings.Settings/GetValue', + requestStream: false, + responseStream: false, + requestType: settings_settings_pb.GetValueRequest, + responseType: settings_settings_pb.Value, + requestSerialize: serialize_cc_arduino_cli_settings_GetValueRequest, + requestDeserialize: deserialize_cc_arduino_cli_settings_GetValueRequest, + responseSerialize: serialize_cc_arduino_cli_settings_Value, + responseDeserialize: deserialize_cc_arduino_cli_settings_Value, + }, + setValue: { + path: '/cc.arduino.cli.settings.Settings/SetValue', + requestStream: false, + responseStream: false, + requestType: settings_settings_pb.Value, + responseType: settings_settings_pb.SetValueResponse, + requestSerialize: serialize_cc_arduino_cli_settings_Value, + requestDeserialize: deserialize_cc_arduino_cli_settings_Value, + responseSerialize: serialize_cc_arduino_cli_settings_SetValueResponse, + responseDeserialize: deserialize_cc_arduino_cli_settings_SetValueResponse, + }, +}; + +exports.SettingsClient = grpc.makeGenericClientConstructor(SettingsService); diff --git a/arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.d.ts b/arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.d.ts new file mode 100644 index 00000000..e5278795 --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.d.ts @@ -0,0 +1,125 @@ +// package: cc.arduino.cli.settings +// file: settings/settings.proto + +/* tslint:disable */ +/* eslint-disable */ + +import * as jspb from "google-protobuf"; + +export class RawData extends jspb.Message { + getJsondata(): string; + setJsondata(value: string): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): RawData.AsObject; + static toObject(includeInstance: boolean, msg: RawData): RawData.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: RawData, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): RawData; + static deserializeBinaryFromReader(message: RawData, reader: jspb.BinaryReader): RawData; +} + +export namespace RawData { + export type AsObject = { + jsondata: string, + } +} + +export class Value extends jspb.Message { + getKey(): string; + setKey(value: string): void; + + getJsondata(): string; + setJsondata(value: string): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): Value.AsObject; + static toObject(includeInstance: boolean, msg: Value): Value.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: Value, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): Value; + static deserializeBinaryFromReader(message: Value, reader: jspb.BinaryReader): Value; +} + +export namespace Value { + export type AsObject = { + key: string, + jsondata: string, + } +} + +export class GetAllRequest extends jspb.Message { + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): GetAllRequest.AsObject; + static toObject(includeInstance: boolean, msg: GetAllRequest): GetAllRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: GetAllRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): GetAllRequest; + static deserializeBinaryFromReader(message: GetAllRequest, reader: jspb.BinaryReader): GetAllRequest; +} + +export namespace GetAllRequest { + export type AsObject = { + } +} + +export class GetValueRequest extends jspb.Message { + getKey(): string; + setKey(value: string): void; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): GetValueRequest.AsObject; + static toObject(includeInstance: boolean, msg: GetValueRequest): GetValueRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: GetValueRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): GetValueRequest; + static deserializeBinaryFromReader(message: GetValueRequest, reader: jspb.BinaryReader): GetValueRequest; +} + +export namespace GetValueRequest { + export type AsObject = { + key: string, + } +} + +export class MergeResponse extends jspb.Message { + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): MergeResponse.AsObject; + static toObject(includeInstance: boolean, msg: MergeResponse): MergeResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: MergeResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): MergeResponse; + static deserializeBinaryFromReader(message: MergeResponse, reader: jspb.BinaryReader): MergeResponse; +} + +export namespace MergeResponse { + export type AsObject = { + } +} + +export class SetValueResponse extends jspb.Message { + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SetValueResponse.AsObject; + static toObject(includeInstance: boolean, msg: SetValueResponse): SetValueResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SetValueResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SetValueResponse; + static deserializeBinaryFromReader(message: SetValueResponse, reader: jspb.BinaryReader): SetValueResponse; +} + +export namespace SetValueResponse { + export type AsObject = { + } +} diff --git a/arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.js b/arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.js new file mode 100644 index 00000000..dd356474 --- /dev/null +++ b/arduino-ide-extension/src/node/cli-protocol/settings/settings_pb.js @@ -0,0 +1,821 @@ +/** + * @fileoverview + * @enhanceable + * @suppress {messageConventions} JS Compiler reports an error if a variable or + * field starts with 'MSG_' and isn't a translatable message. + * @public + */ +// GENERATED CODE -- DO NOT EDIT! + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = Function('return this')(); + +goog.exportSymbol('proto.cc.arduino.cli.settings.GetAllRequest', null, global); +goog.exportSymbol('proto.cc.arduino.cli.settings.GetValueRequest', null, global); +goog.exportSymbol('proto.cc.arduino.cli.settings.MergeResponse', null, global); +goog.exportSymbol('proto.cc.arduino.cli.settings.RawData', null, global); +goog.exportSymbol('proto.cc.arduino.cli.settings.SetValueResponse', null, global); +goog.exportSymbol('proto.cc.arduino.cli.settings.Value', null, global); + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.RawData = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.RawData, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.settings.RawData.displayName = 'proto.cc.arduino.cli.settings.RawData'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.RawData.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.RawData.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.RawData} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.RawData.toObject = function(includeInstance, msg) { + var f, obj = { + jsondata: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.RawData} + */ +proto.cc.arduino.cli.settings.RawData.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.RawData; + return proto.cc.arduino.cli.settings.RawData.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.RawData} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.RawData} + */ +proto.cc.arduino.cli.settings.RawData.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setJsondata(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.RawData.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.RawData.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.RawData} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.RawData.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getJsondata(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string jsonData = 1; + * @return {string} + */ +proto.cc.arduino.cli.settings.RawData.prototype.getJsondata = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.settings.RawData.prototype.setJsondata = function(value) { + jspb.Message.setProto3StringField(this, 1, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.Value = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.Value, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.settings.Value.displayName = 'proto.cc.arduino.cli.settings.Value'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.Value.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.Value.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.Value} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.Value.toObject = function(includeInstance, msg) { + var f, obj = { + key: jspb.Message.getFieldWithDefault(msg, 1, ""), + jsondata: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.Value} + */ +proto.cc.arduino.cli.settings.Value.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.Value; + return proto.cc.arduino.cli.settings.Value.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.Value} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.Value} + */ +proto.cc.arduino.cli.settings.Value.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setKey(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setJsondata(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.Value.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.Value.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.Value} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.Value.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getKey(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getJsondata(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional string key = 1; + * @return {string} + */ +proto.cc.arduino.cli.settings.Value.prototype.getKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.settings.Value.prototype.setKey = function(value) { + jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string jsonData = 2; + * @return {string} + */ +proto.cc.arduino.cli.settings.Value.prototype.getJsondata = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.settings.Value.prototype.setJsondata = function(value) { + jspb.Message.setProto3StringField(this, 2, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.GetAllRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.GetAllRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.settings.GetAllRequest.displayName = 'proto.cc.arduino.cli.settings.GetAllRequest'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.GetAllRequest.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.GetAllRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.GetAllRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.GetAllRequest.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.GetAllRequest} + */ +proto.cc.arduino.cli.settings.GetAllRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.GetAllRequest; + return proto.cc.arduino.cli.settings.GetAllRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.GetAllRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.GetAllRequest} + */ +proto.cc.arduino.cli.settings.GetAllRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.GetAllRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.GetAllRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.GetAllRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.GetAllRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.GetValueRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.GetValueRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.settings.GetValueRequest.displayName = 'proto.cc.arduino.cli.settings.GetValueRequest'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.GetValueRequest.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.GetValueRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.GetValueRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.GetValueRequest.toObject = function(includeInstance, msg) { + var f, obj = { + key: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.GetValueRequest} + */ +proto.cc.arduino.cli.settings.GetValueRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.GetValueRequest; + return proto.cc.arduino.cli.settings.GetValueRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.GetValueRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.GetValueRequest} + */ +proto.cc.arduino.cli.settings.GetValueRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setKey(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.GetValueRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.GetValueRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.GetValueRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.GetValueRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getKey(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string key = 1; + * @return {string} + */ +proto.cc.arduino.cli.settings.GetValueRequest.prototype.getKey = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** @param {string} value */ +proto.cc.arduino.cli.settings.GetValueRequest.prototype.setKey = function(value) { + jspb.Message.setProto3StringField(this, 1, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.MergeResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.MergeResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.settings.MergeResponse.displayName = 'proto.cc.arduino.cli.settings.MergeResponse'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.MergeResponse.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.MergeResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.MergeResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.MergeResponse.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.MergeResponse} + */ +proto.cc.arduino.cli.settings.MergeResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.MergeResponse; + return proto.cc.arduino.cli.settings.MergeResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.MergeResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.MergeResponse} + */ +proto.cc.arduino.cli.settings.MergeResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.MergeResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.MergeResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.MergeResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.MergeResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.cc.arduino.cli.settings.SetValueResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.cc.arduino.cli.settings.SetValueResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.cc.arduino.cli.settings.SetValueResponse.displayName = 'proto.cc.arduino.cli.settings.SetValueResponse'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.cc.arduino.cli.settings.SetValueResponse.prototype.toObject = function(opt_includeInstance) { + return proto.cc.arduino.cli.settings.SetValueResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.cc.arduino.cli.settings.SetValueResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.SetValueResponse.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.cc.arduino.cli.settings.SetValueResponse} + */ +proto.cc.arduino.cli.settings.SetValueResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.cc.arduino.cli.settings.SetValueResponse; + return proto.cc.arduino.cli.settings.SetValueResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.cc.arduino.cli.settings.SetValueResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.cc.arduino.cli.settings.SetValueResponse} + */ +proto.cc.arduino.cli.settings.SetValueResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.cc.arduino.cli.settings.SetValueResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.cc.arduino.cli.settings.SetValueResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.cc.arduino.cli.settings.SetValueResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.cc.arduino.cli.settings.SetValueResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + +goog.object.extend(exports, proto.cc.arduino.cli.settings); diff --git a/arduino-ide-extension/src/node/core-client-provider-impl.ts b/arduino-ide-extension/src/node/core-client-provider-impl.ts index 92692feb..a56ec2b7 100644 --- a/arduino-ide-extension/src/node/core-client-provider-impl.ts +++ b/arduino-ide-extension/src/node/core-client-provider-impl.ts @@ -11,7 +11,6 @@ import { ArduinoCoreClient } from './cli-protocol/commands/commands_grpc_pb'; import { InitResp, InitReq, - Configuration, UpdateIndexReq, UpdateIndexResp, UpdateLibrariesIndexReq, @@ -91,7 +90,6 @@ export class CoreClientProviderImpl implements CoreClientProvider { console.info(` >>> Creating and caching a new client for ${rootUri}...`); const client = new ArduinoCoreClient('localhost:50051', grpc.credentials.createInsecure()); - const config = new Configuration(); const rootPath = await this.fileSystem.getFsPath(rootUri); if (!rootPath) { throw new Error(`Could not resolve filesystem path of URI: ${rootUri}.`); @@ -114,13 +112,7 @@ export class CoreClientProviderImpl implements CoreClientProvider { fs.mkdirSync(downloadDir); } - config.setSketchbookdir(sketchDirPath); - config.setDatadir(dataDirPath); - config.setDownloadsdir(downloadDir); - config.setBoardmanageradditionalurlsList(['https://downloads.arduino.cc/packages/package_index.json']); - const initReq = new InitReq(); - initReq.setConfiguration(config); initReq.setLibraryManagerOnly(false); const initResp = await new Promise(resolve => { let resp: InitResp | undefined = undefined; diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index 549611d9..24862806 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -50,6 +50,7 @@ export class CoreServiceImpl implements CoreService { compilerReq.setInstance(instance); compilerReq.setSketchpath(sketchpath); compilerReq.setFqbn(currentBoard.fqbn!); + compilerReq.setOptimizefordebug(options.optimizeForDebug); compilerReq.setPreprocess(false); compilerReq.setVerbose(true); compilerReq.setQuiet(false); @@ -72,7 +73,7 @@ export class CoreServiceImpl implements CoreService { } async upload(options: CoreService.Upload.Options): Promise { - await this.compile({ uri: options.uri, board: options.board }); + await this.compile({ uri: options.uri, board: options.board, optimizeForDebug: options.optimizeForDebug }); console.log('upload', options); const { uri } = options; From acf7b6a8dacd3b362bd15fd2e310c75ba3cc9da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 10:37:17 +0100 Subject: [PATCH 33/44] Removed workaround --- .../src/node/language/arduino-language-server-contribution.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts index 00620c49..d3db36e7 100644 --- a/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts +++ b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts @@ -44,8 +44,6 @@ export class ArduinoLanguageServerContribution extends BaseLanguageServerContrib console.log(`Starting language server ${languageServer} ${args.join(' ')}`); const serverConnection = await this.createProcessStreamConnectionAsync(languageServer, args); this.forward(clientConnection, serverConnection); - // https://github.com/eclipse-theia/theia/issues/6308 - serverConnection.onClose(() => (clientConnection as any).reader.socket.close()); } } From a72533b2085d40cc806503f312d5a2d44ae2d5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 14:33:51 +0100 Subject: [PATCH 34/44] Added basic support for global and static variables --- ...debug-frontend-application-contribution.ts | 14 +-- .../arduino-debug-adapter-contribution.ts | 3 +- .../debug-adapter/arduino-debug-session.ts | 107 +++++++++++++--- .../node/debug-adapter/arduino-gdb-backend.ts | 15 ++- .../debug-adapter/arduino-variable-handler.ts | 115 ++++++++++++++++++ .../browser/arduino-frontend-contribution.tsx | 2 +- 6 files changed, 223 insertions(+), 33 deletions(-) create mode 100644 arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index 8f9b6ea0..60f9dd21 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -1,17 +1,17 @@ import { injectable, inject } from 'inversify'; import { MenuModelRegistry, Path, MessageService, Command, CommandRegistry } from '@theia/core'; -import { KeybindingRegistry, Widget } from '@theia/core/lib/browser'; +import { KeybindingRegistry } from '@theia/core/lib/browser'; import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; -import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; -import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; -import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service'; import { FileSystem } from '@theia/filesystem/lib/common'; import URI from '@theia/core/lib/common/uri'; import { EditorManager } from '@theia/editor/lib/browser'; +import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; +import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service'; import { ArduinoToolbar } from 'arduino-ide-extension/lib/browser/toolbar/arduino-toolbar'; +import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; export namespace ArduinoDebugCommands { export const START_DEBUG: Command = { @@ -109,9 +109,7 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp } registerToolbarItems(toolbar: TabBarToolbarRegistry): void { - if (this.editorMode.proMode) { - super.registerToolbarItems(toolbar); - } + super.registerToolbarItems(toolbar); toolbar.registerItem({ id: ArduinoDebugCommands.START_DEBUG.id, command: ArduinoDebugCommands.START_DEBUG.id, @@ -125,7 +123,7 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp registry.registerCommand(ArduinoDebugCommands.START_DEBUG, { isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left', - execute: async (widget: Widget, target: EventTarget) => { + execute: () => { registry.executeCommand(DebugCommands.START.id); } }); 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 9f6ee487..92ddf504 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -70,7 +70,8 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution fqbn: '${fqbn}', uploadPort: '${port}', initCommands: [ - `-break-insert -t --function ${startFunction}` + `-break-insert -t --function ${startFunction}`, + '-interpreter-exec console "monitor reset halt"' ] } if (!res.sketch) { 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 index 670a9e93..ef69bf67 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -1,8 +1,10 @@ -import * as mi from 'cdt-gdb-adapter/dist/mi'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { GDBDebugSession, LaunchRequestArguments } from 'cdt-gdb-adapter/dist/GDBDebugSession'; +import { GDBDebugSession, FrameVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import * as mi from 'cdt-gdb-adapter/dist/mi'; import { ArduinoGDBBackend } from './arduino-gdb-backend'; +import { ArduinoVariableHandler } from './arduino-variable-handler'; +import { Scope } from 'vscode-debugadapter'; export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { arduinoCli?: string; @@ -11,28 +13,34 @@ export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchReque uploadPort?: string; } +const GLOBAL_HANDLE_ID = 0xFE; +const STATIC_HANDLES_START = 0x010000; +const STATIC_HANDLES_FINISH = 0x01FFFF; + export class ArduinoDebugSession extends GDBDebugSession { - protected get arduinoBackend(): ArduinoGDBBackend { + private _variableHandler: ArduinoVariableHandler; + + get arduinoBackend(): ArduinoGDBBackend { return this.gdb as ArduinoGDBBackend; } + protected get variableHandler() { + if (this._variableHandler) { + return this._variableHandler; + } + if (!this.gdb) { + throw new Error("GDB backend is not ready."); + } + const handler = new ArduinoVariableHandler(this, this.frameHandles, this.variableHandles); + this._variableHandler = handler; + return handler; + } + protected createBackend(): GDBBackend { return new ArduinoGDBBackend(); } - protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): Promise { - const additionalCommands = [ - '-interpreter-exec console "monitor reset halt"' - ]; - if (!args.initCommands) { - args.initCommands = additionalCommands; - } else { - args.initCommands.push(...additionalCommands); - } - return super.launchRequest(response, args); - } - protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise { try { await mi.sendExecContinue(this.gdb); @@ -50,11 +58,6 @@ export class ArduinoDebugSession extends GDBDebugSession { this.gdb.pause(); await waitPromise; } - try { - await this.arduinoBackend.sendTargetDetach(); - } catch (e) { - // Need to catch here as the command result being returned will never exist as it's detached - } await this.gdb.sendGDBExit(); this.sendResponse(response); } catch (err) { @@ -62,4 +65,68 @@ export class ArduinoDebugSession extends GDBDebugSession { } } + protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): Promise { + try { + + return super.stackTraceRequest(response, args); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + try { + const frame: FrameVariableReference = { + type: 'frame', + frameHandle: args.frameId, + }; + // const pins: ObjectVariableReference = { + // type: "object", + // varobjName: "__pins", + // frameHandle: 42000, + // } + + response.body = { + scopes: [ + // new Scope('Pins', this.variableHandles.create(pins), false), + new Scope('Local', this.variableHandles.create(frame), false), + new Scope('Global', GLOBAL_HANDLE_ID, false), + new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false) + ], + }; + + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + try { + response.body = { + variables: [] as DebugProtocol.Variable[] + }; + const ref = this.variableHandles.get(args.variablesReference); + if (args.variablesReference === GLOBAL_HANDLE_ID) { + // Use hardcoded global handle to load and store global variables + response.body.variables = await this.variableHandler.getGlobalVariables(); + } else if (args.variablesReference >= STATIC_HANDLES_START && args.variablesReference <= STATIC_HANDLES_FINISH) { + // Use STATIC_HANDLES_START to shift the framehandles back + const frameHandle = args.variablesReference - STATIC_HANDLES_START; + response.body.variables = await this.variableHandler.getStaticVariables(frameHandle); + } else if (ref && ref.type === 'frame') { + // List variables for current frame + response.body.variables = await this.handleVariableRequestFrame(ref); + } else if (ref && ref.varobjName === '__pins') { + response.body.variables = await this.variableHandler.handlePinStatusRequest(); + } else if (ref && ref.type === 'object') { + // List data under any variable + response.body.variables = await this.handleVariableRequestObject(ref); + } + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + } 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 index 2630eeb4..a805522f 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import * as fs from 'arduino-ide-extension/lib/node/fs-extra' import { spawn } from 'child_process'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi'; import { ArduinoLaunchRequestArguments } from './arduino-debug-session'; export class ArduinoGDBBackend extends GDBBackend { @@ -33,9 +34,17 @@ export class ArduinoGDBBackend extends GDBBackend { return Promise.resolve(); } - pause(): boolean { - this.sendCommand('-exec-interrupt'); - return true; + sendExecInterrupt(threadId?: number) { + let command = '-exec-interrupt'; + if (threadId) { + command += ` --thread ${threadId}`; + } + return this.sendCommand(command); + } + + sendStackInfoFrame(gdb: GDBBackend, threadId: number, frameId: number): Promise<{ frame: MIFrameInfo }> { + const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`; + return gdb.sendCommand(command); } sendTargetDetach(): Promise { diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts new file mode 100644 index 00000000..c3705f95 --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts @@ -0,0 +1,115 @@ +import * as path from 'path'; +import { DebugProtocol } from "vscode-debugprotocol"; +import { Handles } from 'vscode-debugadapter/lib/handles'; +import { FrameReference, VariableReference } from "cdt-gdb-adapter/dist/GDBDebugSession"; +import { VarManager } from 'cdt-gdb-adapter/dist/varManager'; +import * as mi from 'cdt-gdb-adapter/dist/mi'; +import { ArduinoDebugSession } from "./arduino-debug-session"; +import { ArduinoGDBBackend } from './arduino-gdb-backend'; + +export class ArduinoVariableHandler { + + protected readonly gdb: ArduinoGDBBackend; + protected readonly varMgr: VarManager; + + protected globalHandle: number; + + constructor(protected readonly session: ArduinoDebugSession, + protected frameHandles: Handles, + protected variableHandles: Handles) { + this.gdb = session.arduinoBackend; + this.varMgr = new VarManager(this.gdb); + } + + createGlobalHandle() { + this.globalHandle = this.frameHandles.create({ + threadId: -1, + frameId: -1 + }); + } + + /** TODO */ + async getGlobalVariables(): Promise { + throw new Error('Global variables are not supported yet.'); + const frame = this.frameHandles.get(this.globalHandle); + const symbolInfo: any[] = [] // this.symbolTable.getGlobalVariables(); + const variables: DebugProtocol.Variable[] = []; + + for (const symbol of symbolInfo) { + const name = `global_var_${symbol.name}`; + const variable = await this.getVariables(frame, name, symbol.name, -1); + variables.push(variable); + } + + return variables; + } + + private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise { + let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name); + + if (global) { + // Update value if it is already loaded + const vup = await mi.sendVarUpdate(this.gdb, { name }); + const update = vup.changelist[0]; + if (update && update.in_scope === 'true' && update.name === global.varname) { + global.value = update.value; + } + } else { + // create var in GDB and store it in the varMgr + const varCreateResponse = await mi.sendVarCreate(this.gdb, { + name, + frame: 'current', + expression, + }); + + global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse); + } + + return { + name: expression, + value: (global.value === void 0) ? '' : global.value, + type: global.type, + variablesReference: parseInt(global.numchild, 10) > 0 + ? this.variableHandles.create({ + frameHandle: this.globalHandle, + type: 'object', + varobjName: global.varname, + }) + : 0, + }; + } + + /** TODO */ + async getStaticVariables(frameHandle: number): Promise { + throw new Error('Static variables are not supported yet.'); + const frame = this.frameHandles.get(frameHandle); + const result = await this.gdb.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); + const file = path.normalize(result.frame.file || ''); + const symbolInfo: any[] = [] // this.symbolTable.getStaticVariables(file); + const variables: DebugProtocol.Variable[] = []; + + // Fetch stack depth to obtain frameId/threadId/depth tuple + const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 }); + const depth = parseInt(stackDepth.depth, 10); + + for (const symbol of symbolInfo) { + const name = `${file}_static_var_${symbol.name}`; + const variable = await this.getVariables(frame, name, symbol.name, depth); + variables.push(variable); + } + + return variables; + } + + async handlePinStatusRequest(): Promise { + const variables: DebugProtocol.Variable[] = []; + variables.push({ + name: "D2", + type: "gpio", + value: "0x00", + variablesReference: 0 + }) + return variables; + } + +} diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index c89e2b96..37f0997b 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -461,7 +461,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerMenuAction(ArduinoMenus.SKETCH, { commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, - label: 'Optimize for Debug', + label: 'Optimize for Debugging', order: '2' }); registry.registerMenuAction(ArduinoMenus.SKETCH, { From beb529cf15447bc97e9219a14f9766c0d09983b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 15:05:09 +0100 Subject: [PATCH 35/44] Removed unused CMSIS code --- .../src/node/debug-adapter/abstract-server.ts | 164 ------- .../src/node/debug-adapter/cmsis-backend.ts | 39 -- .../node/debug-adapter/cmsis-debug-session.ts | 458 ------------------ .../src/node/debug-adapter/mi.ts | 80 --- .../src/node/debug-adapter/openocd-server.ts | 54 --- .../src/node/debug-adapter/port-scanner.ts | 192 -------- .../src/node/debug-adapter/pyocd-server.ts | 71 --- .../src/node/debug-adapter/symbols.ts | 141 ------ 8 files changed, 1199 deletions(-) delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/mi.ts delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts delete mode 100644 arduino-debugger-extension/src/node/debug-adapter/symbols.ts diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts deleted file mode 100644 index 6ae6fc2b..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts +++ /dev/null @@ -1,164 +0,0 @@ -/* -* CMSIS Debug Adapter -* Copyright (c) 2017-2019 Marcel Ball -* 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 { EOL } from 'os'; -import { spawn, ChildProcess } from 'child_process'; -import { EventEmitter } from 'events'; -import { dirname } from 'path'; -import { CmsisRequestArguments } from './cmsis-debug-session'; - -const TIMEOUT = 1000 * 10; // 10 seconds - -export abstract class AbstractServer extends EventEmitter { - protected process?: ChildProcess; - protected outBuffer: string = ''; - protected errBuffer: string = ''; - protected launchResolve?: () => void; - protected launchReject?: (error: any) => void; - protected timer?: NodeJS.Timer; - - serverErrorEmitted = false; - - get isRunning(): boolean { - return !!this.process && !this.process.killed; - } - - spawn(args: CmsisRequestArguments): Promise { - return new Promise(async (resolve, reject) => { - this.launchResolve = resolve; - this.launchReject = reject; - - try { - this.timer = setTimeout(() => this.onSpawnError(new Error('Timeout waiting for gdb server to start')), TIMEOUT); - - const command = args.gdbServer; - if (!command) { - throw new Error('Missing parameter: gdbServer'); - } - const varRegexp = /\$\{.*\}/; - if (varRegexp.test(command)) { - throw new Error(`Unresolved variable: ${command}`) - } - const serverArguments = await this.resolveServerArguments(args); - this.process = spawn(command, serverArguments, { - cwd: dirname(command), - }); - - if (!this.process) { - throw new Error('Unable to spawn gdb server'); - } - - this.process.on('exit', this.onExit.bind(this)); - this.process.on('error', this.onSpawnError.bind(this)); - - if (this.process.stdout) { - this.process.stdout.on('data', this.onStdout.bind(this)); - } - if (this.process.stderr) { - this.process.stderr.on('data', this.onStderr.bind(this)); - } - } catch (error) { - this.onSpawnError(error); - } - }); - } - - kill() { - if (this.process) { - this.process.kill('SIGINT'); - } - } - - protected async resolveServerArguments(req: CmsisRequestArguments): Promise { - return req.gdbServerArguments || []; - } - - protected onExit(code: number, signal: string) { - this.emit('exit', code, signal); - - // Code can be undefined, null or 0 and we want to ignore those values - if (!!code && !this.serverErrorEmitted) { - this.emit('error', `GDB server stopped unexpectedly with exit code ${code}`); - } - } - - protected onSpawnError(error: Error) { - if (this.launchReject) { - this.clearTimer(); - this.launchReject(error); - this.clearPromises(); - } - } - - protected onStdout(chunk: string | Buffer) { - this.onData(chunk, this.outBuffer, 'stdout'); - } - - protected onStderr(chunk: string | Buffer) { - this.onData(chunk, this.errBuffer, 'stderr'); - } - - protected onData(chunk: string | Buffer, buffer: string, event: string) { - buffer += typeof chunk === 'string' ? chunk - : chunk.toString('utf8'); - - const end = buffer.lastIndexOf('\n'); - if (end !== -1) { - const data = buffer.substring(0, end); - this.emit(event, data); - this.handleData(data); - buffer = buffer.substring(end + 1); - } - } - - protected handleData(data: string) { - if (this.launchResolve && this.serverStarted(data)) { - this.clearTimer(); - this.launchResolve(); - this.clearPromises(); - } - - if (this.serverError(data)) { - this.emit('error', data.split(EOL)[0]); - this.serverErrorEmitted = true; - } - } - - protected clearTimer() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = undefined; - } - } - - protected clearPromises() { - this.launchResolve = undefined; - this.launchReject = undefined; - } - - protected abstract serverStarted(data: string): boolean; - protected abstract serverError(data: string): boolean; -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts deleted file mode 100644 index fd96566e..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* -* 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 { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; -import * as mi from './mi'; - -export class CmsisBackend extends GDBBackend { - - public get isRunning(): boolean { - return !!this.out; - } - - public pause() { - mi.sendExecInterrupt(this); - return true; - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts deleted file mode 100644 index 5b9d3f77..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ /dev/null @@ -1,458 +0,0 @@ -/* -* 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 { normalize } from 'path'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { Logger, logger, InitializedEvent, OutputEvent, Scope, TerminatedEvent, ErrorDestination } from 'vscode-debugadapter'; -import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; -import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; -import { CmsisBackend } from './cmsis-backend'; -// import { PyocdServer } from './pyocd-server'; -import { PortScanner } from './port-scanner'; -import { SymbolTable } from './symbols'; -import { VarManager } from 'cdt-gdb-adapter/dist/varManager'; -import * as mi from './mi'; -import { OpenocdServer } from './openocd-server'; - -export interface CmsisRequestArguments extends RequestArguments { - runToMain?: boolean; - gdbServer?: string; - gdbServerArguments?: string[]; - gdbServerPort?: number; - objdump?: string; -} - -const GLOBAL_HANDLE_ID = 0xFE; -const STATIC_HANDLES_START = 0x010000; -const STATIC_HANDLES_FINISH = 0x01FFFF; - -export class CmsisDebugSession extends GDBDebugSession { - - protected readonly gdbServer = new OpenocdServer(); - protected readonly portScanner = new PortScanner(); - protected symbolTable!: SymbolTable; - protected globalHandle!: number; - protected varMgr: VarManager; - - constructor() { - super(); - this.addProcessListeners(); - } - - protected addProcessListeners(): void { - process.on('exit', () => { - if (this.gdbServer.isRunning) { - this.gdbServer.kill(); - } - }); - const signalHandler = () => { - if (this.gdbServer.isRunning) { - this.gdbServer.kill(); - } - process.exit(); - } - process.on('SIGINT', signalHandler); - process.on('SIGTERM', signalHandler); - process.on('SIGHUP', signalHandler); - } - - protected createBackend(): GDBBackend { - const gdb = new CmsisBackend(); - this.varMgr = new VarManager(gdb); - return gdb; - } - - protected async launchRequest(response: DebugProtocol.LaunchResponse, args: CmsisRequestArguments): Promise { - try { - await this.runSession(args); - this.sendResponse(response); - } catch (err) { - const message = `Failed to launch the debugger:\n${err.message}`; - this.sendErrorResponse(response, 1, message); - } - } - - protected async attachRequest(response: DebugProtocol.AttachResponse, args: CmsisRequestArguments): Promise { - try { - await this.runSession(args); - this.sendResponse(response); - } catch (err) { - const message = `Failed to attach the debugger:\n${err.message}`; - this.sendErrorResponse(response, 1, message); - } - } - - protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise { - try { - await mi.sendExecContinue(this.gdb); - this.sendResponse(response); - } catch (err) { - this.sendErrorResponse(response, 100, err.message); - } - } - - protected async pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): Promise { - try { - await mi.sendExecInterrupt(this.gdb, args.threadId); - this.sendResponse(response); - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - - protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): Promise { - try { - this.globalHandle = this.frameHandles.create({ - threadId: -1, - frameId: -1 - }); - - return super.stackTraceRequest(response, args); - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - - protected async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise { - await super.setBreakPointsRequest(response, args); - } - - protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { - try { - const frame: FrameVariableReference = { - type: 'frame', - frameHandle: args.frameId, - }; - // const pins: ObjectVariableReference = { - // type: "object", - // varobjName: "__pins", - // frameHandle: 42000, - // } - - response.body = { - scopes: [ - // new Scope('Pins', this.variableHandles.create(pins), false), - new Scope('Local', this.variableHandles.create(frame), false), - new Scope('Global', GLOBAL_HANDLE_ID, false), - new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false) - ], - }; - - this.sendResponse(response); - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - - protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { - try { - response.body = { - variables: new Array() - }; - - const ref = this.variableHandles.get(args.variablesReference); - - if (args.variablesReference === GLOBAL_HANDLE_ID) { - // Use hardcoded global handle to load and store global variables - response.body.variables = await this.getGlobalVariables(this.globalHandle); - } else if (args.variablesReference >= STATIC_HANDLES_START && args.variablesReference <= STATIC_HANDLES_FINISH) { - // Use STATIC_HANDLES_START to shift the framehandles back - const frameHandle = args.variablesReference - STATIC_HANDLES_START; - response.body.variables = await this.getStaticVariables(frameHandle); - } else if (ref && ref.type === 'frame') { - // List variables for current frame - response.body.variables = await this.handleVariableRequestFrame(ref); - } else if (ref && ref.varobjName === '__pins') { - response.body.variables = await this.handlePinStatusRequest(); - } else if (ref && ref.type === 'object') { - // List data under any variable - response.body.variables = await this.handleVariableRequestObject(ref); - } - - this.sendResponse(response); - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - - protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): Promise { - try { - if (args.context === 'repl') { - const command = args.expression; - const output = await mi.sendUserInput(this.gdb, command); - if (typeof output === 'undefined') { - response.body = { - result: '', - variablesReference: 0 - }; - } else { - response.body = { - result: JSON.stringify(output), - variablesReference: 0 - }; - } - - this.sendResponse(response); - } else { - return super.evaluateRequest(response, args); - } - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - - protected async disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): Promise { - try { - this.stopSession(); - this.sendResponse(response); - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - - private async runSession(args: CmsisRequestArguments): Promise { - logger.setup(args.verbose ? Logger.LogLevel.Verbose : Logger.LogLevel.Warn, args.logFile || false); - - this.gdb.on('consoleStreamOutput', (output, category) => this.sendEvent(new OutputEvent(output, category))); - this.gdb.on('execAsync', (resultClass, resultData) => this.handleGDBAsync(resultClass, resultData)); - this.gdb.on('notifyAsync', (resultClass, resultData) => this.handleGDBNotify(resultClass, resultData)); - - // gdb server has main info channel on stderr - this.gdbServer.on('stderr', data => this.sendEvent(new OutputEvent(data, 'stdout'))); - const gdbServerErrors: any[] = [] - const gdbServerErrorAccumulator = (message: any) => gdbServerErrors.push(message); - this.gdbServer.on('error', gdbServerErrorAccumulator); - - try { - this.symbolTable = new SymbolTable(args.program, args.objdump); - await this.symbolTable.loadSymbols(); - } catch (error) { - throw new Error(`Unable to load debug symbols: ${error.message}`); - } - - const port = await this.portScanner.findFreePort(); - if (!port) { - throw new Error('Unable to find a free port to use for debugging'); - } - this.sendEvent(new OutputEvent(`Selected port ${port} for debugging`)); - - const remote = `localhost:${port}`; - - // Set gdb arguments - if (!args.gdbArguments) { - args.gdbArguments = []; - } - args.gdbArguments.push('-q', args.program); - - // Set gdb server arguments - if (!args.gdbServerArguments) { - args.gdbServerArguments = []; - } - args.gdbServerPort = port; - - // Start gdb client and server - this.progressEvent(0, 'Starting Debugger'); - this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`)); - await this.gdbServer.spawn(args); - await this.spawn(args); - this.checkServerErrors(gdbServerErrors); - - // Send commands - await mi.sendTargetAsyncOn(this.gdb); - await mi.sendTargetSelectRemote(this.gdb, remote); - await mi.sendMonitorResetHalt(this.gdb); - this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`)); - this.checkServerErrors(gdbServerErrors); - - // Download image - const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image'); - progressListener(0); - this.gdbServer.on('progress', progressListener); - await mi.sendTargetDownload(this.gdb); - this.gdbServer.removeListener('progress', progressListener); - progressListener(100); - - // Halt after image download - await mi.sendMonitorResetHalt(this.gdb); - await this.gdb.sendEnablePrettyPrint(); - - if (args.runToMain === true) { - await mi.sendBreakOnFunction(this.gdb); - } - - this.checkServerErrors(gdbServerErrors); - this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`)); - this.sendEvent(new InitializedEvent()); - - this.gdbServer.removeListener('error', gdbServerErrorAccumulator); - this.gdbServer.on('error', message => { - logger.error(JSON.stringify(message)); - if (!this.gdbServer.serverErrorEmitted) { - this.sendEvent(new TerminatedEvent()); - } - }); - } - - private checkServerErrors(errors: any[]): void { - if (errors.length > 0) { - throw new Error(errors.join('\n')); - } - } - - protected spawn(args: CmsisRequestArguments): Promise { - const varRegexp = /\$\{.*\}/; - if (args.gdb && varRegexp.test(args.gdb)) { - throw new Error(`Unresolved variable: ${args.gdb}`) - } - return super.spawn(args); - } - - private async getGlobalVariables(frameHandle: number): Promise { - const frame = this.frameHandles.get(frameHandle); - const symbolInfo = this.symbolTable.getGlobalVariables(); - const variables: DebugProtocol.Variable[] = []; - - for (const symbol of symbolInfo) { - const name = `global_var_${symbol.name}`; - const variable = await this.getVariables(frame, name, symbol.name, -1); - variables.push(variable); - } - - return variables; - } - - private async handlePinStatusRequest(): Promise { - const variables: DebugProtocol.Variable[] = []; - variables.push({ - name: "D2", - type: "gpio", - value: "0x00", - variablesReference: 0 - }) - return variables; - } - - private async getStaticVariables(frameHandle: number): Promise { - const frame = this.frameHandles.get(frameHandle); - const result = await mi.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); - const file = normalize(result.frame.file || ''); - const symbolInfo = this.symbolTable.getStaticVariables(file); - const variables: DebugProtocol.Variable[] = []; - - // Fetch stack depth to obtain frameId/threadId/depth tuple - const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 }); - const depth = parseInt(stackDepth.depth, 10); - - for (const symbol of symbolInfo) { - const name = `${file}_static_var_${symbol.name}`; - const variable = await this.getVariables(frame, name, symbol.name, depth); - variables.push(variable); - } - - return variables; - } - - private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise { - let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name); - - if (global) { - // Update value if it is already loaded - const vup = await mi.sendVarUpdate(this.gdb, { name }); - const update = vup.changelist[0]; - if (update && update.in_scope === 'true' && update.name === global.varname) { - global.value = update.value; - } - } else { - // create var in GDB and store it in the varMgr - const varCreateResponse = await mi.sendVarCreate(this.gdb, { - name, - frame: 'current', - expression, - }); - - global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse); - } - - return { - name: expression, - value: (global.value === void 0) ? '' : global.value, - type: global.type, - variablesReference: parseInt(global.numchild, 10) > 0 - ? this.variableHandles.create({ - frameHandle: this.globalHandle, - type: 'object', - varobjName: global.varname, - }) - : 0, - }; - } - - private progressEvent(percent: number, message: string) { - this.sendEvent(new OutputEvent('progress', 'telemetry', { - percent, - message - })); - } - - protected sendErrorResponse(response: DebugProtocol.Response, - codeOrMessage: number | DebugProtocol.Message, format?: string, - variables?: any, dest?: ErrorDestination): void { - if (!!format && (dest === undefined || dest === ErrorDestination.User)) { - format = format.replace(/\n/g, '
'); - } - super.sendErrorResponse(response, codeOrMessage, format, variables, dest); - } - - protected async stopSession() { - // Pause debugging - if (this.isRunning) { - // Need to pause first - const waitPromise = new Promise(resolve => this.waitPaused = resolve); - this.gdb.pause(); - await waitPromise; - } - - // Detach - if ((this.gdb as CmsisBackend).isRunning) { - try { - await mi.sendTargetDetach(this.gdb); - } catch (e) { - // Need to catch here as the command result being returned will never exist as it's detached - } - } - - // Stop gdb client - try { - await this.gdb.sendGDBExit(); - } catch (e) { - // Need to catch here in case the connection has already been closed - } - } - - public async shutdown() { - await this.stopSession(); - super.shutdown(); - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/mi.ts b/arduino-debugger-extension/src/node/debug-adapter/mi.ts deleted file mode 100644 index d962e0d6..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/mi.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* -* 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 { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi'; -import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; - -export function sendTargetAsyncOn(gdb: GDBBackend) { - const set = 'target-async on'; - return gdb.sendGDBSet(set); -} - -export function sendMonitorResetHalt(gdb: GDBBackend) { - const command = '-interpreter-exec console "monitor reset halt"'; - return gdb.sendCommand(command); -} - -export function sendTargetSelectRemote(gdb: GDBBackend, remote: string) { - const command = `-target-select extended-remote ${remote}`; - return gdb.sendCommand(command); -} - -export function sendTargetDownload(gdb: GDBBackend) { - // const command = '-target-download'; - // return gdb.sendCommand(command); -} - -export function sendBreakOnFunction(gdb: GDBBackend, fn: string = 'main') { - const command = `-break-insert -t --function ${fn}`; - return gdb.sendCommand(command); -} - -export function sendExecInterrupt(gdb: GDBBackend, threadId?: number) { - let command = '-exec-interrupt'; - if (threadId) { - command += ` --thread ${threadId}`; - } - return gdb.sendCommand(command); -} - -export function sendStackInfoFrame(gdb: GDBBackend, threadId: number, frameId: number): Promise<{frame: MIFrameInfo}> { - const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`; - return gdb.sendCommand(command); -} - -export function sendUserInput(gdb: GDBBackend, command: string): Promise { - if (!command.startsWith('-')) { - command = `interpreter-exec console "${command}"`; - } - - return gdb.sendCommand(command); -} - -export function sendTargetDetach(gdb: GDBBackend) { - const command = '-target-detach'; - return gdb.sendCommand(command); -} - -export * from 'cdt-gdb-adapter/dist/mi'; diff --git a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts deleted file mode 100644 index e574ce98..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { AbstractServer } from './abstract-server'; -import { PortScanner } from './port-scanner'; -import { CmsisRequestArguments } from './cmsis-debug-session'; -import * as fs from 'fs-extra'; -import * as path from 'path'; -import * as os from 'os'; - -const LAUNCH_REGEX = /GDB server started/; -const ERROR_REGEX = /^error:/mi; -const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress - -export class OpenocdServer extends AbstractServer { - protected portScanner = new PortScanner(); - protected progress = 0; - - protected async resolveServerArguments(req: CmsisRequestArguments): Promise { - let sessionConfigFile = `gdb_port ${req.gdbServerPort!}${"\n"}`; - const telnetPort = await this.portScanner.findFreePort(4444); - if (!!telnetPort) { - sessionConfigFile += `telnet_port ${telnetPort}${"\n"}` - } - sessionConfigFile += `echo "GDB server started"${"\n"}` - - const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "arduino-debugger")); - const sessionCfgPath = path.join(tmpdir, "gdb.cfg"); - await fs.writeFile(sessionCfgPath, sessionConfigFile); - - let serverArguments = req.gdbServerArguments || []; - serverArguments.push("--file", sessionCfgPath); - - return serverArguments; - } - - protected onStdout(chunk: string | Buffer) { - super.onStdout(chunk); - const buffer = typeof chunk === 'string' ? chunk : chunk.toString('utf8'); - const match = buffer.match(/=/g); - - if (match) { - this.progress += match.length; - const percent = Math.round(this.progress * PERCENT_MULTIPLIER); - this.emit('progress', percent); - } - } - - protected serverStarted(data: string): boolean { - return LAUNCH_REGEX.test(data); - } - - protected serverError(data: string): boolean { - return ERROR_REGEX.test(data); - } - -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts b/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts deleted file mode 100644 index 8e811627..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* -* CMSIS Debug Adapter -* Copyright (c) 2016 Zoujie -* 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 { exec } from 'child_process'; - -const maxBuffer = 2 * 1024 * 1024; - -export class PortScanner { - - public async findFreePort(start: number = 50000, length: number = 100): Promise { - const fn = this.getFunction().bind(this); - - for (let i = start; i <= start + length; i++) { - try { - // Try to find pid of port - await fn(i); - } catch (_e) { - // Port is free when pid not found - return i; - } - } - - return undefined; - } - - private getFunction(): (port: number) => Promise { - switch (process.platform) { - case 'darwin': - case 'freebsd': - case 'sunos': { - return this.darwin; - break; - } - case 'linux': { - return this.linux; - break; - } - case 'win32': { - return this.windows; - break; - } - } - - return () => Promise.resolve(0); - } - - private async darwin(port: number): Promise { - const result = await this.execute('netstat -anv -p TCP && netstat -anv -p UDP'); - - // Replace header - const data = this.stripLine(result.toString(), 2); - - const found = this.extractColumns(data, [0, 3, 8], 10) - .filter(row => !!String(row[0]).match(/^(udp|tcp)/)) - .find(row => { - const matches = String(row[1]).match(/\.(\d+)$/); - return (matches && matches[1] === String(port)); - }); - - if (found && found[2].length) { - return parseInt(found[2], 10); - } - - throw new Error(`pid of port (${port}) not found`); - } - - private async linux(port: number): Promise { - // netstat -p ouputs warning if user is no-root - const result = await this.execute('netstat -tunlp'); - - // Replace header - const data = this.stripLine(result.toString(), 2); - - const columns = this.extractColumns(data, [3, 6], 7) - .find(column => { - const matches = String(column[0]).match(/:(\d+)$/); - return (matches && matches[1] === String(port)); - }); - - if (columns && columns[1]) { - const pid = columns[1].split('/', 1)[0]; - - if (pid.length) { - return parseInt(pid, 10); - } - } - - throw new Error(`pid of port (${port}) not found`); - } - - private async windows(port: number): Promise { - const result = await this.execute('netstat -ano'); - - // Replace header - const data = this.stripLine(result.toString(), 4); - - const columns = this.extractColumns(data, [1, 4], 5) - .find(column => { - const matches = String(column[0]).match(/:(\d+)$/); - return (matches && matches[1] === String(port)); - }); - - if (columns && columns[1].length && parseInt(columns[1], 10) > 0) { - return parseInt(columns[1], 10); - } - - throw new Error(`pid of port (${port}) not found`); - } - - private execute(cmd: string): Promise { - return new Promise((resolve, reject) => { - exec(cmd, { - maxBuffer, - windowsHide: true - }, (error: Error | null, stdout: string) => { - if (error) { - return reject(error); - } - - return resolve(stdout); - }); - }); - } - - private stripLine(text: string, num: number): string { - let idx = 0; - - while (num-- > 0) { - const nIdx = text.indexOf('\n', idx); - if (nIdx >= 0) { - idx = nIdx + 1; - } - } - - return idx > 0 ? text.substring(idx) : text; - } - - private extractColumns(text: string, idxes: number[], max: number): string[][] { - const lines = text.split(/(\r\n|\n|\r)/); - const columns: string[][] = []; - - if (!max) { - max = Math.max.apply(null, idxes) + 1; - } - - lines.forEach(line => { - const cols = this.split(line, max); - const column: string[] = []; - - idxes.forEach(idx => { - column.push(cols[idx] || ''); - }); - - columns.push(column); - }); - - return columns; - } - - private split(line: string, max: number): string[] { - const cols = line.trim().split(/\s+/); - - if (cols.length > max) { - cols[max - 1] = cols.slice(max - 1).join(' '); - } - - return cols; - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts deleted file mode 100644 index f9570fc4..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* -* 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 { AbstractServer } from './abstract-server'; -import { PortScanner } from './port-scanner'; -import { CmsisRequestArguments } from './cmsis-debug-session'; - -const LAUNCH_REGEX = /GDB server started/; -const ERROR_REGEX = /:ERROR:gdbserver:/; -const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress - -export class PyocdServer extends AbstractServer { - - protected portScanner = new PortScanner(); - protected progress = 0; - - protected async resolveServerArguments(req: CmsisRequestArguments): Promise { - let serverArguments = req.gdbServerArguments || []; - - serverArguments.push('--port', req.gdbServerPort!.toString()) - - const telnetPort = await this.portScanner.findFreePort(4444); - if (!!telnetPort) { - serverArguments.push('--telnet-port', telnetPort.toString()) - } - - return serverArguments; - } - - protected onStdout(chunk: string | Buffer) { - super.onStdout(chunk); - const buffer = typeof chunk === 'string' ? chunk : chunk.toString('utf8'); - const match = buffer.match(/=/g); - - if (match) { - this.progress += match.length; - const percent = Math.round(this.progress * PERCENT_MULTIPLIER); - this.emit('progress', percent); - } - } - - protected serverStarted(data: string): boolean { - return LAUNCH_REGEX.test(data); - } - - protected serverError(data: string): boolean { - return ERROR_REGEX.test(data); - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts deleted file mode 100644 index 244d9f52..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* -* CMSIS Debug Adapter -* Copyright (c) 2017-2019 Marcel Ball -* 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 { EOL } from 'os'; -import { normalize, basename } from 'path'; -import { spawnCommand } from 'arduino-ide-extension/lib/node/exec-util'; - -export enum SymbolType { - Function, - File, - Object, - Normal -} - -export enum SymbolScope { - Local, - Global, - Neither, - Both -} - -export interface SymbolInformation { - address: number; - length: number; - name: string; - section: string; - type: SymbolType; - scope: SymbolScope; - file?: string; - hidden: boolean; -} - -const SYMBOL_REGEX = /^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s([^\s]+)\s([0-9a-f]+)\s(.*)\r?$/; - -const TYPE_MAP: { [id: string]: SymbolType } = { - 'F': SymbolType.Function, - 'f': SymbolType.File, - 'O': SymbolType.Object, - ' ': SymbolType.Normal -}; - -const SCOPE_MAP: { [id: string]: SymbolScope } = { - 'l': SymbolScope.Local, - 'g': SymbolScope.Global, - ' ': SymbolScope.Neither, - '!': SymbolScope.Both -}; - -export class SymbolTable { - - private symbols: SymbolInformation[] = []; - - constructor(private program: string, private objdump?: string) { - const varRegexp = /\$\{.*\}/; - if (varRegexp.test(program)) { - throw new Error(`Unresolved variable: ${program}`); - } - if (objdump && varRegexp.test(objdump)) { - throw new Error(`Unresolved variable: ${objdump}`); - } - } - - public async loadSymbols(): Promise { - const results = await this.execute(); - const output = results.toString(); - const lines = output.split(EOL); - let currentFile: string | undefined; - - for (const line of lines) { - const match = line.match(SYMBOL_REGEX); - if (match) { - if (match[7] === 'd' && match[8] === 'f') { - currentFile = match[11].trim(); - } - const type = TYPE_MAP[match[8]]; - const scope = SCOPE_MAP[match[2]]; - let name = match[11].trim(); - let hidden = false; - - if (name.startsWith('.hidden')) { - name = name.substring(7).trim(); - hidden = true; - } - - this.symbols.push({ - address: parseInt(match[1], 16), - type: type, - scope: scope, - section: match[9].trim(), - length: parseInt(match[10], 16), - name: name, - file: scope === SymbolScope.Local ? currentFile : undefined, - hidden: hidden - }); - } - } - } - - public getGlobalVariables(): SymbolInformation[] { - const matches = this.symbols.filter(s => s.type === SymbolType.Object && s.scope === SymbolScope.Global); - return matches; - } - - public getStaticVariables(file: string): SymbolInformation[] { - return this.symbols.filter(s => - s.type === SymbolType.Object // Only load objects - && s.scope === SymbolScope.Local // Scoped to this file - && !s.name.startsWith('.') // Ignore names beginning with '.' - && (normalize(s.file || '') === normalize(file) || normalize(s.file || '') === basename(file))); // Match full path or file name - } - - private execute(): Promise { - if (!this.objdump) { - return Promise.reject(new Error('Missing parameter: objdump')); - } - return spawnCommand(this.objdump, ['--syms', this.program]); - } -} From 1bc996d8d8fd733a107a225283b370d0b456cc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 15:26:35 +0100 Subject: [PATCH 36/44] Updated to CLI version 0.9.0-rc2 --- .../debug-adapter/arduino-debug-session.ts | 2 +- .../debug-adapter/arduino-variable-handler.ts | 44 +++++++++---------- arduino-ide-extension/scripts/download-cli.js | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) 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 index ef69bf67..1b2f6683 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -91,7 +91,7 @@ export class ArduinoDebugSession extends GDBDebugSession { // new Scope('Pins', this.variableHandles.create(pins), false), new Scope('Local', this.variableHandles.create(frame), false), new Scope('Global', GLOBAL_HANDLE_ID, false), - new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false) + // new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false) ], }; diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts index c3705f95..db3c7590 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts @@ -44,6 +44,28 @@ export class ArduinoVariableHandler { return variables; } + /** TODO */ + async getStaticVariables(frameHandle: number): Promise { + throw new Error('Static variables are not supported yet.'); + const frame = this.frameHandles.get(frameHandle); + const result = await this.gdb.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); + const file = path.normalize(result.frame.file || ''); + const symbolInfo: any[] = [] // this.symbolTable.getStaticVariables(file); + const variables: DebugProtocol.Variable[] = []; + + // Fetch stack depth to obtain frameId/threadId/depth tuple + const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 }); + const depth = parseInt(stackDepth.depth, 10); + + for (const symbol of symbolInfo) { + const name = `${file}_static_var_${symbol.name}`; + const variable = await this.getVariables(frame, name, symbol.name, depth); + variables.push(variable); + } + + return variables; + } + private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise { let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name); @@ -79,28 +101,6 @@ export class ArduinoVariableHandler { }; } - /** TODO */ - async getStaticVariables(frameHandle: number): Promise { - throw new Error('Static variables are not supported yet.'); - const frame = this.frameHandles.get(frameHandle); - const result = await this.gdb.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); - const file = path.normalize(result.frame.file || ''); - const symbolInfo: any[] = [] // this.symbolTable.getStaticVariables(file); - const variables: DebugProtocol.Variable[] = []; - - // Fetch stack depth to obtain frameId/threadId/depth tuple - const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 }); - const depth = parseInt(stackDepth.depth, 10); - - for (const symbol of symbolInfo) { - const name = `${file}_static_var_${symbol.name}`; - const variable = await this.getVariables(frame, name, symbol.name, depth); - variables.push(variable); - } - - return variables; - } - async handlePinStatusRequest(): Promise { const variables: DebugProtocol.Variable[] = []; variables.push({ diff --git a/arduino-ide-extension/scripts/download-cli.js b/arduino-ide-extension/scripts/download-cli.js index 79ac7cfa..7009a077 100755 --- a/arduino-ide-extension/scripts/download-cli.js +++ b/arduino-ide-extension/scripts/download-cli.js @@ -10,7 +10,7 @@ (() => { - const DEFAULT_VERSION = '0.7.1'; // require('moment')().format('YYYYMMDD'); + const DEFAULT_VERSION = '0.9.0-rc2'; // require('moment')().format('YYYYMMDD'); const path = require('path'); const shell = require('shelljs'); From 24bd23b66938338639b008e73e3c1a3fbfbccca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 15:27:20 +0100 Subject: [PATCH 37/44] arduino/arduino-pro-ide#188: Open Boards and Library manager on first start --- .../src/browser/arduino-frontend-contribution.tsx | 8 ++++---- .../browser/boards/boards-widget-frontend-contribution.ts | 4 ++++ .../library/library-widget-frontend-contribution.ts | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 37f0997b..445bf759 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -455,13 +455,13 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut registry.registerSubmenu(ArduinoMenus.SKETCH, 'Sketch'); registry.registerMenuAction(ArduinoMenus.SKETCH, { - commandId: ArduinoCommands.VERIFY.id, - label: 'Verify/Compile', + commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, + label: 'Optimize for Debugging', order: '1' }); registry.registerMenuAction(ArduinoMenus.SKETCH, { - commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, - label: 'Optimize for Debugging', + commandId: ArduinoCommands.VERIFY.id, + label: 'Verify/Compile', order: '2' }); registry.registerMenuAction(ArduinoMenus.SKETCH, { diff --git a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts index d46b3f39..fa21e31d 100644 --- a/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/boards/boards-widget-frontend-contribution.ts @@ -23,6 +23,10 @@ export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendCont }); } + async initializeLayout(): Promise { + this.openView(); + } + registerMenus(menus: MenuModelRegistry): void { if (this.toggleCommand) { menus.registerMenuAction(ArduinoMenus.TOOLS, { diff --git a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts index 349f0185..ed02f095 100644 --- a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts @@ -14,15 +14,15 @@ export class LibraryListWidgetFrontendContribution extends AbstractViewContribut widgetName: LibraryListWidget.WIDGET_LABEL, defaultWidgetOptions: { area: 'left', - rank: 600 + rank: 700 }, toggleCommandId: `${LibraryListWidget.WIDGET_ID}:toggle`, toggleKeybinding: 'ctrlcmd+shift+l' }); } - initializeLayout(): void { - // NOOP + async initializeLayout(): Promise { + this.openView(); } registerMenus(menus: MenuModelRegistry): void { From 2d9fa5615b3d308f7ff8f85dd1da63cd2e842dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 15:50:46 +0100 Subject: [PATCH 38/44] Removed some unused stuff --- .vscode/arduino.json | 4 ---- arduino-ide-extension/src/node/boards-service-impl.ts | 10 ---------- 2 files changed, 14 deletions(-) delete mode 100644 .vscode/arduino.json diff --git a/.vscode/arduino.json b/.vscode/arduino.json deleted file mode 100644 index 513efb45..00000000 --- a/.vscode/arduino.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "board": "arduino:samd:arduino_zero_edbg", - "port": "/dev/cu.usbmodem141402" -} \ No newline at end of file diff --git a/arduino-ide-extension/src/node/boards-service-impl.ts b/arduino-ide-extension/src/node/boards-service-impl.ts index 8fcc8260..e668d13f 100644 --- a/arduino-ide-extension/src/node/boards-service-impl.ts +++ b/arduino-ide-extension/src/node/boards-service-impl.ts @@ -2,7 +2,6 @@ import * as PQueue from 'p-queue'; import { injectable, inject, postConstruct, named } from 'inversify'; import { ILogger } from '@theia/core/lib/common/logger'; import { Deferred } from '@theia/core/lib/common/promise-util'; -import { FileSystem } from '@theia/filesystem/lib/common'; import { BoardsService, AttachedSerialBoard, BoardPackage, Board, AttachedNetworkBoard, BoardsServiceClient, Port, BoardDetails, Tool @@ -15,7 +14,6 @@ import { CoreClientProvider } from './core-client-provider'; import { BoardListReq, BoardListResp, BoardDetailsReq, BoardDetailsResp } from './cli-protocol/commands/board_pb'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; import { Installable } from '../common/protocol/installable'; -import { ConfigService } from '../common/protocol/config-service'; @injectable() export class BoardsServiceImpl implements BoardsService { @@ -33,12 +31,6 @@ export class BoardsServiceImpl implements BoardsService { @inject(ToolOutputServiceServer) protected readonly toolOutputService: ToolOutputServiceServer; - @inject(ConfigService) - protected readonly configService: ConfigService; - - @inject(FileSystem) - protected readonly fileSystem: FileSystem; - protected discoveryInitialized = false; protected discoveryTimer: NodeJS.Timer | undefined; /** @@ -231,8 +223,6 @@ export class BoardsServiceImpl implements BoardsService { req.setFqbn(options.id); const resp = await new Promise((resolve, reject) => client.boardDetails(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp))); - - const tools = await Promise.all(resp.getRequiredToolsList().map(async t => { name: t.getName(), packager: t.getPackager(), From 3ed72de810b71095931160096324fbebea969ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Tue, 25 Feb 2020 14:08:08 +0100 Subject: [PATCH 39/44] Detect errors in spawned process --- .../debug-adapter/arduino-debug-session.ts | 9 --- .../node/debug-adapter/arduino-gdb-backend.ts | 8 ++- .../src/node/debug-adapter/arduino-parser.ts | 65 +++++++++++++++++++ 3 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts 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 index 1b2f6683..d6c67746 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -65,15 +65,6 @@ export class ArduinoDebugSession extends GDBDebugSession { } } - protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): Promise { - try { - - return super.stackTraceRequest(response, args); - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { try { const frame: FrameVariableReference = { 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 index a805522f..c791f1a0 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -4,9 +4,15 @@ import { spawn } from 'child_process'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; import { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi'; import { ArduinoLaunchRequestArguments } from './arduino-debug-session'; +import { ArduinoParser } from './arduino-parser'; export class ArduinoGDBBackend extends GDBBackend { + constructor() { + super(); + this.parser = new ArduinoParser(this); + } + spawn(requestArgs: ArduinoLaunchRequestArguments): Promise { if (!requestArgs.sketch) { throw new Error('Missing argument: sketch'); @@ -26,7 +32,7 @@ export class ArduinoGDBBackend extends GDBBackend { const proc = spawn(command, args); this.proc = proc; this.out = proc.stdin; - return this.parser.parse(proc.stdout); + return (this.parser as ArduinoParser).parseFull(proc); } sendFileExecAndSymbols(): Promise { diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts new file mode 100644 index 00000000..51507da5 --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts @@ -0,0 +1,65 @@ +import { ChildProcessWithoutNullStreams } from 'child_process'; +import { Readable } from 'stream'; +import { MIParser } from "cdt-gdb-adapter/dist/MIParser"; + +const READY_TIMEOUT = 7000; +const LINE_REGEX = /(.*)(\r?\n)/; + +export class ArduinoParser extends MIParser { + + parseFull(proc: ChildProcessWithoutNullStreams): Promise { + return new Promise((resolve, reject) => { + proc.on('error', reject); + let ready = false; + proc.on('exit', () => { + if (!ready) { + reject(new Error('The gdb debugger terminated unexpectedly.')); + } + }); + + const timeout = setTimeout(() => { + reject(new Error(`No response from gdb after ${READY_TIMEOUT} ms.`)); + }, READY_TIMEOUT); + this.waitReady = () => { + ready = true; + clearTimeout(timeout); + resolve(); + } + this.readInputStream(proc.stdout); + this.readErrorStream(proc.stderr, reject); + }); + } + + private readInputStream(stream: Readable) { + let buff = ''; + stream.on('data', chunk => { + buff += chunk.toString(); + let regexArray = LINE_REGEX.exec(buff); + while (regexArray) { + this.line = regexArray[1]; + this.pos = 0; + this.handleLine(); + buff = buff.substring(regexArray[1].length + regexArray[2].length); + regexArray = LINE_REGEX.exec(buff); + } + }); + } + + private readErrorStream(stream: Readable, reject: (reason?: any) => void) { + let buff = ''; + stream.on('data', chunk => { + buff += chunk.toString(); + let regexArray = LINE_REGEX.exec(buff); + while (regexArray) { + const line = regexArray[1]; + this.gdb.emit('consoleStreamOutput', line + '\n', 'stderr'); + if (line.toLowerCase().startsWith('error')) { + reject(new Error(line)); + } + buff = buff.substring(regexArray[1].length + regexArray[2].length); + regexArray = LINE_REGEX.exec(buff); + } + }); + } + +} From f6a8dceabc4e923bc009b36d5bc4da9c055f126e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Tue, 25 Feb 2020 15:43:21 +0100 Subject: [PATCH 40/44] Improved error detection to catch more cases --- .../arduino-debug-adapter-contribution.ts | 3 +- .../debug-adapter/arduino-debug-session.ts | 1 + .../node/debug-adapter/arduino-gdb-backend.ts | 4 +- .../src/node/debug-adapter/arduino-parser.ts | 46 +++++++++++++------ .../debug-adapter/arduino-variable-handler.ts | 2 +- 5 files changed, 38 insertions(+), 18 deletions(-) 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 92ddf504..9f6ee487 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -70,8 +70,7 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution fqbn: '${fqbn}', uploadPort: '${port}', initCommands: [ - `-break-insert -t --function ${startFunction}`, - '-interpreter-exec console "monitor reset halt"' + `-break-insert -t --function ${startFunction}` ] } if (!res.sketch) { 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 index d6c67746..77f85ff0 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -43,6 +43,7 @@ export class ArduinoDebugSession extends GDBDebugSession { protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise { try { + await this.gdb.sendCommand('-interpreter-exec console "monitor reset halt"') await mi.sendExecContinue(this.gdb); this.sendResponse(response); } catch (err) { 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 index c791f1a0..16cf5be6 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -48,9 +48,9 @@ export class ArduinoGDBBackend extends GDBBackend { return this.sendCommand(command); } - sendStackInfoFrame(gdb: GDBBackend, threadId: number, frameId: number): Promise<{ frame: MIFrameInfo }> { + sendStackInfoFrame(threadId: number, frameId: number): Promise<{ frame: MIFrameInfo }> { const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`; - return gdb.sendCommand(command); + return this.sendCommand(command); } sendTargetDetach(): Promise { diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts index 51507da5..3922367b 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts @@ -7,26 +7,35 @@ const LINE_REGEX = /(.*)(\r?\n)/; export class ArduinoParser extends MIParser { + protected rejectReady?: (error: Error) => void; + parseFull(proc: ChildProcessWithoutNullStreams): Promise { return new Promise((resolve, reject) => { + // Detect errors when the child process could not be spawned proc.on('error', reject); - let ready = false; - proc.on('exit', () => { - if (!ready) { - reject(new Error('The gdb debugger terminated unexpectedly.')); - } - }); - + // Detect hanging process (does not print command prompt or error) const timeout = setTimeout(() => { reject(new Error(`No response from gdb after ${READY_TIMEOUT} ms.`)); }, READY_TIMEOUT); + this.waitReady = () => { - ready = true; + this.rejectReady = undefined; clearTimeout(timeout); resolve(); } + this.rejectReady = (error: Error) => { + this.waitReady = undefined; + clearTimeout(timeout); + reject(error); + } + // Detect unexpected termination + proc.on('exit', () => { + if (this.rejectReady) { + this.rejectReady(new Error('The gdb debugger terminated unexpectedly.')); + } + }); this.readInputStream(proc.stdout); - this.readErrorStream(proc.stderr, reject); + this.readErrorStream(proc.stderr); }); } @@ -36,16 +45,25 @@ export class ArduinoParser extends MIParser { buff += chunk.toString(); let regexArray = LINE_REGEX.exec(buff); while (regexArray) { - this.line = regexArray[1]; + const line = regexArray[1]; + this.line = line; this.pos = 0; this.handleLine(); + // Detect error emitted as log message + if (line.startsWith('&"error') && this.rejectReady) { + this.line = line; + this.pos = 0; + this.next(); + this.rejectReady(new Error(this.handleCString() || regexArray[1])); + this.rejectReady = undefined; + } buff = buff.substring(regexArray[1].length + regexArray[2].length); regexArray = LINE_REGEX.exec(buff); } }); } - private readErrorStream(stream: Readable, reject: (reason?: any) => void) { + private readErrorStream(stream: Readable) { let buff = ''; stream.on('data', chunk => { buff += chunk.toString(); @@ -53,8 +71,10 @@ export class ArduinoParser extends MIParser { while (regexArray) { const line = regexArray[1]; this.gdb.emit('consoleStreamOutput', line + '\n', 'stderr'); - if (line.toLowerCase().startsWith('error')) { - reject(new Error(line)); + // Detect error emitted on the stderr stream + if (line.toLowerCase().startsWith('error') && this.rejectReady) { + this.rejectReady(new Error(line)); + this.rejectReady = undefined; } buff = buff.substring(regexArray[1].length + regexArray[2].length); regexArray = LINE_REGEX.exec(buff); diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts index db3c7590..e440c946 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts @@ -48,7 +48,7 @@ export class ArduinoVariableHandler { async getStaticVariables(frameHandle: number): Promise { throw new Error('Static variables are not supported yet.'); const frame = this.frameHandles.get(frameHandle); - const result = await this.gdb.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); + const result = await this.gdb.sendStackInfoFrame(frame.threadId, frame.frameId); const file = path.normalize(result.frame.file || ''); const symbolInfo: any[] = [] // this.symbolTable.getStaticVariables(file); const variables: DebugProtocol.Variable[] = []; From 69c7383da82bafc401e601123e6ea3e699f0b637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 26 Feb 2020 09:43:32 +0100 Subject: [PATCH 41/44] Update Electron main menu when a toggle button is switched --- .../browser/arduino-frontend-contribution.tsx | 10 ++++++++-- .../src/browser/editor-mode.ts | 3 +++ .../electron-arduino-menu-contribution.ts | 20 ++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 445bf759..4878840f 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -301,7 +301,10 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, { - execute: () => this.editorMode.toggleCompileForDebug(), + execute: () => { + this.editorMode.toggleCompileForDebug(); + this.editorMode.menuContentChanged.fire(); + }, isToggled: () => this.editorMode.compileForDebug }); @@ -416,7 +419,10 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, { - execute: () => this.editorMode.toggleProMode(), + execute: () => { + this.editorMode.toggleProMode(); + this.editorMode.menuContentChanged.fire(); + }, isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right', isToggled: () => this.editorMode.proMode }); diff --git a/arduino-ide-extension/src/browser/editor-mode.ts b/arduino-ide-extension/src/browser/editor-mode.ts index 857ee75b..71125d50 100644 --- a/arduino-ide-extension/src/browser/editor-mode.ts +++ b/arduino-ide-extension/src/browser/editor-mode.ts @@ -1,4 +1,5 @@ import { injectable } from 'inversify'; +import { Emitter } from '@theia/core/lib/common/event'; import { ApplicationShell, FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser'; import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer'; import { OutputWidget } from '@theia/output/lib/browser/output-widget'; @@ -7,6 +8,8 @@ import { EditorWidget } from '@theia/editor/lib/browser'; @injectable() export class EditorMode implements FrontendApplicationContribution { + readonly menuContentChanged = new Emitter(); + protected app: FrontendApplication; onStart(app: FrontendApplication): void { diff --git a/arduino-ide-extension/src/electron-browser/electron-arduino-menu-contribution.ts b/arduino-ide-extension/src/electron-browser/electron-arduino-menu-contribution.ts index 4fce50ef..4559c6d7 100644 --- a/arduino-ide-extension/src/electron-browser/electron-arduino-menu-contribution.ts +++ b/arduino-ide-extension/src/electron-browser/electron-arduino-menu-contribution.ts @@ -1,9 +1,27 @@ -import { injectable } from 'inversify'; +import * as electron from 'electron'; +import { injectable, inject, postConstruct } from 'inversify'; +import { isOSX } from '@theia/core/lib/common/os'; import { ElectronMenuContribution } from '@theia/core/lib/electron-browser/menu/electron-menu-contribution'; +import { EditorMode } from '../browser/editor-mode'; @injectable() export class ElectronArduinoMenuContribution extends ElectronMenuContribution { + @inject(EditorMode) + protected readonly editorMode: EditorMode; + + @postConstruct() + protected init(): void { + this.editorMode.menuContentChanged.event(() => { + const createdMenuBar = this.factory.createMenuBar(); + if (isOSX) { + electron.remote.Menu.setApplicationMenu(createdMenuBar); + } else { + electron.remote.getCurrentWindow().setMenu(createdMenuBar); + } + }); + } + protected hideTopPanel(): void { // NOOP // We reuse the `div` for the Arduino toolbar. From d56962251e04809ce0ade32524229461b24ab549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 26 Feb 2020 10:55:03 +0100 Subject: [PATCH 42/44] Fixed: Commands that are registered to toolbar did not show up in Electron menu --- .../src/browser/arduino-commands.ts | 9 + .../browser/arduino-frontend-contribution.tsx | 183 +++++++++--------- .../src/browser/editor-mode.ts | 17 +- .../library-widget-frontend-contribution.ts | 2 +- .../monitor/monitor-view-contribution.tsx | 20 +- 5 files changed, 132 insertions(+), 99 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-commands.ts b/arduino-ide-extension/src/browser/arduino-commands.ts index 34026f34..94376fe5 100644 --- a/arduino-ide-extension/src/browser/arduino-commands.ts +++ b/arduino-ide-extension/src/browser/arduino-commands.ts @@ -6,11 +6,17 @@ export namespace ArduinoCommands { id: 'arduino-verify', label: 'Verify Sketch' } + export const VERIFY_TOOLBAR: Command = { + id: 'arduino-verify-toolbar', + } export const UPLOAD: Command = { id: 'arduino-upload', label: 'Upload Sketch' } + export const UPLOAD_TOOLBAR: Command = { + id: 'arduino-upload-toolbar', + } export const TOGGLE_COMPILE_FOR_DEBUG: Command = { id: "arduino-toggle-compile-for-debug" @@ -46,4 +52,7 @@ export namespace ArduinoCommands { export const TOGGLE_ADVANCED_MODE: Command = { id: "arduino-toggle-advanced-mode" } + export const TOGGLE_ADVANCED_MODE_TOOLBAR: Command = { + id: "arduino-toggle-advanced-mode-toolbar" + } } diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 4878840f..299d4cfb 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -181,12 +181,12 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut registerToolbarItems(registry: TabBarToolbarRegistry): void { registry.registerItem({ id: ArduinoCommands.VERIFY.id, - command: ArduinoCommands.VERIFY.id, + command: ArduinoCommands.VERIFY_TOOLBAR.id, tooltip: 'Verify' }); registry.registerItem({ id: ArduinoCommands.UPLOAD.id, - command: ArduinoCommands.UPLOAD.id, + command: ArduinoCommands.UPLOAD_TOOLBAR.id, tooltip: 'Upload' }); registry.registerItem({ @@ -213,17 +213,15 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerItem({ id: 'toggle-serial-monitor', - command: MonitorViewContribution.OPEN_SERIAL_MONITOR, - tooltip: 'Toggle Serial Monitor', - isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right' + command: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR, + tooltip: 'Toggle Serial Monitor' }); registry.registerItem({ id: ArduinoCommands.TOGGLE_ADVANCED_MODE.id, - command: ArduinoCommands.TOGGLE_ADVANCED_MODE.id, + command: ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR.id, tooltip: 'Toggle Advanced Mode', - text: (this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)'), - isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right' + text: (this.editorMode.proMode ? '$(toggle-on)' : '$(toggle-off)') }); } @@ -266,38 +264,11 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut } registry.registerCommand(ArduinoCommands.VERIFY, { + execute: this.verify.bind(this) + }); + registry.registerCommand(ArduinoCommands.VERIFY_TOOLBAR, { isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', - isEnabled: widget => true, - execute: async () => { - const widget = this.getCurrentWidget(); - if (widget instanceof EditorWidget) { - await widget.saveable.save(); - } - - const uri = this.toUri(widget); - if (!uri) { - return; - } - - try { - const { boardsConfig } = this.boardsServiceClient; - if (!boardsConfig || !boardsConfig.selectedBoard) { - throw new Error('No boards selected. Please select a board.'); - } - if (!boardsConfig.selectedBoard.fqbn) { - throw new Error(`No core is installed for ${boardsConfig.selectedBoard.name}. Please install the board.`); - } - // Reveal the Output view asynchronously (don't await it) - this.outputContribution.openView({ reveal: true }); - await this.coreService.compile({ - uri: uri.toString(), - board: boardsConfig.selectedBoard, - optimizeForDebug: this.editorMode.compileForDebug - }); - } catch (e) { - await this.messageService.error(e.toString()); - } - } + execute: this.verify.bind(this) }); registry.registerCommand(ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG, { @@ -309,54 +280,15 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerCommand(ArduinoCommands.UPLOAD, { + execute: this.upload.bind(this) + }); + registry.registerCommand(ArduinoCommands.UPLOAD_TOOLBAR, { isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', - isEnabled: widget => true, - execute: async () => { - const widget = this.getCurrentWidget(); - if (widget instanceof EditorWidget) { - await widget.saveable.save(); - } - - const uri = this.toUri(widget); - if (!uri) { - return; - } - - const monitorConfig = this.monitorConnection.monitorConfig; - if (monitorConfig) { - await this.monitorConnection.disconnect(); - } - - try { - const { boardsConfig } = this.boardsServiceClient; - if (!boardsConfig || !boardsConfig.selectedBoard) { - throw new Error('No boards selected. Please select a board.'); - } - const { selectedPort } = boardsConfig; - if (!selectedPort) { - throw new Error('No ports selected. Please select a port.'); - } - // Reveal the Output view asynchronously (don't await it) - this.outputContribution.openView({ reveal: true }); - await this.coreService.upload({ - uri: uri.toString(), - board: boardsConfig.selectedBoard, - port: selectedPort.address, - optimizeForDebug: this.editorMode.compileForDebug - }); - } catch (e) { - await this.messageService.error(e.toString()); - } finally { - if (monitorConfig) { - await this.monitorConnection.connect(monitorConfig); - } - } - } + execute: this.upload.bind(this) }); registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, { isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', - isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left', execute: async (widget: Widget, target: EventTarget) => { if (this.wsSketchCount) { const el = (target as HTMLElement).parentElement; @@ -385,7 +317,6 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerCommand(ArduinoCommands.SAVE_SKETCH, { - isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left', isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', execute: async (sketch: Sketch) => { registry.executeCommand(CommonCommands.SAVE_ALL.id); @@ -419,13 +350,87 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE, { - execute: () => { - this.editorMode.toggleProMode(); - this.editorMode.menuContentChanged.fire(); - }, - isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right', - isToggled: () => this.editorMode.proMode + isToggled: () => this.editorMode.proMode, + execute: () => this.editorMode.toggleProMode() }); + registry.registerCommand(ArduinoCommands.TOGGLE_ADVANCED_MODE_TOOLBAR, { + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right', + isToggled: () => this.editorMode.proMode, + execute: () => this.editorMode.toggleProMode() + }); + } + + protected async verify() { + const widget = this.getCurrentWidget(); + if (widget instanceof EditorWidget) { + await widget.saveable.save(); + } + + const uri = this.toUri(widget); + if (!uri) { + return; + } + + try { + const { boardsConfig } = this.boardsServiceClient; + if (!boardsConfig || !boardsConfig.selectedBoard) { + throw new Error('No boards selected. Please select a board.'); + } + if (!boardsConfig.selectedBoard.fqbn) { + throw new Error(`No core is installed for ${boardsConfig.selectedBoard.name}. Please install the board.`); + } + // Reveal the Output view asynchronously (don't await it) + this.outputContribution.openView({ reveal: true }); + await this.coreService.compile({ + uri: uri.toString(), + board: boardsConfig.selectedBoard, + optimizeForDebug: this.editorMode.compileForDebug + }); + } catch (e) { + await this.messageService.error(e.toString()); + } + } + + protected async upload() { + const widget = this.getCurrentWidget(); + if (widget instanceof EditorWidget) { + await widget.saveable.save(); + } + + const uri = this.toUri(widget); + if (!uri) { + return; + } + + const monitorConfig = this.monitorConnection.monitorConfig; + if (monitorConfig) { + await this.monitorConnection.disconnect(); + } + + try { + const { boardsConfig } = this.boardsServiceClient; + if (!boardsConfig || !boardsConfig.selectedBoard) { + throw new Error('No boards selected. Please select a board.'); + } + const { selectedPort } = boardsConfig; + if (!selectedPort) { + throw new Error('No ports selected. Please select a port.'); + } + // Reveal the Output view asynchronously (don't await it) + this.outputContribution.openView({ reveal: true }); + await this.coreService.upload({ + uri: uri.toString(), + board: boardsConfig.selectedBoard, + port: selectedPort.address, + optimizeForDebug: this.editorMode.compileForDebug + }); + } catch (e) { + await this.messageService.error(e.toString()); + } finally { + if (monitorConfig) { + await this.monitorConnection.connect(monitorConfig); + } + } } registerMenus(registry: MenuModelRegistry) { diff --git a/arduino-ide-extension/src/browser/editor-mode.ts b/arduino-ide-extension/src/browser/editor-mode.ts index 71125d50..c697edd4 100644 --- a/arduino-ide-extension/src/browser/editor-mode.ts +++ b/arduino-ide-extension/src/browser/editor-mode.ts @@ -1,9 +1,11 @@ import { injectable } from 'inversify'; import { Emitter } from '@theia/core/lib/common/event'; -import { ApplicationShell, FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser'; -import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer'; +import { ApplicationShell, FrontendApplicationContribution, FrontendApplication, Widget } from '@theia/core/lib/browser'; import { OutputWidget } from '@theia/output/lib/browser/output-widget'; import { EditorWidget } from '@theia/editor/lib/browser'; +import { ArduinoShellLayoutRestorer } from './shell/arduino-shell-layout-restorer'; +import { BoardsListWidget } from './boards/boards-list-widget'; +import { LibraryListWidget } from './library/library-list-widget'; @injectable() export class EditorMode implements FrontendApplicationContribution { @@ -31,9 +33,9 @@ export class EditorMode implements FrontendApplicationContribution { window.localStorage.setItem(EditorMode.PRO_MODE_KEY, String(inAdvancedMode)); if (!inAdvancedMode) { const { shell } = this.app; - // Close all widget that is neither editor nor `Output`. + // Close all widgets that are neither editor nor `Output` / `Boards Manager` / `Library Manager`. for (const area of ['left', 'right', 'bottom', 'main'] as Array) { - shell.closeTabs(area, ({ owner }) => !(owner instanceof EditorWidget || owner instanceof OutputWidget)); + shell.closeTabs(area, title => !this.isInSimpleMode(title.owner)); } } // `storeLayout` has a sync API but the implementation is async, we store the layout manually before we reload the page. @@ -44,6 +46,13 @@ export class EditorMode implements FrontendApplicationContribution { window.location.reload(true); } + protected isInSimpleMode(widget: Widget): boolean { + return widget instanceof EditorWidget + || widget instanceof OutputWidget + || widget instanceof BoardsListWidget + || widget instanceof LibraryListWidget; + } + get compileForDebug(): boolean { const value = window.localStorage.getItem(EditorMode.COMPILE_FOR_DEBUG_KEY); return value === 'true'; diff --git a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts index ed02f095..ed165c7a 100644 --- a/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/library/library-widget-frontend-contribution.ts @@ -27,7 +27,7 @@ export class LibraryListWidgetFrontendContribution extends AbstractViewContribut registerMenus(menus: MenuModelRegistry): void { if (this.toggleCommand) { - menus.registerMenuAction(ArduinoMenus.SKETCH, { + menus.registerMenuAction(ArduinoMenus.TOOLS, { commandId: this.toggleCommand.id, label: 'Manage Libraries...' }); diff --git a/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx index 821de29f..343cb796 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx @@ -5,8 +5,8 @@ import { MonitorWidget } from "./monitor-widget"; import { MenuModelRegistry, Command, CommandRegistry } from "@theia/core"; import { ArduinoMenus } from "../arduino-frontend-contribution"; import { TabBarToolbarContribution, TabBarToolbarRegistry } from "@theia/core/lib/browser/shell/tab-bar-toolbar"; -import { MonitorModel } from './monitor-model'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; +import { MonitorModel } from './monitor-model'; export namespace SerialMonitor { export namespace Commands { @@ -29,7 +29,8 @@ export namespace SerialMonitor { @injectable() export class MonitorViewContribution extends AbstractViewContribution implements TabBarToolbarContribution { - static readonly OPEN_SERIAL_MONITOR = MonitorWidget.ID + ':toggle'; + static readonly TOGGLE_SERIAL_MONITOR = MonitorWidget.ID + ':toggle'; + static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR = MonitorWidget.ID + ':toggle-toolbar'; @inject(MonitorModel) protected readonly model: MonitorModel; @@ -40,7 +41,7 @@ export class MonitorViewContribution extends AbstractViewContribution this.openView({ toggle: true, activate: true - }), - isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right' + }) + }); + const toolbarCmd = { + id: MonitorViewContribution.TOGGLE_SERIAL_MONITOR_TOOLBAR + } + commands.registerCommand(toolbarCmd, { + isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right', + execute: () => this.openView({ + toggle: true, + activate: true + }) }); } } From a6cef7c605abb724e4fbc568105c94b440926259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 26 Feb 2020 13:55:12 +0100 Subject: [PATCH 43/44] Detect even more error cases --- .../src/node/debug-adapter/arduino-parser.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts index 3922367b..8f29b525 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts @@ -50,10 +50,8 @@ export class ArduinoParser extends MIParser { this.pos = 0; this.handleLine(); // Detect error emitted as log message - if (line.startsWith('&"error') && this.rejectReady) { - this.line = line; - this.pos = 0; - this.next(); + if (this.rejectReady && line.toLowerCase().startsWith('&"error')) { + this.pos = 1; this.rejectReady(new Error(this.handleCString() || regexArray[1])); this.rejectReady = undefined; } @@ -72,7 +70,7 @@ export class ArduinoParser extends MIParser { const line = regexArray[1]; this.gdb.emit('consoleStreamOutput', line + '\n', 'stderr'); // Detect error emitted on the stderr stream - if (line.toLowerCase().startsWith('error') && this.rejectReady) { + if (this.rejectReady && line.toLowerCase().startsWith('error')) { this.rejectReady(new Error(line)); this.rejectReady = undefined; } From b055bd9e414b277c738353ee4e3b0137980a13e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Wed, 26 Feb 2020 16:12:02 +0100 Subject: [PATCH 44/44] Workaround for Windows: We cannot use SIGINT to interrupt gdb, so kill the process on disconnect --- .../debug-adapter/arduino-debug-session.ts | 18 +++++++++++++++++- .../node/debug-adapter/arduino-gdb-backend.ts | 11 +++++++++++ .../src/node/debug-adapter/arduino-parser.ts | 7 ------- 3 files changed, 28 insertions(+), 8 deletions(-) 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 index 77f85ff0..87eba74a 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -4,7 +4,7 @@ import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; import * as mi from 'cdt-gdb-adapter/dist/mi'; import { ArduinoGDBBackend } from './arduino-gdb-backend'; import { ArduinoVariableHandler } from './arduino-variable-handler'; -import { Scope } from 'vscode-debugadapter'; +import { Scope, OutputEvent } from 'vscode-debugadapter'; export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { arduinoCli?: string; @@ -51,9 +51,25 @@ export class ArduinoDebugSession extends GDBDebugSession { } } + protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): Promise { + if (process.platform === 'win32') { + const message = 'Pause is not supported on Windows. Please stop the debug session and set a breakpoint instead.'; + this.sendEvent(new OutputEvent(message)); + this.sendErrorResponse(response, 1, message); + return Promise.resolve(); + } + return super.pauseRequest(response, args); + } + protected async disconnectRequest(response: DebugProtocol.DisconnectResponse): Promise { try { if (this.isRunning) { + if (process.platform === 'win32') { + // We cannot pause on Windows + this.arduinoBackend.kill(); + this.sendResponse(response); + return; + } // Need to pause first const waitPromise = new Promise(resolve => this.waitPaused = resolve); this.gdb.pause(); 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 index 16cf5be6..b1dd3fa5 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -57,4 +57,15 @@ export class ArduinoGDBBackend extends GDBBackend { return this.sendCommand('-target-detach'); } + kill(): void { + if (!this.proc) { + return; + } + if (process.platform === 'win32') { + spawn('taskkill', ['/pid', this.proc.pid.toString(), '/f', '/t']); + } else { + this.proc.kill('SIGKILL'); + } + } + } diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts index 8f29b525..07767baf 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-parser.ts @@ -2,7 +2,6 @@ import { ChildProcessWithoutNullStreams } from 'child_process'; import { Readable } from 'stream'; import { MIParser } from "cdt-gdb-adapter/dist/MIParser"; -const READY_TIMEOUT = 7000; const LINE_REGEX = /(.*)(\r?\n)/; export class ArduinoParser extends MIParser { @@ -13,19 +12,13 @@ export class ArduinoParser extends MIParser { return new Promise((resolve, reject) => { // Detect errors when the child process could not be spawned proc.on('error', reject); - // Detect hanging process (does not print command prompt or error) - const timeout = setTimeout(() => { - reject(new Error(`No response from gdb after ${READY_TIMEOUT} ms.`)); - }, READY_TIMEOUT); this.waitReady = () => { this.rejectReady = undefined; - clearTimeout(timeout); resolve(); } this.rejectReady = (error: Error) => { this.waitReady = undefined; - clearTimeout(timeout); reject(error); } // Detect unexpected termination