diff --git a/src/arduino/arduino.ts b/src/arduino/arduino.ts index 790dd48d..a1880ba3 100644 --- a/src/arduino/arduino.ts +++ b/src/arduino/arduino.ts @@ -189,580 +189,576 @@ export class ArduinoApp { }); } -// Not moving _build around in the file (yet?) because it would create too much merge/rebase problems. -/* tslint:disable:member-ordering */ - - /** - * Private implementation. Not to be called directly. The wrapper build() - * manages the build state. - * @param buildMode See build() - * @param buildDir See build() - * @see https://github.com/arduino/Arduino/blob/master/build/shared/manpage.adoc - */ - public async _build(buildMode: BuildMode, compile: boolean, buildDir?: string): Promise { - const dc = DeviceContext.getInstance(); - const args: string[] = []; - let restoreSerialMonitor: boolean = false; - const verbose = VscodeSettings.getInstance().logLevel === constants.LogLevel.Verbose; - - if (!this.boardManager.currentBoard) { - if (buildMode !== BuildMode.Analyze) { - logger.notifyUserError("boardManager.currentBoard", new Error(constants.messages.NO_BOARD_SELECTED)); - } - return false; - } - const boardDescriptor = this.boardManager.currentBoard.getBuildConfig(); - - if (!this.useArduinoCli()) { - args.push("--board", boardDescriptor); - } - + // Include the *.h header files from selected library to the arduino sketch. + public async includeLibrary(libraryPath: string) { if (!ArduinoWorkspace.rootPath) { - vscode.window.showWarningMessage("Workspace doesn't seem to have a folder added to it yet."); - return false; + return; } + const dc = DeviceContext.getInstance(); + const appPath = path.join(ArduinoWorkspace.rootPath, dc.sketch); + if (util.fileExistsSync(appPath)) { + const hFiles = glob.sync(`${libraryPath}/*.h`, { + nodir: true, + matchBase: true, + }); + const hIncludes = hFiles.map((hFile) => { + return `#include <${path.basename(hFile)}>`; + }).join(os.EOL); - if (!dc.sketch || !util.fileExistsSync(path.join(ArduinoWorkspace.rootPath, dc.sketch))) { - if (buildMode === BuildMode.Analyze) { - // Analyze runs non interactively - return false; - } - if (!await dc.resolveMainSketch()) { - vscode.window.showErrorMessage("No sketch file was found. Please specify the sketch in the arduino.json file"); - return false; + // Open the sketch and bring up it to current visible view. + const textDocument = await vscode.workspace.openTextDocument(appPath); + await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One, true); + const activeEditor = vscode.window.visibleTextEditors.find((textEditor) => { + return path.resolve(textEditor.document.fileName) === path.resolve(appPath); + }); + if (activeEditor) { + // Insert *.h at the beginning of the sketch code. + await activeEditor.edit((editBuilder) => { + editBuilder.insert(new vscode.Position(0, 0), `${hIncludes}${os.EOL}${os.EOL}`); + }); } } + } - 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"); + /** + * Installs arduino board package. + * (If using the aduino CLI this installs the corrosponding core.) + * @param {string} packageName - board vendor + * @param {string} arch - board architecture + * @param {string} version - version of board package or core to download + * @param {boolean} [showOutput=true] - show raw output from command + */ + public async installBoard(packageName: string, arch: string = "", version: string = "", showOutput: boolean = true) { + arduinoChannel.show(); + const updatingIndex = packageName === "dummy" && !arch && !version; + if (updatingIndex) { + arduinoChannel.start(`Update package index files...`); + } else { + try { + const packagePath = path.join(this._settings.packagePath, "packages", packageName, arch); + if (util.directoryExistsSync(packagePath)) { + util.rmdirRecursivelySync(packagePath); + } + arduinoChannel.start(`Install package - ${packageName}...`); + } catch (error) { + arduinoChannel.start(`Install package - ${packageName} failed under directory : ${error.path}${os.EOL} + Please make sure the folder is not occupied by other procedures .`); + arduinoChannel.error(`Error message - ${error.message}${os.EOL}`); + arduinoChannel.error(`Exit with code=${error.code}${os.EOL}`); + return; } } - - if (buildMode === BuildMode.Upload) { - if ((!dc.configuration || !/upload_method=[^=,]*st[^,]*link/i.test(dc.configuration)) && !dc.port) { - await selectSerial(); - return false; - } - - if (!compile && !this.useArduinoCli()) { - arduinoChannel.error("This command is only available when using the Arduino CLI"); - return false; + arduinoChannel.info(`${packageName}${arch && ":" + arch}${version && ":" + version}`); + try { + if (this.useArduinoCli()) { + await util.spawn(this._settings.commandPath, + ["core", "install", `${packageName}${arch && ":" + arch}${version && "@" + version}`], + undefined, + { channel: showOutput ? arduinoChannel.channel : null }); + } else { + await util.spawn(this._settings.commandPath, + ["--install-boards", `${packageName}${arch && ":" + arch}${version && ":" + version}`], + undefined, + { channel: showOutput ? arduinoChannel.channel : null }); } - - if (!this.useArduinoCli()) { - args.push("--upload"); + if (updatingIndex) { + arduinoChannel.end("Updated package index files."); } else { - // TODO: add the --clean argument to the cli args when v 0.14 is released (this will clean up the build folder after uploading) - if (compile) { - args.push("compile", "--upload"); + arduinoChannel.end(`Installed board package - ${packageName}${os.EOL}`); + } + } catch (error) { + // If a platform with the same version is already installed, nothing is installed and program exits with exit code 1 + if (error.code === 1) { + if (updatingIndex) { + arduinoChannel.end("Updated package index files."); } else { - args.push("upload"); + arduinoChannel.end(`Installed board package - ${packageName}${os.EOL}`); } - args.push("-b", boardDescriptor); + } else { + arduinoChannel.error(`Exit with code=${error.code}${os.EOL}`); } + } + } - if (dc.port) { - args.push("--port", dc.port); - } - } else if (buildMode === BuildMode.UploadProgrammer) { - const programmer = this.programmerManager.currentProgrammer; - if (!programmer) { - logger.notifyUserError("programmerManager.currentProgrammer", new Error(constants.messages.NO_PROGRAMMMER_SELECTED)); - return false; - } - if (!dc.port) { - await selectSerial(); - return false; - } - if (!compile && !this.useArduinoCli()) { - arduinoChannel.error("This command is only available when using the Arduino CLI"); - return false; - } + public uninstallBoard(boardName: string, packagePath: string) { + arduinoChannel.start(`Uninstall board package - ${boardName}...`); + util.rmdirRecursivelySync(packagePath); + arduinoChannel.end(`Uninstalled board package - ${boardName}${os.EOL}`); + } - if (!this.useArduinoCli()) { - args.push("--upload"); - } else { - // TODO: add the --clean argument to the cli args when v 0.14 is released (this will clean up the build folder after uploading) - if (compile) { - args.push("compile", "--upload"); - } else { - args.push("upload"); - } - args.push("-b", boardDescriptor); - } + /** + * Downloads or updates a library + * @param {string} libName - name of the library to download + * @param {string} version - version of library to download + * @param {boolean} [showOutput=true] - show raw output from command + */ + public async installLibrary(libName: string, version: string = "", showOutput: boolean = true) { + arduinoChannel.show(); + const updatingIndex = (libName === "dummy" && !version); + if (updatingIndex) { + arduinoChannel.start("Update library index files..."); + } else { + arduinoChannel.start(`Install library - ${libName}`); + } + try { if (this.useArduinoCli()) { - args.push("--programmer", programmer) + await util.spawn(this._settings.commandPath, + ["lib", "install", `${libName}${version && "@" + version}`], + undefined, + { channel: showOutput ? arduinoChannel.channel : undefined }); } else { - args.push("--useprogrammer", "--pref", `programmer=arduino:${programmer}`); + await util.spawn(this._settings.commandPath, + ["--install-library", `${libName}${version && ":" + version}`], + undefined, + { channel: showOutput ? arduinoChannel.channel : undefined }); } - - args.push("--port", dc.port); - if (!this.useArduinoCli()) { - args.push("--verify"); + if (updatingIndex) { + arduinoChannel.end("Updated library index files."); } else { - args.push("compile", "-b", boardDescriptor); + arduinoChannel.end(`Installed library - ${libName}${os.EOL}`); } - } else { - if (!this.useArduinoCli()) { - args.push("--verify"); + } catch (error) { + // If a library with the same version is already installed, nothing is installed and program exits with exit code 1 + if (error.code === 1) { + if (updatingIndex) { + arduinoChannel.end("Updated library index files."); + } else { + arduinoChannel.end(`Installed library - ${libName}${os.EOL}`); + } } else { - args.push("compile", "-b", boardDescriptor); + arduinoChannel.error(`Exit with code=${error.code}${os.EOL}`); } } + } - 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("--pref", `${pref[0]}=${pref[1]}`); + public uninstallLibrary(libName: string, libPath: string) { + arduinoChannel.start(`Remove library - ${libName}`); + util.rmdirRecursivelySync(libPath); + arduinoChannel.end(`Removed library - ${libName}${os.EOL}`); + } + + public openExample(example) { + function tmpName(name) { + let counter = 0; + let candidateName = name; + while (true) { + if (!util.fileExistsSync(candidateName) && !util.directoryExistsSync(candidateName)) { + return candidateName; + } + counter++; + candidateName = `${name}_${counter}`; } } - // We always build verbosely but filter the output based on the settings - args.push("--verbose-build"); - if (verbose) { - args.push("--verbose-upload"); + // Step 1: Copy the example project to a temporary directory. + const sketchPath = path.join(this._settings.sketchbookPath, "generated_examples"); + if (!util.directoryExistsSync(sketchPath)) { + util.mkdirRecursivelySync(sketchPath); } - - await vscode.workspace.saveAll(false); - - // we prepare the channel here since all following code will - // or at leas can possibly output to it - arduinoChannel.show(); - arduinoChannel.start(`${buildMode} sketch '${dc.sketch}'`); - - if ((buildDir || dc.output) && compile) { - // 2020-02-29, EW: This whole code appears a bit wonky to me. - // What if the user specifies an output directory "../builds/my project" - buildDir = path.resolve(ArduinoWorkspace.rootPath, buildDir || dc.output); - const dirPath = path.dirname(buildDir); - if (!util.directoryExistsSync(dirPath)) { - logger.notifyUserError("InvalidOutPutPath", new Error(constants.messages.INVALID_OUTPUT_PATH + buildDir)); - return false; + let destExample = ""; + if (util.directoryExistsSync(example)) { + destExample = tmpName(path.join(sketchPath, path.basename(example))); + util.cp(example, destExample); + } else if (util.fileExistsSync(example)) { + const exampleName = path.basename(example, path.extname(example)); + destExample = tmpName(path.join(sketchPath, exampleName)); + util.mkdirRecursivelySync(destExample); + util.cp(example, path.join(destExample, path.basename(example))); + } + if (destExample) { + // Step 2: Scaffold the example project to an arduino project. + const items = fs.readdirSync(destExample); + const sketchFile = items.find((item) => { + return util.isArduinoFile(path.join(destExample, item)); + }); + if (sketchFile) { + // Generate arduino.json + const dc = DeviceContext.getInstance(); + const arduinoJson = { + sketch: sketchFile, + // TODO EW, 2020-02-18: COM1 is Windows specific - what about OSX and Linux users? + port: dc.port || "COM1", + board: dc.board, + configuration: dc.configuration, + }; + const arduinoConfigFilePath = path.join(destExample, constants.ARDUINO_CONFIG_FILE); + util.mkdirRecursivelySync(path.dirname(arduinoConfigFilePath)); + fs.writeFileSync(arduinoConfigFilePath, JSON.stringify(arduinoJson, null, 4)); } - if (this.useArduinoCli()) { - args.push("--build-path", buildDir); + // Step 3: Open the arduino project at a new vscode window. + vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(destExample), true); + } + return destExample; + } - } else { - args.push("--pref", `build.path=${buildDir}`); - } + public get settings() { + return this._settings; + } - arduinoChannel.info(`Please see the build logs in output path: ${buildDir}`); - } else { - const msg = "Output path is not specified. Unable to reuse previously compiled files. Build will be slower. See README."; - arduinoChannel.warning(msg); - } + public get boardManager() { + return this._boardManager; + } - // Environment variables passed to pre- and post-build commands - const env = { - VSCA_BUILD_MODE: buildMode, - VSCA_SKETCH: dc.sketch, - VSCA_BOARD: boardDescriptor, - VSCA_WORKSPACE_DIR: ArduinoWorkspace.rootPath, - VSCA_LOG_LEVEL: verbose ? constants.LogLevel.Verbose : constants.LogLevel.Info, - }; - if (dc.port) { - env["VSCA_SERIAL"] = dc.port; - } - if (buildDir) { - env["VSCA_BUILD_DIR"] = buildDir; - } + public set boardManager(value: BoardManager) { + this._boardManager = value; + } - // TODO EW: What should we do with pre-/post build commands when running - // analysis? Some could use it to generate/manipulate code which could - // be a prerequisite for a successful build - if (!await this.runPrePostBuildCommand(dc, env, "pre")) { - return false; - } + public get libraryManager() { + return this._libraryManager; + } - // stop serial monitor when everything is prepared and good - // what makes restoring of its previous state easier - if (buildMode === BuildMode.Upload || buildMode === BuildMode.UploadProgrammer) { - restoreSerialMonitor = await SerialMonitor.getInstance().closeSerialMonitor(dc.port); - UsbDetector.getInstance().pauseListening(); - } + public set libraryManager(value: LibraryManager) { + this._libraryManager = value; + } - // Push sketch as last argument - args.push(path.join(ArduinoWorkspace.rootPath, dc.sketch)); + public get exampleManager() { + return this._exampleManager; + } - const cocopa = makeCompilerParserContext(dc); + public set exampleManager(value: ExampleManager) { + this._exampleManager = value; + } - const cleanup = async (result: "ok" | "error") => { - let ret = true; - if (result === "ok") { - ret = await this.runPrePostBuildCommand(dc, env, "post"); - } - await cocopa.conclude(); - if (buildMode === BuildMode.Upload || buildMode === BuildMode.UploadProgrammer) { - UsbDetector.getInstance().resumeListening(); - if (restoreSerialMonitor) { - await SerialMonitor.getInstance().openSerialMonitor(); - } - } - return ret; - } - const stdoutcb = (line: string) => { - if (cocopa.callback) { - cocopa.callback(line); - } - if (verbose) { - arduinoChannel.channel.append(line); - } - } - const stderrcb = (line: string) => { + public get programmerManager() { + return this._programmerManager; + } + + public set programmerManager(value: ProgrammerManager) { + this._programmerManager = value; + } + + /** + * Runs the pre or post build command. + * Usually before one of + * * verify + * * upload + * * upload using programmer + * @param dc Device context prepared during one of the above actions + * @param what "pre" if the pre-build command should be run, "post" if the + * post-build command should be run. + * @returns True if successful, false on error. + */ + protected async runPrePostBuildCommand(dc: DeviceContext, + environment: any, + what: "pre" | "post"): Promise { + const cmdline = what === "pre" + ? dc.prebuild + : dc.postbuild; + + if (cmdline) { + arduinoChannel.info(`Running ${what}-build command: "${cmdline}"`); + let cmd: string; + let args: string[]; + // pre-/post-build commands feature full bash support on UNIX systems. + // On Windows you have full cmd support. 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}`; + args = []; + cmd = cmdline; + } else { + args = ["-c", cmdline]; + cmd = "bash"; } - 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; - } - } + try { + await util.spawn(cmd, + args, + { + shell: os.platform() === "win32", + cwd: ArduinoWorkspace.rootPath, + env: {...environment}, + }, + { channel: arduinoChannel.channel }); + } catch (ex) { + const msg = ex.error + ? `${ex.error}` + : ex.code + ? `Exit code = ${ex.code}` + : JSON.stringify(ex); + arduinoChannel.error(`Running ${what}-build command failed: ${os.EOL}${msg}`); + return false; } - arduinoChannel.channel.append(line); } + return true; + } - return await util.spawn( - this._settings.commandPath, - args, - undefined, - { stdout: stdoutcb, stderr: stderrcb }, - ).then(async () => { - const ret = await cleanup("ok"); - if (ret) { - arduinoChannel.end(`${buildMode} sketch '${dc.sketch}'${os.EOL}`); + /** + * Checks if the arduino cli is being used + * @returns {bool} - true if arduino cli is being use + */ + private useArduinoCli() { + return this._settings.useArduinoCli; + // return VscodeSettings.getInstance().useArduinoCli; + } + + /** + * Private implementation. Not to be called directly. The wrapper build() + * manages the build state. + * @param buildMode See build() + * @param buildDir See build() + * @see https://github.com/arduino/Arduino/blob/master/build/shared/manpage.adoc + */ + private async _build(buildMode: BuildMode, compile: boolean, buildDir?: string): Promise { + const dc = DeviceContext.getInstance(); + const args: string[] = []; + let restoreSerialMonitor: boolean = false; + const verbose = VscodeSettings.getInstance().logLevel === constants.LogLevel.Verbose; + + if (!this.boardManager.currentBoard) { + if (buildMode !== BuildMode.Analyze) { + logger.notifyUserError("boardManager.currentBoard", new Error(constants.messages.NO_BOARD_SELECTED)); } - return ret; - }, async (reason) => { - await cleanup("error"); - const msg = reason.code - ? `Exit with code=${reason.code}` - : JSON.stringify(reason); - arduinoChannel.error(`${buildMode} sketch '${dc.sketch}': ${msg}${os.EOL}`); return false; - }); - } + } + const boardDescriptor = this.boardManager.currentBoard.getBuildConfig(); + + if (!this.useArduinoCli()) { + args.push("--board", boardDescriptor); + } - // Include the *.h header files from selected library to the arduino sketch. - public async includeLibrary(libraryPath: string) { if (!ArduinoWorkspace.rootPath) { - return; + vscode.window.showWarningMessage("Workspace doesn't seem to have a folder added to it yet."); + return false; } - const dc = DeviceContext.getInstance(); - const appPath = path.join(ArduinoWorkspace.rootPath, dc.sketch); - if (util.fileExistsSync(appPath)) { - const hFiles = glob.sync(`${libraryPath}/*.h`, { - nodir: true, - matchBase: true, - }); - const hIncludes = hFiles.map((hFile) => { - return `#include <${path.basename(hFile)}>`; - }).join(os.EOL); - // Open the sketch and bring up it to current visible view. - const textDocument = await vscode.workspace.openTextDocument(appPath); - await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One, true); - const activeEditor = vscode.window.visibleTextEditors.find((textEditor) => { - return path.resolve(textEditor.document.fileName) === path.resolve(appPath); - }); - if (activeEditor) { - // Insert *.h at the beginning of the sketch code. - await activeEditor.edit((editBuilder) => { - editBuilder.insert(new vscode.Position(0, 0), `${hIncludes}${os.EOL}${os.EOL}`); - }); + if (!dc.sketch || !util.fileExistsSync(path.join(ArduinoWorkspace.rootPath, dc.sketch))) { + if (buildMode === BuildMode.Analyze) { + // Analyze runs non interactively + return false; + } + if (!await dc.resolveMainSketch()) { + vscode.window.showErrorMessage("No sketch file was found. Please specify the sketch in the arduino.json file"); + return false; } } - } - /** - * Installs arduino board package. - * (If using the aduino CLI this installs the corrosponding core.) - * @param {string} packageName - board vendor - * @param {string} arch - board architecture - * @param {string} version - version of board package or core to download - * @param {boolean} [showOutput=true] - show raw output from command - */ - public async installBoard(packageName: string, arch: string = "", version: string = "", showOutput: boolean = true) { - arduinoChannel.show(); - const updatingIndex = packageName === "dummy" && !arch && !version; - if (updatingIndex) { - arduinoChannel.start(`Update package index files...`); - } else { - try { - const packagePath = path.join(this._settings.packagePath, "packages", packageName, arch); - if (util.directoryExistsSync(packagePath)) { - util.rmdirRecursivelySync(packagePath); - } - arduinoChannel.start(`Install package - ${packageName}...`); - } catch (error) { - arduinoChannel.start(`Install package - ${packageName} failed under directory : ${error.path}${os.EOL} - Please make sure the folder is not occupied by other procedures .`); - arduinoChannel.error(`Error message - ${error.message}${os.EOL}`); - arduinoChannel.error(`Exit with code=${error.code}${os.EOL}`); - return; + 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"); } } - arduinoChannel.info(`${packageName}${arch && ":" + arch}${version && ":" + version}`); - try { - if (this.useArduinoCli()) { - await util.spawn(this._settings.commandPath, - ["core", "install", `${packageName}${arch && ":" + arch}${version && "@" + version}`], - undefined, - { channel: showOutput ? arduinoChannel.channel : null }); - } else { - await util.spawn(this._settings.commandPath, - ["--install-boards", `${packageName}${arch && ":" + arch}${version && ":" + version}`], - undefined, - { channel: showOutput ? arduinoChannel.channel : null }); + + if (buildMode === BuildMode.Upload) { + if ((!dc.configuration || !/upload_method=[^=,]*st[^,]*link/i.test(dc.configuration)) && !dc.port) { + await selectSerial(); + return false; } - if (updatingIndex) { - arduinoChannel.end("Updated package index files."); - } else { - arduinoChannel.end(`Installed board package - ${packageName}${os.EOL}`); + + if (!compile && !this.useArduinoCli()) { + arduinoChannel.error("This command is only available when using the Arduino CLI"); + return false; } - } catch (error) { - // If a platform with the same version is already installed, nothing is installed and program exits with exit code 1 - if (error.code === 1) { - if (updatingIndex) { - arduinoChannel.end("Updated package index files."); + + if (!this.useArduinoCli()) { + args.push("--upload"); + } else { + // TODO: add the --clean argument to the cli args when v 0.14 is released (this will clean up the build folder after uploading) + if (compile) { + args.push("compile", "--upload"); } else { - arduinoChannel.end(`Installed board package - ${packageName}${os.EOL}`); + args.push("upload"); } - } else { - arduinoChannel.error(`Exit with code=${error.code}${os.EOL}`); + args.push("-b", boardDescriptor); } - } - } - - public uninstallBoard(boardName: string, packagePath: string) { - arduinoChannel.start(`Uninstall board package - ${boardName}...`); - util.rmdirRecursivelySync(packagePath); - arduinoChannel.end(`Uninstalled board package - ${boardName}${os.EOL}`); - } - - /** - * Downloads or updates a library - * @param {string} libName - name of the library to download - * @param {string} version - version of library to download - * @param {boolean} [showOutput=true] - show raw output from command - */ - public async installLibrary(libName: string, version: string = "", showOutput: boolean = true) { - arduinoChannel.show(); - const updatingIndex = (libName === "dummy" && !version); - if (updatingIndex) { - arduinoChannel.start("Update library index files..."); - } else { - arduinoChannel.start(`Install library - ${libName}`); - } - try { - if (this.useArduinoCli()) { - await util.spawn(this._settings.commandPath, - ["lib", "install", `${libName}${version && "@" + version}`], - undefined, - { channel: showOutput ? arduinoChannel.channel : undefined }); - } else { - await util.spawn(this._settings.commandPath, - ["--install-library", `${libName}${version && ":" + version}`], - undefined, - { channel: showOutput ? arduinoChannel.channel : undefined }); + if (dc.port) { + args.push("--port", dc.port); } - if (updatingIndex) { - arduinoChannel.end("Updated library index files."); - } else { - arduinoChannel.end(`Installed library - ${libName}${os.EOL}`); + } else if (buildMode === BuildMode.UploadProgrammer) { + const programmer = this.programmerManager.currentProgrammer; + if (!programmer) { + logger.notifyUserError("programmerManager.currentProgrammer", new Error(constants.messages.NO_PROGRAMMMER_SELECTED)); + return false; } - } catch (error) { - // If a library with the same version is already installed, nothing is installed and program exits with exit code 1 - if (error.code === 1) { - if (updatingIndex) { - arduinoChannel.end("Updated library index files."); + if (!dc.port) { + await selectSerial(); + return false; + } + if (!compile && !this.useArduinoCli()) { + arduinoChannel.error("This command is only available when using the Arduino CLI"); + return false; + } + + if (!this.useArduinoCli()) { + args.push("--upload"); + } else { + // TODO: add the --clean argument to the cli args when v 0.14 is released (this will clean up the build folder after uploading) + if (compile) { + args.push("compile", "--upload"); } else { - arduinoChannel.end(`Installed library - ${libName}${os.EOL}`); + args.push("upload"); } - } else { - arduinoChannel.error(`Exit with code=${error.code}${os.EOL}`); + args.push("-b", boardDescriptor); } - } - } - public uninstallLibrary(libName: string, libPath: string) { - arduinoChannel.start(`Remove library - ${libName}`); - util.rmdirRecursivelySync(libPath); - arduinoChannel.end(`Removed library - ${libName}${os.EOL}`); - } + if (this.useArduinoCli()) { + args.push("--programmer", programmer) + } else { + args.push("--useprogrammer", "--pref", `programmer=arduino:${programmer}`); + } - public openExample(example) { - function tmpName(name) { - let counter = 0; - let candidateName = name; - while (true) { - if (!util.fileExistsSync(candidateName) && !util.directoryExistsSync(candidateName)) { - return candidateName; - } - counter++; - candidateName = `${name}_${counter}`; + args.push("--port", dc.port); + if (!this.useArduinoCli()) { + args.push("--verify"); + } else { + args.push("compile", "-b", boardDescriptor); + } + } else { + if (!this.useArduinoCli()) { + args.push("--verify"); + } else { + args.push("compile", "-b", boardDescriptor); } } - // Step 1: Copy the example project to a temporary directory. - const sketchPath = path.join(this._settings.sketchbookPath, "generated_examples"); - if (!util.directoryExistsSync(sketchPath)) { - util.mkdirRecursivelySync(sketchPath); - } - let destExample = ""; - if (util.directoryExistsSync(example)) { - destExample = tmpName(path.join(sketchPath, path.basename(example))); - util.cp(example, destExample); - } else if (util.fileExistsSync(example)) { - const exampleName = path.basename(example, path.extname(example)); - destExample = tmpName(path.join(sketchPath, exampleName)); - util.mkdirRecursivelySync(destExample); - util.cp(example, path.join(destExample, path.basename(example))); - } - if (destExample) { - // Step 2: Scaffold the example project to an arduino project. - const items = fs.readdirSync(destExample); - const sketchFile = items.find((item) => { - return util.isArduinoFile(path.join(destExample, item)); - }); - if (sketchFile) { - // Generate arduino.json - const dc = DeviceContext.getInstance(); - const arduinoJson = { - sketch: sketchFile, - // TODO EW, 2020-02-18: COM1 is Windows specific - what about OSX and Linux users? - port: dc.port || "COM1", - board: dc.board, - configuration: dc.configuration, - }; - const arduinoConfigFilePath = path.join(destExample, constants.ARDUINO_CONFIG_FILE); - util.mkdirRecursivelySync(path.dirname(arduinoConfigFilePath)); - fs.writeFileSync(arduinoConfigFilePath, JSON.stringify(arduinoJson, null, 4)); + 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("--pref", `${pref[0]}=${pref[1]}`); } + } - // Step 3: Open the arduino project at a new vscode window. - vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(destExample), true); + // We always build verbosely but filter the output based on the settings + args.push("--verbose-build"); + if (verbose) { + args.push("--verbose-upload"); } - return destExample; - } - public get settings() { - return this._settings; - } + await vscode.workspace.saveAll(false); - public get boardManager() { - return this._boardManager; - } + // we prepare the channel here since all following code will + // or at leas can possibly output to it + arduinoChannel.show(); + arduinoChannel.start(`${buildMode} sketch '${dc.sketch}'`); - public set boardManager(value: BoardManager) { - this._boardManager = value; - } + if ((buildDir || dc.output) && compile) { + // 2020-02-29, EW: This whole code appears a bit wonky to me. + // What if the user specifies an output directory "../builds/my project" + buildDir = path.resolve(ArduinoWorkspace.rootPath, buildDir || dc.output); + const dirPath = path.dirname(buildDir); + if (!util.directoryExistsSync(dirPath)) { + logger.notifyUserError("InvalidOutPutPath", new Error(constants.messages.INVALID_OUTPUT_PATH + buildDir)); + return false; + } - public get libraryManager() { - return this._libraryManager; - } + if (this.useArduinoCli()) { + args.push("--build-path", buildDir); - public set libraryManager(value: LibraryManager) { - this._libraryManager = value; - } + } else { + args.push("--pref", `build.path=${buildDir}`); + } - public get exampleManager() { - return this._exampleManager; - } + arduinoChannel.info(`Please see the build logs in output path: ${buildDir}`); + } else { + const msg = "Output path is not specified. Unable to reuse previously compiled files. Build will be slower. See README."; + arduinoChannel.warning(msg); + } - public set exampleManager(value: ExampleManager) { - this._exampleManager = value; - } + // Environment variables passed to pre- and post-build commands + const env = { + VSCA_BUILD_MODE: buildMode, + VSCA_SKETCH: dc.sketch, + VSCA_BOARD: boardDescriptor, + VSCA_WORKSPACE_DIR: ArduinoWorkspace.rootPath, + VSCA_LOG_LEVEL: verbose ? constants.LogLevel.Verbose : constants.LogLevel.Info, + }; + if (dc.port) { + env["VSCA_SERIAL"] = dc.port; + } + if (buildDir) { + env["VSCA_BUILD_DIR"] = buildDir; + } - public get programmerManager() { - return this._programmerManager; - } + // TODO EW: What should we do with pre-/post build commands when running + // analysis? Some could use it to generate/manipulate code which could + // be a prerequisite for a successful build + if (!await this.runPrePostBuildCommand(dc, env, "pre")) { + return false; + } - public set programmerManager(value: ProgrammerManager) { - this._programmerManager = value; - } + // stop serial monitor when everything is prepared and good + // what makes restoring of its previous state easier + if (buildMode === BuildMode.Upload || buildMode === BuildMode.UploadProgrammer) { + restoreSerialMonitor = await SerialMonitor.getInstance().closeSerialMonitor(dc.port); + UsbDetector.getInstance().pauseListening(); + } - /** - * Runs the pre or post build command. - * Usually before one of - * * verify - * * upload - * * upload using programmer - * @param dc Device context prepared during one of the above actions - * @param what "pre" if the pre-build command should be run, "post" if the - * post-build command should be run. - * @returns True if successful, false on error. - */ - protected async runPrePostBuildCommand(dc: DeviceContext, - environment: any, - what: "pre" | "post"): Promise { - const cmdline = what === "pre" - ? dc.prebuild - : dc.postbuild; + // Push sketch as last argument + args.push(path.join(ArduinoWorkspace.rootPath, dc.sketch)); - if (cmdline) { - arduinoChannel.info(`Running ${what}-build command: "${cmdline}"`); - let cmd: string; - let args: string[]; - // pre-/post-build commands feature full bash support on UNIX systems. - // On Windows you have full cmd support. + const cocopa = makeCompilerParserContext(dc); + + const cleanup = async (result: "ok" | "error") => { + let ret = true; + if (result === "ok") { + ret = await this.runPrePostBuildCommand(dc, env, "post"); + } + await cocopa.conclude(); + if (buildMode === BuildMode.Upload || buildMode === BuildMode.UploadProgrammer) { + UsbDetector.getInstance().resumeListening(); + if (restoreSerialMonitor) { + await SerialMonitor.getInstance().openSerialMonitor(); + } + } + return ret; + } + const stdoutcb = (line: string) => { + if (cocopa.callback) { + cocopa.callback(line); + } + if (verbose) { + arduinoChannel.channel.append(line); + } + } + const stderrcb = (line: string) => { if (os.platform() === "win32") { - args = []; - cmd = cmdline; - } else { - args = ["-c", cmdline]; - cmd = "bash"; + line = line.trim(); + if (line.length <= 0) { + return; + } + line = line.replace(/(?:\r|\r\n|\n)+/g, os.EOL); + line = `${line}${os.EOL}`; } - try { - await util.spawn(cmd, - args, - { - shell: os.platform() === "win32", - cwd: ArduinoWorkspace.rootPath, - env: {...environment}, - }, - { channel: arduinoChannel.channel }); - } catch (ex) { - const msg = ex.error - ? `${ex.error}` - : ex.code - ? `Exit code = ${ex.code}` - : JSON.stringify(ex); - arduinoChannel.error(`Running ${what}-build command failed: ${os.EOL}${msg}`); - return false; + 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 true; - } - /** - * Checks if the arduino cli is being used - * @returns {bool} - true if arduino cli is being use - */ - private useArduinoCli() { - return this._settings.useArduinoCli; - // return VscodeSettings.getInstance().useArduinoCli; + return await util.spawn( + this._settings.commandPath, + args, + undefined, + { stdout: stdoutcb, stderr: stderrcb }, + ).then(async () => { + const ret = await cleanup("ok"); + if (ret) { + arduinoChannel.end(`${buildMode} sketch '${dc.sketch}'${os.EOL}`); + } + return ret; + }, async (reason) => { + await cleanup("error"); + const msg = reason.code + ? `Exit with code=${reason.code}` + : JSON.stringify(reason); + arduinoChannel.error(`${buildMode} sketch '${dc.sketch}': ${msg}${os.EOL}`); + return false; + }); } -/* tslint:enable:member-ordering */ }