Skip to content

Commit

Permalink
fix: custom board options handling in data store
Browse files Browse the repository at this point in the history
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta committed Feb 9, 2024
1 parent 9351f5f commit 3093a85
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 36 deletions.
49 changes: 30 additions & 19 deletions arduino-ide-extension/src/browser/boards/boards-data-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
Programmer,
isBoardIdentifierChangeEvent,
isProgrammer,
sanitizeFqbn,
} from '../../common/protocol';
import { notEmpty } from '../../common/utils';
import type {
Expand Down Expand Up @@ -130,7 +131,7 @@ export class BoardsDataStore
if (!fqbn) {
return undefined;
} else {
const data = await this.getData(fqbn);
const data = await this.getData(sanitizeFqbn(fqbn));
if (data === BoardsDataStore.Data.EMPTY) {
return undefined;
}
Expand Down Expand Up @@ -228,15 +229,19 @@ export class BoardsDataStore
fqbn: string;
selectedProgrammer: Programmer;
}): Promise<boolean> {
const storedData = deepClone(await this.getData(fqbn));
const sanitizedFQBN = sanitizeFqbn(fqbn);
const storedData = deepClone(await this.getData(sanitizedFQBN));
const { programmers } = storedData;
if (!programmers.find((p) => Programmer.equals(selectedProgrammer, p))) {
return false;
}

const data = { ...storedData, selectedProgrammer };
await this.setData({ fqbn, data });
this.fireChanged({ fqbn, data });
const change: BoardsDataStoreChange = {
fqbn: sanitizedFQBN,
data: { ...storedData, selectedProgrammer },
};
await this.setData(change);
this.fireChanged(change);
return true;
}

Expand All @@ -246,24 +251,26 @@ export class BoardsDataStore
return false;
}

const mutableData = deepClone(await this.getData(fqbn));
const sanitizedFQBN = sanitizeFqbn(fqbn);
const mutableData = deepClone(await this.getData(sanitizedFQBN));
let didChange = false;

for (const { option, selectedValue } of optionsToUpdate) {
const { configOptions } = mutableData;
const configOption = configOptions.find((c) => c.option === option);
if (configOption) {
let updated = false;
for (const value of configOption.values) {
const mutable: Mutable<ConfigValue> = value;
if (mutable.value === selectedValue) {
mutable.selected = true;
updated = true;
} else {
mutable.selected = false;
}
}
if (updated) {
const configOptionValueIndex = configOption.values.findIndex(
(configOptionValue) => configOptionValue.value === selectedValue
);
if (configOptionValueIndex >= 0) {
// unselect all
configOption.values
.map((value) => value as Mutable<ConfigValue>)
.forEach((value) => (value.selected = false));
const mutableConfigValue: Mutable<ConfigValue> =
configOption.values[configOptionValueIndex];
// make the new value `selected`
mutableConfigValue.selected = true;
didChange = true;
}
}
Expand All @@ -273,8 +280,12 @@ export class BoardsDataStore
return false;
}

await this.setData({ fqbn, data: mutableData });
this.fireChanged({ fqbn, data: mutableData });
const change: BoardsDataStoreChange = {
fqbn: sanitizedFQBN,
data: mutableData,
};
await this.setData(change);
this.fireChanged(change);
return true;
}

Expand Down
51 changes: 40 additions & 11 deletions arduino-ide-extension/src/browser/boards/boards-service-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Emitter } from '@theia/core/lib/common/event';
import { ILogger } from '@theia/core/lib/common/logger';
import { MessageService } from '@theia/core/lib/common/message-service';
import { nls } from '@theia/core/lib/common/nls';
import { deepClone } from '@theia/core/lib/common/objects';
import { Deferred } from '@theia/core/lib/common/promise-util';
import type { Mutable } from '@theia/core/lib/common/types';
import { inject, injectable, optional } from '@theia/core/shared/inversify';
Expand All @@ -21,31 +22,32 @@ import {
} from '@theia/output/lib/browser/output-channel';
import {
BoardIdentifier,
boardIdentifierEquals,
BoardUserField,
BoardWithPackage,
BoardsConfig,
BoardsConfigChangeEvent,
BoardsPackage,
BoardsService,
BoardUserField,
BoardWithPackage,
DetectedPorts,
Port,
PortIdentifier,
boardIdentifierEquals,
emptyBoardsConfig,
isBoardIdentifier,
isBoardIdentifierChangeEvent,
isPortIdentifier,
isPortIdentifierChangeEvent,
Port,
PortIdentifier,
portIdentifierEquals,
sanitizeFqbn,
serializePlatformIdentifier,
} from '../../common/protocol';
import {
BoardList,
BoardListHistory,
createBoardList,
EditBoardsConfigActionParams,
isBoardListHistory,
SelectBoardsConfigActionParams,
createBoardList,
isBoardListHistory,
} from '../../common/protocol/board-list';
import type { Defined } from '../../common/types';
import type {
Expand Down Expand Up @@ -104,6 +106,21 @@ type BoardListHistoryUpdateResult =
type BoardToSelect = BoardIdentifier | undefined | 'ignore-board';
type PortToSelect = PortIdentifier | undefined | 'ignore-port';

function sanitizeBoardToSelectFQBN(board: BoardToSelect): BoardToSelect {
if (isBoardIdentifier(board)) {
return sanitizeBoardIdentifierFQBN(board);
}
return board;
}
function sanitizeBoardIdentifierFQBN(board: BoardIdentifier): BoardIdentifier {
if (board.fqbn) {
const copy: Mutable<BoardIdentifier> = deepClone(board);
copy.fqbn = sanitizeFqbn(board.fqbn);
return copy;
}
return board;
}

interface UpdateBoardListHistoryParams {
readonly portToSelect: PortToSelect;
readonly boardToSelect: BoardToSelect;
Expand Down Expand Up @@ -356,7 +373,8 @@ export class BoardsServiceProvider
portToSelect !== 'ignore-port' &&
!portIdentifierEquals(portToSelect, previousSelectedPort);
const boardDidChangeEvent = boardDidChange
? { selectedBoard: boardToSelect, previousSelectedBoard }
? // The change event must always contain any custom board options. Hence the board to select is not sanitized.
{ selectedBoard: boardToSelect, previousSelectedBoard }
: undefined;
const portDidChangeEvent = portDidChange
? { selectedPort: portToSelect, previousSelectedPort }
Expand All @@ -377,11 +395,22 @@ export class BoardsServiceProvider
return false;
}

this.maybeUpdateBoardListHistory({ portToSelect, boardToSelect });
this.maybeUpdateBoardsData({ boardToSelect, reason });
// unlike for the board change event, every persistent state must not contain custom board config options in the FQBN
const sanitizedBoardToSelect = sanitizeBoardToSelectFQBN(boardToSelect);

this.maybeUpdateBoardListHistory({
portToSelect,
boardToSelect: sanitizedBoardToSelect,
});
this.maybeUpdateBoardsData({
boardToSelect: sanitizedBoardToSelect,
reason,
});

if (isBoardIdentifierChangeEvent(event)) {
this._boardsConfig.selectedBoard = event.selectedBoard;
this._boardsConfig.selectedBoard = event.selectedBoard
? sanitizeBoardIdentifierFQBN(event.selectedBoard)
: event.selectedBoard;
}
if (isPortIdentifierChangeEvent(event)) {
this._boardsConfig.selectedPort = event.selectedPort;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
BoardsService,
ExecutableService,
isBoardIdentifierChangeEvent,
sanitizeFqbn,
} from '../../common/protocol';
import { FQBN } from 'fqbn';
import {
defaultAsyncWorkers,
maxAsyncWorkers,
Expand Down Expand Up @@ -158,11 +158,11 @@ export class InoLanguage extends SketchContribution {
this.notificationCenter.onDidReinitialize(() => forceRestart()),
this.boardDataStore.onDidChange((event) => {
if (this.languageServerFqbn) {
const sanitizedFqbn = new FQBN(this.languageServerFqbn).sanitize();
const sanitizedFQBN = sanitizeFqbn(this.languageServerFqbn);
// The incoming FQBNs might contain custom boards configs, sanitize them before the comparison.
// https://github.com/arduino/arduino-ide/pull/2113#pullrequestreview-1499998328
const matchingChange = event.changes.find((change) =>
new FQBN(change.fqbn).sanitize().equals(sanitizedFqbn)
const matchingChange = event.changes.find(
(change) => sanitizedFQBN === sanitizeFqbn(change.fqbn)
);
const { boardsConfig } = this.boardsServiceProvider;
if (
Expand Down
9 changes: 9 additions & 0 deletions arduino-ide-extension/src/common/protocol/boards-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,15 @@ export namespace Board {
}
}

/**
* Converts the `VENDOR:ARCHITECTURE:BOARD_ID[:MENU_ID=OPTION_ID[,MENU2_ID=OPTION_ID ...]]` FQBN to
* `VENDOR:ARCHITECTURE:BOARD_ID` format.
* See the details of the `{build.fqbn}` entry in the [specs](https://arduino.github.io/arduino-cli/latest/platform-specification/#global-predefined-properties).
*/
export function sanitizeFqbn(fqbn: string): string {
return new FQBN(fqbn).sanitize().toString();
}

export type PlatformIdentifier = Readonly<{ vendorId: string; arch: string }>;
export function createPlatformIdentifier(
board: BoardWithPackage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,36 @@ describe('board-service-provider', () => {
expect(events).deep.equals([expectedEvent]);
});

it('should ignore custom board configs from the FQBN', () => {
boardsServiceProvider['_boardsConfig'] = {
selectedBoard: uno,
selectedPort: unoSerialPort,
};
const events: BoardsConfigChangeEvent[] = [];
toDisposeAfterEach.push(
boardsServiceProvider.onBoardsConfigDidChange((event) =>
events.push(event)
)
);
const mkr1000WithCustomOptions = {
...mkr1000,
fqbn: `${mkr1000.fqbn}:c1=v1`,
};
const didUpdate = boardsServiceProvider.updateConfig(
mkr1000WithCustomOptions
);
expect(didUpdate).to.be.true;
const expectedEvent: BoardIdentifierChangeEvent = {
previousSelectedBoard: uno,
selectedBoard: mkr1000WithCustomOptions, // the even has the custom board options
};
expect(events).deep.equals([expectedEvent]);
// the persisted state does not have the config options property
expect(boardsServiceProvider.boardsConfig.selectedBoard?.fqbn).to.equal(
mkr1000.fqbn
);
});

it('should not update the board if did not change (board identifier)', () => {
boardsServiceProvider['_boardsConfig'] = {
selectedBoard: uno,
Expand Down
Loading

0 comments on commit 3093a85

Please sign in to comment.