This repository was archived by the owner on Oct 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 236
Add CLI Burn Bootloader Command #1463
Open
davidcooper1
wants to merge
10
commits into
microsoft:main
Choose a base branch
from
davidcooper1:burn-bootloader
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
19ef57d
Add cli burn bootloader command
davidcooper1 2018892
Fixes and cleanup
davidcooper1 c0a1389
Fix linting errors
davidcooper1 d70c8b8
Fix linting errors
davidcooper1 18cd7e0
Comments and fixes
davidcooper1 778de72
Add command to test
davidcooper1 03e9b84
Fix linting errors
davidcooper1 ddd4ba0
Merge branch 'main' of https://github.com/davidcooper1/vscode-arduino…
davidcooper1 ca9e10a
Use build preferences
davidcooper1 01b6051
Merge branch 'main' into burn-bootloader
davidcooper1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,18 +71,18 @@ 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. | ||
*/ | ||
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); | ||
} | ||
|
@@ -148,10 +155,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; | ||
} | ||
|
||
/** | ||
|
@@ -171,30 +178,54 @@ 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._building) { | ||
if (!this._boardManager || !this._programmerManager || this._state !== ArduinoState.Idle) { | ||
return false; | ||
} | ||
|
||
this._building = true; | ||
this._state = ArduinoState.Building; | ||
|
||
return await this._build(buildMode, buildDir) | ||
.then((ret) => { | ||
this._building = false; | ||
return ret; | ||
}) | ||
.catch((reason) => { | ||
this._building = false; | ||
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; | ||
} | ||
} | ||
|
||
/** | ||
* 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 !== ArduinoState.Idle) { | ||
return false; | ||
} | ||
|
||
this._state = ArduinoState.BurningBootloader; | ||
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. | ||
|
@@ -511,9 +542,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<void> { | ||
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 | ||
|
@@ -554,18 +598,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; | ||
} | ||
|
||
|
@@ -580,7 +615,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; | ||
} | ||
|
||
|
@@ -601,7 +636,7 @@ export class ArduinoApp { | |
return false; | ||
} | ||
if (!dc.port) { | ||
await selectSerial(); | ||
await this._selectSerial(); | ||
return false; | ||
} | ||
|
||
|
@@ -623,7 +658,7 @@ export class ArduinoApp { | |
return false; | ||
} | ||
if (!dc.port) { | ||
await selectSerial(); | ||
await this._selectSerial(); | ||
return false; | ||
} | ||
if (!this.useArduinoCli()) { | ||
|
@@ -665,7 +700,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(); | ||
|
@@ -835,4 +870,126 @@ 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<boolean> { | ||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm understanding this right, this forces someone to select the port, and then have to restart the burn bootloader process. Is this what you were going after? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I was using the same flow found in |
||
} | ||
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"); | ||
|
||
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(); | ||
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; | ||
}); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is located in a try catch, but the _burnBootloader method seems to primarly use the boolean return value to signal success or failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I mostly followed a similar flow to
build()
which handles potentially unhandled exceptions.Originally the code was like the following:
I figured that a try catch finally approach would do the same task and appear more readable and reduce the repeated lines that set the state. The only difference being that the state is set after the operation returns.
I'm open to suggestions though!