in src/arduino/arduino.ts [519:806]
private async _build(buildMode: BuildMode, buildDir?: string): Promise<boolean> {
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("-b", boardDescriptor);
} else {
args.push("--board", boardDescriptor);
}
if (!ArduinoWorkspace.rootPath) {
vscode.window.showWarningMessage("Workspace doesn't seem to have a folder added to it yet.");
return false;
}
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;
}
}
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();
return false;
}
if (this.useArduinoCli()) {
args.push("compile", "--upload");
} else {
args.push("--upload");
}
if (dc.port) {
args.push("--port", dc.port);
}
} else if (buildMode === BuildMode.CliUpload) {
if ((!dc.configuration || !/upload_method=[^=,]*st[^,]*link/i.test(dc.configuration)) && !dc.port) {
await selectSerial();
return false;
}
if (!this.useArduinoCli()) {
arduinoChannel.error("This command is only available when using the Arduino CLI");
return false;
}
args.push("upload");
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 (this.useArduinoCli()) {
args.push("compile",
"--upload",
"--programmer", programmer);
} else {
args.push("--upload",
"--useprogrammer",
"--pref", `programmer=${programmer}`);
}
args.push("--port", dc.port);
} else if (buildMode === BuildMode.CliUploadProgrammer) {
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 (!this.useArduinoCli()) {
arduinoChannel.error("This command is only available when using the Arduino CLI");
return false;
}
args.push("upload",
"--programmer", programmer,
"--port", dc.port);
} else {
if (this.useArduinoCli()) {
args.unshift("compile");
} else {
args.push("--verify");
}
}
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).
if (this.useArduinoCli()) {
args.push("--build-property", `${pref[0]}=${pref[1]}`);
} else {
args.push("--pref", `${pref[0]}=${pref[1]}`);
}
}
}
// We always build verbosely but filter the output based on the settings
this._settings.useArduinoCli ? args.push("--verbose") : args.push("--verbose-build");
if (verbose && !this._settings.useArduinoCli) {
args.push("--verbose-upload");
}
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();
if (VscodeSettings.getInstance().clearOutputOnBuild) {
arduinoChannel.clear();
}
arduinoChannel.start(`${buildMode} sketch '${dc.sketch}'`);
if (buildDir || dc.output) {
// 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;
}
if (this.useArduinoCli()) {
args.push("--build-path", buildDir);
} else {
args.push("--pref", `build.path=${buildDir}`);
}
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);
}
// 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;
}
// 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;
}
// stop serial monitor when everything is prepared and good
// what makes restoring of its previous state easier
if (buildMode === BuildMode.Upload ||
buildMode === BuildMode.UploadProgrammer ||
buildMode === BuildMode.CliUpload ||
buildMode === BuildMode.CliUploadProgrammer) {
restoreSerialMonitor = await SerialMonitor.getInstance().closeSerialMonitor(dc.port);
UsbDetector.getInstance().pauseListening();
}
// Push sketch as last argument
args.push(path.join(ArduinoWorkspace.rootPath, dc.sketch));
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);
} else {
// Output sketch memory usage in non-verbose mode
if (this.isMemoryUsageInformation(line)) {
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 () => {
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;
});
}