From 19ef57de0344002a837a9ed3ab86ebe0541b2d78 Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Sat, 19 Feb 2022 17:05:08 -0600 Subject: [PATCH 1/8] Add cli burn bootloader command --- README.md | 1 + package.json | 4 + src/arduino/arduino.ts | 196 +++++++++++++++++++++++++++++++++++------ src/extension.ts | 29 ++++-- 4 files changed, 196 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 558479f4..ebfdb365 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ This extension provides several commands in the Command Palette (F1 o - **Arduino: CLI Upload**: Upload complied code without building sketch (CLI only). - **Arduino: Upload Using Programmer**: Upload using an external programmer. - **Arduino: CLI Upload Using Programmer**: Upload using an external programmer without building sketch (CLI only). +- **Arduino: CLI Burn Bootloader**: Burn bootloader using external programmer (CLI Only). - **Arduino: Verify**: Build sketch. - **Arduino: Rebuild IntelliSense Configuration**: Forced/manual rebuild of the IntelliSense configuration. The extension analyzes Arduino's build output and sets the IntelliSense include paths, defines, compiler arguments accordingly. diff --git a/package.json b/package.json index 2db00527..39e588f8 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,10 @@ "command": "arduino.cliUploadUsingProgrammer", "title": "Arduino CLI: Upload Using Programmer" }, + { + "command": "arduino.cliBurnBootloader", + "title": "Arduino CLI: Burn Bootloader" + }, { "command": "arduino.rebuildIntelliSenseConfig", "title": "Arduino: Rebuild IntelliSense Configuration" diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 9f7dabfe..3b83b488 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -39,6 +39,13 @@ export enum BuildMode { CliUpload = "Uploading using Arduino CLI", UploadProgrammer = "Uploading (programmer)", CliUploadProgrammer = "Uploading (programmer) using Arduino CLI", + CliBurnBootloader = "Burning Bootloader using Arduino CLI" +} + +export enum ArduinoState { + Idle, + Building, + BurningBootloader } /** @@ -64,10 +71,10 @@ export class ArduinoApp { private _analysisManager: AnalysisManager; /** - * Indicates if a build is currently in progress. - * If so any call to this.build() will return false immediately. + * Indicates if a build or bootloader burn is currently in progress. + * If so any call to this.build() or this.burnBootloader() will return false immediately. */ - private _building: boolean = false; + private _state: ArduinoState = ArduinoState.Idle; /** * @param {IArduinoSettings} _settings ArduinoSetting object. @@ -75,7 +82,7 @@ export class ArduinoApp { constructor(private _settings: IArduinoSettings) { const analysisDelayMs = 1000 * 3; this._analysisManager = new AnalysisManager( - () => this._building, + () => this._state === ArduinoState.Idle, async () => { await this.build(BuildMode.Analyze); }, analysisDelayMs); } @@ -146,10 +153,10 @@ export class ArduinoApp { } /** - * Returns true if a build is currently in progress. + * Returns true if a build or bootloader burn is currently in progress. */ - public get building() { - return this._building; + public get state() { + return this._state; } /** @@ -175,19 +182,19 @@ export class ArduinoApp { */ public async build(buildMode: BuildMode, buildDir?: string) { - if (!this._boardManager || !this._programmerManager || this._building) { + if (!this._boardManager || !this._programmerManager || this._state) { return false; } - this._building = true; + this._state = ArduinoState.Building; return await this._build(buildMode, buildDir) .then((ret) => { - this._building = false; + this._state = ArduinoState.Idle; return ret; }) .catch((reason) => { - this._building = false; + this._state = ArduinoState.Idle; logger.notifyUserError("ArduinoApp.build", reason, `Unhandled exception when cleaning up build "${buildMode}": ${JSON.stringify(reason)}`); @@ -195,6 +202,25 @@ export class ArduinoApp { }); } + public async burnBootloader() { + if (!this._boardManager || !this.programmerManager || this._state) { + return false; + } + + this._state = ArduinoState.BurningBootloader; + return await this._burnBootloader(). + then((ret) => { + this._state = ArduinoState.Idle; + }) + .catch((reason) => { + this._state = ArduinoState.Idle; + logger.notifyUserError("ArduinoApp.burnBootloader", + reason, + `Unhandled exception burning bootloader: ${JSON.stringify(reason)}`); + return false; + }); + } + // Include the *.h header files from selected library to the arduino sketch. public async includeLibrary(libraryPath: string) { if (!ArduinoWorkspace.rootPath) { @@ -509,9 +535,22 @@ export class ArduinoApp { return line.startsWith("Sketch uses ") || line.startsWith("Global variables use "); } + /** + * Triggers serial selection prompt. Used in build and burnBootloader + * processes if no serial port selected already. + */ + private async _selectSerial(): Promise { + const choice = await vscode.window.showInformationMessage( + "Serial port is not specified. Do you want to select a serial port for uploading?", + "Yes", "No"); + if (choice === "Yes") { + vscode.commands.executeCommand("arduino.selectSerialPort"); + } + } + /** * Private implementation. Not to be called directly. The wrapper build() - * manages the build state. + * manages the busy state. * @param buildMode See build() * @param buildDir See build() * @see https://github.com/arduino/Arduino/blob/master/build/shared/manpage.adoc @@ -552,18 +591,9 @@ export class ArduinoApp { } } - const selectSerial = async () => { - const choice = await vscode.window.showInformationMessage( - "Serial port is not specified. Do you want to select a serial port for uploading?", - "Yes", "No"); - if (choice === "Yes") { - vscode.commands.executeCommand("arduino.selectSerialPort"); - } - } - if (buildMode === BuildMode.Upload) { if ((!dc.configuration || !/upload_method=[^=,]*st[^,]*link/i.test(dc.configuration)) && !dc.port) { - await selectSerial(); + await this._selectSerial(); return false; } @@ -578,7 +608,7 @@ export class ArduinoApp { } } else if (buildMode === BuildMode.CliUpload) { if ((!dc.configuration || !/upload_method=[^=,]*st[^,]*link/i.test(dc.configuration)) && !dc.port) { - await selectSerial(); + await this._selectSerial(); return false; } @@ -599,7 +629,7 @@ export class ArduinoApp { return false; } if (!dc.port) { - await selectSerial(); + await this._selectSerial(); return false; } @@ -621,7 +651,7 @@ export class ArduinoApp { return false; } if (!dc.port) { - await selectSerial(); + await this._selectSerial(); return false; } if (!this.useArduinoCli()) { @@ -663,7 +693,7 @@ export class ArduinoApp { await vscode.workspace.saveAll(false); // we prepare the channel here since all following code will - // or at leas can possibly output to it + // or at least can possibly output to it arduinoChannel.show(); if (VscodeSettings.getInstance().clearOutputOnBuild) { arduinoChannel.clear(); @@ -804,4 +834,118 @@ export class ArduinoApp { return false; }); } + + /** + * Private implementation. Not to be called directly. The wrapper burnBootloader() + * manages the busy state. + * @see https://arduino.github.io/arduino-cli/ + * @see https://github.com/arduino/Arduino/issues/11765 + * @remarks Currently this is only supported by `arduino-cli`. A request has been + * made with the Arduino repo. + */ + private async _burnBootloader(): Promise { + const dc = DeviceContext.getInstance(); + const args: string[] = []; + let restoreSerialMonitor: boolean = false; + const verbose = VscodeSettings.getInstance().logLevel === constants.LogLevel.Verbose; + + if (!this.boardManager.currentBoard) { + logger.notifyUserError("boardManager.currentBoard", new Error(constants.messages.NO_BOARD_SELECTED)); + return false; + } + const boardDescriptor = this.boardManager.currentBoard.getBuildConfig(); + + if (this.useArduinoCli()) { + args.push("burn-bootloader", + "-b", boardDescriptor); + } else { + arduinoChannel.error("This command is only available when using the Arduino CLI"); + return false; + } + + if (!dc.port) { + await this._selectSerial(); + return false; + } + args.push("--port", dc.port); + + const programmer = this.programmerManager.currentProgrammer; + if (!programmer) { + logger.notifyUserError("programmerManager.currentProgrammer", new Error(constants.messages.NO_PROGRAMMMER_SELECTED)); + return false; + } + args.push('--programmer', programmer); + + // We always build verbosely but filter the output based on the settings + args.push("--verbose"); + + // we prepare the channel here since all following code will + // or at least can possibly output to it + arduinoChannel.show(); + if (VscodeSettings.getInstance().clearOutputOnBuild) { + arduinoChannel.clear(); + } + arduinoChannel.start(`Burning booloader for ${boardDescriptor} using programmer ${programmer}'`); + + restoreSerialMonitor = await SerialMonitor.getInstance().closeSerialMonitor(dc.port); + UsbDetector.getInstance().pauseListening(); + + const cleanup = async () => { + UsbDetector.getInstance().resumeListening(); + if (restoreSerialMonitor) { + await SerialMonitor.getInstance().openSerialMonitor(); + } + } + + const stdoutcb = (line: string) => { + if (verbose) { + arduinoChannel.channel.append(line); + } + } + + const stderrcb = (line: string) => { + if (os.platform() === "win32") { + line = line.trim(); + if (line.length <= 0) { + return; + } + line = line.replace(/(?:\r|\r\n|\n)+/g, os.EOL); + line = `${line}${os.EOL}`; + } + if (!verbose) { + // Don't spill log with spurious info from the backend. This + // list could be fetched from a config file to accommodate + // messages of unknown board packages, newer backend revisions + const filters = [ + /^Picked\sup\sJAVA_TOOL_OPTIONS:\s+/, + /^\d+\d+-\d+-\d+T\d+:\d+:\d+.\d+Z\s(?:INFO|WARN)\s/, + /^(?:DEBUG|TRACE|INFO)\s+/, + ]; + for (const f of filters) { + if (line.match(f)) { + return; + } + } + } + arduinoChannel.channel.append(line); + } + + return await util.spawn( + this._settings.commandPath, + args, + { cwd: ArduinoWorkspace.rootPath }, + { /*channel: arduinoChannel.channel,*/ stdout: stdoutcb, stderr: stderrcb }, + ).then(async () => { + await cleanup(); + arduinoChannel.end(`Burning booloader for ${boardDescriptor} using programmer ${programmer}'`); + return true; + }, async (reason) => { + await cleanup(); + const msg = reason.code + ? `Exit with code=${reason.code}` + : JSON.stringify(reason); + arduinoChannel.error(`Burning booloader for ${boardDescriptor} using programmer ${programmer}': ${msg}${os.EOL}`); + return false; + }); + } } diff --git a/src/extension.ts b/src/extension.ts index fdcf1ba9..aff171d0 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -27,7 +27,7 @@ const completionProviderModule = impor("./langService/completionProvider") as ty import * as Logger from "./logger/logger"; const nsatModule = impor("./nsat") as typeof import ("./nsat"); -import { BuildMode } from "./arduino/arduino"; +import { ArduinoState, BuildMode } from "./arduino/arduino"; import { SerialMonitor } from "./serialmonitor/serialMonitor"; const usbDetectorModule = impor("./serialmonitor/usbDetector") as typeof import ("./serialmonitor/usbDetector"); @@ -115,7 +115,7 @@ export async function activate(context: vscode.ExtensionContext) { registerArduinoCommand("arduino.initialize", async () => await deviceContext.initialize()); registerArduinoCommand("arduino.verify", async () => { - if (!arduinoContextModule.default.arduinoApp.building) { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Arduino: Verifying...", @@ -131,7 +131,7 @@ export async function activate(context: vscode.ExtensionContext) { }); registerArduinoCommand("arduino.upload", async () => { - if (!arduinoContextModule.default.arduinoApp.building) { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Arduino: Uploading...", @@ -144,7 +144,7 @@ export async function activate(context: vscode.ExtensionContext) { }); registerArduinoCommand("arduino.cliUpload", async () => { - if (!arduinoContextModule.default.arduinoApp.building) { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Arduino: Using CLI to upload...", @@ -194,7 +194,7 @@ export async function activate(context: vscode.ExtensionContext) { }); registerArduinoCommand("arduino.uploadUsingProgrammer", async () => { - if (!arduinoContextModule.default.arduinoApp.building) { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Arduino: Uploading (programmer)...", @@ -207,7 +207,7 @@ export async function activate(context: vscode.ExtensionContext) { }); registerArduinoCommand("arduino.cliUploadUsingProgrammer", async () => { - if (!arduinoContextModule.default.arduinoApp.building) { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Arduino: Using CLI to upload (programmer)...", @@ -219,8 +219,21 @@ export async function activate(context: vscode.ExtensionContext) { return { board: arduinoContextModule.default.boardManager.currentBoard.name }; }); + registerArduinoCommand("arduino.cliBurnBootloader", async () => { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: "Arduino: Using CLI to burn bootloader...", + }, async() => { + await arduinoContextModule.default.arduinoApp.burnBootloader(); + }); + } + }, () => { + return { board: arduinoContextModule.default.boardManager.currentBoard.name } + }); + registerArduinoCommand("arduino.rebuildIntelliSenseConfig", async () => { - if (!arduinoContextModule.default.arduinoApp.building) { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Arduino: Rebuilding IS Configuration...", @@ -238,7 +251,7 @@ export async function activate(context: vscode.ExtensionContext) { // it seems not to be possible to trigger building while setting // the programmer. If the timed IntelliSense analysis is triggered // this is not a problem, since it doesn't use the programmer. - if (!arduinoContextModule.default.arduinoApp.building) { + if (arduinoContextModule.default.arduinoApp.state === ArduinoState.Idle) { try { await arduinoContextModule.default.arduinoApp.programmerManager.selectProgrammer(); } catch (ex) { From 2018892814218a47cb212772d2c5ac30f3f23527 Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Sat, 19 Feb 2022 17:34:30 -0600 Subject: [PATCH 2/8] Fixes and cleanup --- src/arduino/arduino.ts | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 3b83b488..2b52ebae 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -82,7 +82,7 @@ export class ArduinoApp { constructor(private _settings: IArduinoSettings) { const analysisDelayMs = 1000 * 3; this._analysisManager = new AnalysisManager( - () => this._state === ArduinoState.Idle, + () => this._state !== ArduinoState.Idle, async () => { await this.build(BuildMode.Analyze); }, analysisDelayMs); } @@ -188,18 +188,16 @@ export class ArduinoApp { this._state = ArduinoState.Building; - return await this._build(buildMode, buildDir) - .then((ret) => { - this._state = ArduinoState.Idle; - return ret; - }) - .catch((reason) => { - this._state = ArduinoState.Idle; + try { + return await this._build(buildMode, buildDir); + } catch(reason) { logger.notifyUserError("ArduinoApp.build", reason, `Unhandled exception when cleaning up build "${buildMode}": ${JSON.stringify(reason)}`); return false; - }); + } finally { + this._state = ArduinoState.Idle; + } } public async burnBootloader() { @@ -208,17 +206,16 @@ export class ArduinoApp { } this._state = ArduinoState.BurningBootloader; - return await this._burnBootloader(). - then((ret) => { - this._state = ArduinoState.Idle; - }) - .catch((reason) => { - this._state = ArduinoState.Idle; + try { + return await this._burnBootloader(); + } catch(reason) { logger.notifyUserError("ArduinoApp.burnBootloader", reason, `Unhandled exception burning bootloader: ${JSON.stringify(reason)}`); return false; - }); + } finally { + this._state = ArduinoState.Idle; + } } // Include the *.h header files from selected library to the arduino sketch. From c0a1389fcd31f704f3bdff0e7bcba6c576d3b713 Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Sat, 19 Feb 2022 17:38:20 -0600 Subject: [PATCH 3/8] Fix linting errors --- src/arduino/arduino.ts | 6 +++--- src/extension.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 2b52ebae..8cabfa98 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -39,13 +39,13 @@ export enum BuildMode { CliUpload = "Uploading using Arduino CLI", UploadProgrammer = "Uploading (programmer)", CliUploadProgrammer = "Uploading (programmer) using Arduino CLI", - CliBurnBootloader = "Burning Bootloader using Arduino CLI" + CliBurnBootloader = "Burning Bootloader using Arduino CLI", } export enum ArduinoState { Idle, Building, - BurningBootloader + BurningBootloader, } /** @@ -871,7 +871,7 @@ export class ArduinoApp { logger.notifyUserError("programmerManager.currentProgrammer", new Error(constants.messages.NO_PROGRAMMMER_SELECTED)); return false; } - args.push('--programmer', programmer); + args.push("--programmer", programmer); // We always build verbosely but filter the output based on the settings args.push("--verbose"); diff --git a/src/extension.ts b/src/extension.ts index aff171d0..962fcc82 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -224,7 +224,7 @@ export async function activate(context: vscode.ExtensionContext) { await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: "Arduino: Using CLI to burn bootloader...", - }, async() => { + }, async () => { await arduinoContextModule.default.arduinoApp.burnBootloader(); }); } From d70c8b87ea71152364026dbd89b423b42e28241e Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Sat, 19 Feb 2022 17:40:59 -0600 Subject: [PATCH 4/8] Fix linting errors --- src/arduino/arduino.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 8cabfa98..0d3e278d 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -190,7 +190,7 @@ export class ArduinoApp { try { return await this._build(buildMode, buildDir); - } catch(reason) { + } catch (reason) { logger.notifyUserError("ArduinoApp.build", reason, `Unhandled exception when cleaning up build "${buildMode}": ${JSON.stringify(reason)}`); @@ -208,7 +208,7 @@ export class ArduinoApp { this._state = ArduinoState.BurningBootloader; try { return await this._burnBootloader(); - } catch(reason) { + } catch (reason) { logger.notifyUserError("ArduinoApp.burnBootloader", reason, `Unhandled exception burning bootloader: ${JSON.stringify(reason)}`); From 18cd7e0694dff7da7aacaee09667463d6458fa79 Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Sat, 19 Feb 2022 17:46:47 -0600 Subject: [PATCH 5/8] Comments and fixes --- src/arduino/arduino.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 0d3e278d..0e1784f0 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -176,13 +176,13 @@ export class ArduinoApp { * @param buildDir Override the build directory set by the project settings * with the given directory. * @returns true on success, false if - * * another build is currently in progress + * * another build or burn bootloader operation is currently in progress * * board- or programmer-manager aren't initialized yet * * or something went wrong during the build */ public async build(buildMode: BuildMode, buildDir?: string) { - if (!this._boardManager || !this._programmerManager || this._state) { + if (!this._boardManager || !this._programmerManager || this._state !== ArduinoState.Idle) { return false; } @@ -200,8 +200,16 @@ export class ArduinoApp { } } + /** + * Burns the bootloader onto the currently selected board using the currently + * selected programmer. + * @returns true on success, false if + * * another build or burn bootloader operation is currently in progress + * * board- or programmer-manager aren't initialized yet + * * something went wrong while burning the bootloader + */ public async burnBootloader() { - if (!this._boardManager || !this.programmerManager || this._state) { + if (!this._boardManager || !this.programmerManager || this._state !== ArduinoState.Idle) { return false; } From 778de72c3f4a2ce80ad5b491900f0d5e193d1334 Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Sat, 19 Feb 2022 17:48:20 -0600 Subject: [PATCH 6/8] Add command to test --- test/extension.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/extension.test.ts b/test/extension.test.ts index 4ffee724..b327a6d0 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -58,6 +58,7 @@ suite("Arduino: Extension Tests", () => { "arduino.selectSketch", "arduino.cliUpload", "arduino.cliUploadUsingProgrammer", + "arduino.cliBurnBootloader" ]; const foundArduinoCommands = commands.filter((value) => { From 03e9b84fb3e5a0cbeaed95f7bc5b126336726cd5 Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Sat, 19 Feb 2022 17:52:36 -0600 Subject: [PATCH 7/8] Fix linting errors --- src/arduino/arduino.ts | 2 +- test/extension.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 0e1784f0..5f3c329b 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -200,7 +200,7 @@ export class ArduinoApp { } } - /** + /** * Burns the bootloader onto the currently selected board using the currently * selected programmer. * @returns true on success, false if diff --git a/test/extension.test.ts b/test/extension.test.ts index b327a6d0..703d80b6 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -58,7 +58,7 @@ suite("Arduino: Extension Tests", () => { "arduino.selectSketch", "arduino.cliUpload", "arduino.cliUploadUsingProgrammer", - "arduino.cliBurnBootloader" + "arduino.cliBurnBootloader", ]; const foundArduinoCommands = commands.filter((value) => { From ca9e10ad61b5869961fd487a5b06277d95b94ea3 Mon Sep 17 00:00:00 2001 From: davidcooper1 Date: Tue, 1 Mar 2022 02:10:25 -0600 Subject: [PATCH 8/8] Use build preferences --- src/arduino/arduino.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 5f3c329b..21531795 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -884,6 +884,14 @@ export class ArduinoApp { // We always build verbosely but filter the output based on the settings args.push("--verbose"); + if (dc.buildPreferences) { + for (const pref of dc.buildPreferences) { + // Note: BuildPrefSetting makes sure that each preference + // value consists of exactly two items (key and value). + args.push("--build-property", `${pref[0]}=${pref[1]}`); + } + } + // we prepare the channel here since all following code will // or at least can possibly output to it arduinoChannel.show();