Skip to content

Commit f80b38f

Browse files
maddogjtadiazulay
andauthored
Populate the list of programmers by parsing programmers.txt for each package (microsoft#1129)
* Improved handling of programmer selection - Selected programmer is now saved to and loaded from the arduino.json file - Arduino.json is monitored for changes, and changing file will update selected programmer & ui - Programmer selection UI now shows both the friendly name of the programmer, as well as the arduino name - Minor fix to deviceContexts to fire change events after all states are modified - Layed groundwork to support querying list of programmers for the current board from arduino toolchain * Parse the list of programmers from packages * Tests for parsing programmers * Show board specific list of programmers when selecting Populate the selected programmer and it's display name using list of programmers provided by BoardManager. When selecting programmer, only present the user a list of programmers relevant to the current board. * Initial set of tests for ProgrammerManager * add support for cli * fix hardcoded package name for programmers * adds programmer.key back to support arduino IDE * fix handeling of programmer name in ide and cli Co-authored-by: Adi Azulay <[email protected]>
1 parent 8bb1130 commit f80b38f

File tree

12 files changed

+444
-36
lines changed

12 files changed

+444
-36
lines changed

src/arduino/boardManager.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import { DeviceContext } from "../deviceContext";
1313
import { ArduinoApp } from "./arduino";
1414
import { IArduinoSettings } from "./arduinoSettings";
1515
import { boardEqual, parseBoardDescriptor } from "./board";
16-
import { BoardConfigResult, IBoard, IPackage, IPlatform } from "./package";
16+
import { BoardConfigResult, IBoard, IPackage, IPlatform, IProgrammer } from "./package";
17+
import { parseProgrammerDescriptor } from "./programmer";
1718
import { VscodeSettings } from "./vscodeSettings";
1819

1920
export class BoardManager {
@@ -22,6 +23,8 @@ export class BoardManager {
2223

2324
private _platforms: IPlatform[];
2425

26+
private _programmers: Map<string, IProgrammer>;
27+
2528
private _installedPlatforms: IPlatform[];
2629

2730
private _boards: Map<string, IBoard>;
@@ -69,6 +72,9 @@ export class BoardManager {
6972

7073
// Load all supported board types
7174
this.loadInstalledBoards();
75+
this.loadInstalledProgrammers();
76+
this.updateStatusBar();
77+
this._boardConfigStatusBar.show();
7278

7379
const dc = DeviceContext.getInstance();
7480
dc.onChangeBoard(() => this.onDeviceContextBoardChange());
@@ -150,6 +156,10 @@ export class BoardManager {
150156
return this._boards;
151157
}
152158

159+
public get installedProgrammers(): Map<string, IProgrammer> {
160+
return this._programmers;
161+
}
162+
153163
public get currentBoard(): IBoard {
154164
return this._currentBoard;
155165
}
@@ -253,6 +263,7 @@ export class BoardManager {
253263
this._installedPlatforms.push(existingPlatform);
254264
}
255265
this.loadInstalledBoardsFromPlatform(existingPlatform);
266+
this.loadInstalledProgrammersFromPlatform(existingPlatform);
256267
}
257268
}
258269
}
@@ -470,6 +481,23 @@ export class BoardManager {
470481
}
471482
}
472483

484+
private loadInstalledProgrammers(): void {
485+
this._programmers = new Map<string, IProgrammer>();
486+
this._installedPlatforms.forEach((plat) => {
487+
this.loadInstalledProgrammersFromPlatform(plat);
488+
});
489+
}
490+
491+
private loadInstalledProgrammersFromPlatform(plat: IPlatform) {
492+
if (util.fileExistsSync(path.join(plat.rootBoardPath, "programmers.txt"))) {
493+
const programmersContent = fs.readFileSync(path.join(plat.rootBoardPath, "programmers.txt"), "utf8");
494+
const res = parseProgrammerDescriptor(programmersContent, plat);
495+
res.forEach((prog) => {
496+
this._programmers.set(prog.name, prog);
497+
});
498+
}
499+
}
500+
473501
private listBoards(): IBoard[] {
474502
const result = [];
475503
this._boards.forEach((b) => {

src/arduino/package.ts

+31
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,34 @@ export interface IBoard {
267267
*/
268268
getPackageName();
269269
}
270+
271+
/**
272+
* Interface for classes that represent an Arduino supported programmer.
273+
*
274+
* @interface
275+
*/
276+
export interface IProgrammer {
277+
/**
278+
* Unique key that represent the programmer in the package:name.
279+
* @property {string}
280+
*/
281+
key: string;
282+
283+
/**
284+
* Programmer name for Arduino compilation such as `avrisp`, `atmel_ice`
285+
* @property {string}
286+
*/
287+
name: string;
288+
289+
/**
290+
* The human readable name displayed in the Arduino programmer selection menu
291+
* @property {string}
292+
*/
293+
displayName: string;
294+
295+
/**
296+
* Reference to the platform that contains this board.
297+
* @prop {IPlatform}
298+
*/
299+
platform: IPlatform;
300+
}

src/arduino/programmer.ts

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import { IPlatform, IProgrammer } from "./package";
5+
6+
export function parseProgrammerDescriptor(programmerDescriptor: string, plat: IPlatform): Map<string, IProgrammer> {
7+
const progrmmerLineRegex = /([^\.]+)\.(\S+)=(.+)/;
8+
9+
const result = new Map<string, IProgrammer>();
10+
const lines = programmerDescriptor.split(/[\r|\r\n|\n]/);
11+
const menuMap = new Map<string, string>();
12+
13+
lines.forEach((line) => {
14+
// Ignore comments.
15+
if (line.startsWith("#")) {
16+
return;
17+
}
18+
19+
const match = progrmmerLineRegex.exec(line);
20+
if (match && match.length > 3) {
21+
let programmer = result.get(match[1]);
22+
if (!programmer) {
23+
programmer = new Programmer(match[1], plat);
24+
result.set(programmer.name
25+
, programmer);
26+
}
27+
if (match[2] === "name") {
28+
programmer.displayName = match[3].trim();
29+
}
30+
}
31+
});
32+
return result;
33+
}
34+
35+
export class Programmer implements IProgrammer {
36+
constructor(private _name: string,
37+
private _platform: IPlatform,
38+
private _displayName: string = _name) {
39+
}
40+
41+
public get name(): string {
42+
return this._name;
43+
}
44+
45+
public get platform(): IPlatform {
46+
return this._platform;
47+
}
48+
49+
public get displayName(): string {
50+
return this._displayName;
51+
}
52+
53+
public set displayName(value: string) {
54+
this._displayName = value;
55+
}
56+
57+
/**
58+
* @returns {string} Return programmer key in format packageName:name
59+
*/
60+
public get key() {
61+
return `${this.getPackageName}:${this.name}`;
62+
}
63+
64+
private get getPackageName(): string {
65+
return this.platform.packageName ? this.platform.packageName : this.platform.package.name;
66+
}
67+
}

src/arduino/programmerManager.ts

+41-33
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,15 @@ import * as constants from "../common/constants";
33
import { DeviceContext } from "../deviceContext";
44
import { ArduinoApp } from "./arduino";
55
import { IArduinoSettings } from "./arduinoSettings";
6+
import { IBoard, IProgrammer } from "./package";
67

78
export class ProgrammerManager {
8-
private _programmervalue: string;
9+
public static notFoundDisplayValue: string = "<Select Programmer>";
910

10-
private _programmerStatusBar: vscode.StatusBarItem;
11+
private _programmerValue: string;
12+
private _programmerDisplayName: string;
1113

12-
// Static list of 'available' programmers. This should be repopulated by the currently selected board type.
13-
private _availableProgrammers = {
14-
avrisp: "AVR ISP",
15-
avrispmkii: "AVRISP mkII",
16-
usbtinyisp: "USBtinyISP",
17-
arduinoisp: "ArduinoISP",
18-
usbasp: "USBasp",
19-
parallel: "Parallel Programmer",
20-
arduinoasisp: "Arduino as ISP",
21-
usbGemma: "Arduino Gemma",
22-
buspirate: "BusPirate as ISP",
23-
stk500: "Atmel STK500 development board",
24-
jtag3isp: "Atmel JTAGICE3 (ISP mode)",
25-
jtag3: "Atmel JTAGICE3 (JTAG mode)",
26-
atmel_ice: "Atmel-ICE (AVR)",
27-
};
14+
private _programmerStatusBar: vscode.StatusBarItem;
2815

2916
constructor(private _settings: IArduinoSettings, private _arduinoApp: ArduinoApp) {
3017
this._programmerStatusBar = vscode.window.createStatusBarItem(
@@ -41,7 +28,11 @@ export class ProgrammerManager {
4128
}
4229

4330
public get currentProgrammer(): string {
44-
return this._programmervalue;
31+
return this._programmerValue;
32+
}
33+
34+
public get currentDisplayName(): string {
35+
return this._programmerDisplayName;
4536
}
4637

4738
/**
@@ -50,10 +41,10 @@ export class ProgrammerManager {
5041
* List format: programmer_name:friendly_name
5142
*/
5243
public async selectProgrammer() {
53-
const selectionItems = Object.keys(this._availableProgrammers).map(
44+
const selectionItems = this.getAvailableProgrammers(this._arduinoApp.boardManager.currentBoard).map(
5445
(programmer) => ({
55-
label: this.getFriendlyName(programmer),
56-
description: programmer,
46+
label: programmer.displayName,
47+
description: programmer.name,
5748
programmer }));
5849
const chosen = await vscode.window.showQuickPick(selectionItems, {
5950
placeHolder: "Select programmer",
@@ -62,20 +53,37 @@ export class ProgrammerManager {
6253
return;
6354
}
6455

65-
this.setProgrammerValue(chosen.programmer);
66-
const dc = DeviceContext.getInstance();
67-
dc.programmer = chosen.programmer;
56+
this.setProgrammerValue(chosen.programmer.name);
57+
DeviceContext.getInstance().programmer = this._programmerValue;
58+
}
59+
60+
private setProgrammerValue(programmerName: string | null) {
61+
const programmer = this._arduinoApp.boardManager.installedProgrammers.get(programmerName);
62+
this._programmerValue = this._settings.useArduinoCli ? programmerName : programmer ? programmer.key : programmerName;
63+
this._programmerDisplayName = this._programmerValue
64+
? this.getDisplayName(programmerName)
65+
: ProgrammerManager.notFoundDisplayValue;
66+
this._programmerStatusBar.text = this._programmerDisplayName;
6867
}
6968

70-
private setProgrammerValue(programmer: string | null) {
71-
this._programmervalue = programmer;
72-
this._programmerStatusBar.text = this._programmervalue
73-
? this.getFriendlyName(this._programmervalue)
74-
: "<Select Programmer>";
69+
private getDisplayName(programmerName: string): string {
70+
const programmer = this._arduinoApp.boardManager.installedProgrammers.get(programmerName);
71+
return programmer ? programmer.displayName : programmerName;
7572
}
7673

77-
private getFriendlyName(programmer: string): string {
78-
const friendlyName = this._availableProgrammers[programmer];
79-
return friendlyName ? friendlyName : programmer;
74+
private getAvailableProgrammers(currentBoard: IBoard): IProgrammer[] {
75+
if (!currentBoard || !currentBoard.platform) {
76+
return [];
77+
}
78+
79+
// Filter the list of all programmers to those that share the same platform as the board
80+
const availableProgrammers: IProgrammer[] = [];
81+
for (const programmer of this._arduinoApp.boardManager.installedProgrammers.values()) {
82+
if (programmer.platform === currentBoard.platform) {
83+
availableProgrammers.push(programmer);
84+
}
85+
}
86+
87+
return availableProgrammers;
8088
}
8189
}

src/deviceContext.ts

+14
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable {
9292

9393
private _sketchStatusBar: vscode.StatusBarItem;
9494

95+
private _prebuild: string;
96+
97+
private _programmer: string;
98+
99+
private _suppressSaveContext: boolean = false;
100+
95101
/**
96102
* @constructor
97103
*/
@@ -264,6 +270,14 @@ export class DeviceContext implements IDeviceContext, vscode.Disposable {
264270
this.saveContext();
265271
}
266272

273+
public get suppressSaveContext() {
274+
return this._suppressSaveContext;
275+
}
276+
277+
public set suppressSaveContext(value: boolean) {
278+
this._suppressSaveContext = value;
279+
}
280+
267281
public get buildPreferences() {
268282
return this._settings.buildPreferences.value;
269283
}

test/boardmanager.test.ts

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ArduinoSettings } from "../src/arduino/arduinoSettings";
1212
import { parseBoardDescriptor } from "../src/arduino/board";
1313
import { BoardManager } from "../src/arduino/boardManager";
1414
import { IPlatform } from "../src/arduino/package";
15+
import { parseProgrammerDescriptor } from "../src/arduino/programmer";
1516
import * as util from "../src/common/util";
1617

1718
suite("Arduino: Board Manager.", () => {
@@ -67,6 +68,14 @@ suite("Arduino: Board Manager.", () => {
6768
"should parse installed boards from custom packages ($sketchbook/hardware directory)");
6869
});
6970

71+
test("should be able to load installed programmers", () => {
72+
assert.equal(boardManager.installedProgrammers.size, 17, `Expected to find programmers for dummy & AVR boards`);
73+
assert.ok(boardManager.installedProgrammers.get("avrispmkii"),
74+
"should parse installed programmers from Arduino IDE built-in packages");
75+
assert.ok(boardManager.installedProgrammers.get("esp8266_dummy"),
76+
"should parse installed programmers from custom packages ($sketchbook/hardware directory)");
77+
});
78+
7079
test("should parse boards.txt correctly", () => {
7180
const arduinoAvrBoard = fs.readFileSync(Path.join(Resources.mockedIDEPackagePath, "arduino/avr/boards.txt"), "utf8");
7281
const platform = {
@@ -92,6 +101,26 @@ suite("Arduino: Board Manager.", () => {
92101
assert.equal(diecimilaBoard.customConfig, "cpu=atmega328");
93102
});
94103

104+
test("should parse programmers.txt correctly", () => {
105+
const arduinoAvrBoard = fs.readFileSync(Path.join(Resources.mockedIDEPackagePath, "arduino/avr/programmers.txt"), "utf8");
106+
const platform = {
107+
name: "Arduino AVR Boards",
108+
architecture: "avr",
109+
package: {
110+
name: "arduino",
111+
},
112+
};
113+
const programmerDescriptors = parseProgrammerDescriptor(arduinoAvrBoard, <IPlatform> platform);
114+
115+
const avrispmkii = programmerDescriptors.get("avrispmkii");
116+
assert.equal(avrispmkii.name, "avrispmkii");
117+
assert.equal(avrispmkii.displayName, "AVRISP mkII");
118+
119+
const usbGemma = programmerDescriptors.get("usbGemma");
120+
assert.equal(usbGemma.name, "usbGemma");
121+
assert.equal(usbGemma.displayName, "Arduino Gemma");
122+
});
123+
95124
test("should parse platform.txt correctly", () => {
96125
const platformConfig = util.parseConfigFile(Path.join(Resources.mockedSketchbookPath, "hardware/esp8266/esp8266/platform.txt"));
97126
assert.equal(platformConfig.get("name"), "ESP8266 Modules");

test/devicecontext.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ suite("Arduino: Device Context config", () => {
1717
assert.equal(deviceContext.configuration, "cpu=atmega328");
1818
assert.equal(deviceContext.output, null);
1919
assert.equal(deviceContext.debugger_, null);
20+
assert.equal(deviceContext.programmer, "arduino:jtag3isp");
2021
done();
2122
});
2223
} catch (error) {

0 commit comments

Comments
 (0)