Skip to content

Commit 3093a85

Browse files
author
Akos Kitta
committed
fix: custom board options handling in data store
Signed-off-by: Akos Kitta <[email protected]>
1 parent 9351f5f commit 3093a85

File tree

6 files changed

+333
-36
lines changed

6 files changed

+333
-36
lines changed

arduino-ide-extension/src/browser/boards/boards-data-store.ts

+30-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
Programmer,
2222
isBoardIdentifierChangeEvent,
2323
isProgrammer,
24+
sanitizeFqbn,
2425
} from '../../common/protocol';
2526
import { notEmpty } from '../../common/utils';
2627
import type {
@@ -130,7 +131,7 @@ export class BoardsDataStore
130131
if (!fqbn) {
131132
return undefined;
132133
} else {
133-
const data = await this.getData(fqbn);
134+
const data = await this.getData(sanitizeFqbn(fqbn));
134135
if (data === BoardsDataStore.Data.EMPTY) {
135136
return undefined;
136137
}
@@ -228,15 +229,19 @@ export class BoardsDataStore
228229
fqbn: string;
229230
selectedProgrammer: Programmer;
230231
}): Promise<boolean> {
231-
const storedData = deepClone(await this.getData(fqbn));
232+
const sanitizedFQBN = sanitizeFqbn(fqbn);
233+
const storedData = deepClone(await this.getData(sanitizedFQBN));
232234
const { programmers } = storedData;
233235
if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) {
234236
return false;
235237
}
236238

237-
const data = { ...storedData, selectedProgrammer };
238-
await this.setData({ fqbn, data });
239-
this.fireChanged({ fqbn, data });
239+
const change: BoardsDataStoreChange = {
240+
fqbn: sanitizedFQBN,
241+
data: { ...storedData, selectedProgrammer },
242+
};
243+
await this.setData(change);
244+
this.fireChanged(change);
240245
return true;
241246
}
242247

@@ -246,24 +251,26 @@ export class BoardsDataStore
246251
return false;
247252
}
248253

249-
const mutableData = deepClone(await this.getData(fqbn));
254+
const sanitizedFQBN = sanitizeFqbn(fqbn);
255+
const mutableData = deepClone(await this.getData(sanitizedFQBN));
250256
let didChange = false;
251257

252258
for (const { option, selectedValue } of optionsToUpdate) {
253259
const { configOptions } = mutableData;
254260
const configOption = configOptions.find((c) => c.option === option);
255261
if (configOption) {
256-
let updated = false;
257-
for (const value of configOption.values) {
258-
const mutable: Mutable<ConfigValue> = value;
259-
if (mutable.value === selectedValue) {
260-
mutable.selected = true;
261-
updated = true;
262-
} else {
263-
mutable.selected = false;
264-
}
265-
}
266-
if (updated) {
262+
const configOptionValueIndex = configOption.values.findIndex(
263+
(configOptionValue) => configOptionValue.value === selectedValue
264+
);
265+
if (configOptionValueIndex >= 0) {
266+
// unselect all
267+
configOption.values
268+
.map((value) => value as Mutable<ConfigValue>)
269+
.forEach((value) => (value.selected = false));
270+
const mutableConfigValue: Mutable<ConfigValue> =
271+
configOption.values[configOptionValueIndex];
272+
// make the new value `selected`
273+
mutableConfigValue.selected = true;
267274
didChange = true;
268275
}
269276
}
@@ -273,8 +280,12 @@ export class BoardsDataStore
273280
return false;
274281
}
275282

276-
await this.setData({ fqbn, data: mutableData });
277-
this.fireChanged({ fqbn, data: mutableData });
283+
const change: BoardsDataStoreChange = {
284+
fqbn: sanitizedFQBN,
285+
data: mutableData,
286+
};
287+
await this.setData(change);
288+
this.fireChanged(change);
278289
return true;
279290
}
280291

arduino-ide-extension/src/browser/boards/boards-service-provider.ts

+40-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Emitter } from '@theia/core/lib/common/event';
1212
import { ILogger } from '@theia/core/lib/common/logger';
1313
import { MessageService } from '@theia/core/lib/common/message-service';
1414
import { nls } from '@theia/core/lib/common/nls';
15+
import { deepClone } from '@theia/core/lib/common/objects';
1516
import { Deferred } from '@theia/core/lib/common/promise-util';
1617
import type { Mutable } from '@theia/core/lib/common/types';
1718
import { inject, injectable, optional } from '@theia/core/shared/inversify';
@@ -21,31 +22,32 @@ import {
2122
} from '@theia/output/lib/browser/output-channel';
2223
import {
2324
BoardIdentifier,
24-
boardIdentifierEquals,
25+
BoardUserField,
26+
BoardWithPackage,
2527
BoardsConfig,
2628
BoardsConfigChangeEvent,
2729
BoardsPackage,
2830
BoardsService,
29-
BoardUserField,
30-
BoardWithPackage,
3131
DetectedPorts,
32+
Port,
33+
PortIdentifier,
34+
boardIdentifierEquals,
3235
emptyBoardsConfig,
3336
isBoardIdentifier,
3437
isBoardIdentifierChangeEvent,
3538
isPortIdentifier,
3639
isPortIdentifierChangeEvent,
37-
Port,
38-
PortIdentifier,
3940
portIdentifierEquals,
41+
sanitizeFqbn,
4042
serializePlatformIdentifier,
4143
} from '../../common/protocol';
4244
import {
4345
BoardList,
4446
BoardListHistory,
45-
createBoardList,
4647
EditBoardsConfigActionParams,
47-
isBoardListHistory,
4848
SelectBoardsConfigActionParams,
49+
createBoardList,
50+
isBoardListHistory,
4951
} from '../../common/protocol/board-list';
5052
import type { Defined } from '../../common/types';
5153
import type {
@@ -104,6 +106,21 @@ type BoardListHistoryUpdateResult =
104106
type BoardToSelect = BoardIdentifier | undefined | 'ignore-board';
105107
type PortToSelect = PortIdentifier | undefined | 'ignore-port';
106108

109+
function sanitizeBoardToSelectFQBN(board: BoardToSelect): BoardToSelect {
110+
if (isBoardIdentifier(board)) {
111+
return sanitizeBoardIdentifierFQBN(board);
112+
}
113+
return board;
114+
}
115+
function sanitizeBoardIdentifierFQBN(board: BoardIdentifier): BoardIdentifier {
116+
if (board.fqbn) {
117+
const copy: Mutable<BoardIdentifier> = deepClone(board);
118+
copy.fqbn = sanitizeFqbn(board.fqbn);
119+
return copy;
120+
}
121+
return board;
122+
}
123+
107124
interface UpdateBoardListHistoryParams {
108125
readonly portToSelect: PortToSelect;
109126
readonly boardToSelect: BoardToSelect;
@@ -356,7 +373,8 @@ export class BoardsServiceProvider
356373
portToSelect !== 'ignore-port' &&
357374
!portIdentifierEquals(portToSelect, previousSelectedPort);
358375
const boardDidChangeEvent = boardDidChange
359-
? { selectedBoard: boardToSelect, previousSelectedBoard }
376+
? // The change event must always contain any custom board options. Hence the board to select is not sanitized.
377+
{ selectedBoard: boardToSelect, previousSelectedBoard }
360378
: undefined;
361379
const portDidChangeEvent = portDidChange
362380
? { selectedPort: portToSelect, previousSelectedPort }
@@ -377,11 +395,22 @@ export class BoardsServiceProvider
377395
return false;
378396
}
379397

380-
this.maybeUpdateBoardListHistory({ portToSelect, boardToSelect });
381-
this.maybeUpdateBoardsData({ boardToSelect, reason });
398+
// unlike for the board change event, every persistent state must not contain custom board config options in the FQBN
399+
const sanitizedBoardToSelect = sanitizeBoardToSelectFQBN(boardToSelect);
400+
401+
this.maybeUpdateBoardListHistory({
402+
portToSelect,
403+
boardToSelect: sanitizedBoardToSelect,
404+
});
405+
this.maybeUpdateBoardsData({
406+
boardToSelect: sanitizedBoardToSelect,
407+
reason,
408+
});
382409

383410
if (isBoardIdentifierChangeEvent(event)) {
384-
this._boardsConfig.selectedBoard = event.selectedBoard;
411+
this._boardsConfig.selectedBoard = event.selectedBoard
412+
? sanitizeBoardIdentifierFQBN(event.selectedBoard)
413+
: event.selectedBoard;
385414
}
386415
if (isPortIdentifierChangeEvent(event)) {
387416
this._boardsConfig.selectedPort = event.selectedPort;

arduino-ide-extension/src/browser/contributions/ino-language.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
BoardsService,
1111
ExecutableService,
1212
isBoardIdentifierChangeEvent,
13+
sanitizeFqbn,
1314
} from '../../common/protocol';
14-
import { FQBN } from 'fqbn';
1515
import {
1616
defaultAsyncWorkers,
1717
maxAsyncWorkers,
@@ -158,11 +158,11 @@ export class InoLanguage extends SketchContribution {
158158
this.notificationCenter.onDidReinitialize(() => forceRestart()),
159159
this.boardDataStore.onDidChange((event) => {
160160
if (this.languageServerFqbn) {
161-
const sanitizedFqbn = new FQBN(this.languageServerFqbn).sanitize();
161+
const sanitizedFQBN = sanitizeFqbn(this.languageServerFqbn);
162162
// The incoming FQBNs might contain custom boards configs, sanitize them before the comparison.
163163
// https://github.com/arduino/arduino-ide/pull/2113#pullrequestreview-1499998328
164-
const matchingChange = event.changes.find((change) =>
165-
new FQBN(change.fqbn).sanitize().equals(sanitizedFqbn)
164+
const matchingChange = event.changes.find(
165+
(change) => sanitizedFQBN === sanitizeFqbn(change.fqbn)
166166
);
167167
const { boardsConfig } = this.boardsServiceProvider;
168168
if (

arduino-ide-extension/src/common/protocol/boards-service.ts

+9
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,15 @@ export namespace Board {
541541
}
542542
}
543543

544+
/**
545+
* Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to
546+
* `VENDOR:ARCHITECTURE:BOARD_ID` format.
547+
* See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties).
548+
*/
549+
export function sanitizeFqbn(fqbn: string): string {
550+
return new FQBN(fqbn).sanitize().toString();
551+
}
552+
544553
export type PlatformIdentifier = Readonly<{ vendorId: string; arch: string }>;
545554
export function createPlatformIdentifier(
546555
board: BoardWithPackage

arduino-ide-extension/src/test/browser/board-service-provider.test.ts

+30
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,36 @@ describe('board-service-provider', () => {
170170
expect(events).deep.equals([expectedEvent]);
171171
});
172172

173+
it('should ignore custom board configs from the FQBN', () => {
174+
boardsServiceProvider['_boardsConfig'] = {
175+
selectedBoard: uno,
176+
selectedPort: unoSerialPort,
177+
};
178+
const events: BoardsConfigChangeEvent[] = [];
179+
toDisposeAfterEach.push(
180+
boardsServiceProvider.onBoardsConfigDidChange((event) =>
181+
events.push(event)
182+
)
183+
);
184+
const mkr1000WithCustomOptions = {
185+
...mkr1000,
186+
fqbn: `${mkr1000.fqbn}:c1=v1`,
187+
};
188+
const didUpdate = boardsServiceProvider.updateConfig(
189+
mkr1000WithCustomOptions
190+
);
191+
expect(didUpdate).to.be.true;
192+
const expectedEvent: BoardIdentifierChangeEvent = {
193+
previousSelectedBoard: uno,
194+
selectedBoard: mkr1000WithCustomOptions, // the even has the custom board options
195+
};
196+
expect(events).deep.equals([expectedEvent]);
197+
// the persisted state does not have the config options property
198+
expect(boardsServiceProvider.boardsConfig.selectedBoard?.fqbn).to.equal(
199+
mkr1000.fqbn
200+
);
201+
});
202+
173203
it('should not update the board if did not change (board identifier)', () => {
174204
boardsServiceProvider['_boardsConfig'] = {
175205
selectedBoard: uno,

0 commit comments

Comments
 (0)