mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-04-27 08:47:18 +00:00

handle cornercase when 2 packages are associated to the same board updated cli version and grpc support deprecated cores in the boards manager bump cli version Bump ArduinoCLI version to latest release Add package version in notification
322 lines
13 KiB
TypeScript
322 lines
13 KiB
TypeScript
import { injectable, inject, named } from 'inversify';
|
|
import { ILogger } from '@theia/core/lib/common/logger';
|
|
import { notEmpty } from '@theia/core/lib/common/objects';
|
|
import {
|
|
BoardsService,
|
|
Installable,
|
|
BoardsPackage, Board, Port, BoardDetails, Tool, ConfigOption, ConfigValue, Programmer, ResponseService, NotificationServiceServer, AvailablePorts, BoardWithPackage
|
|
} from '../common/protocol';
|
|
import {
|
|
PlatformInstallRequest, PlatformListRequest, PlatformListResponse, PlatformSearchRequest,
|
|
PlatformSearchResponse, PlatformUninstallRequest
|
|
} from './cli-protocol/cc/arduino/cli/commands/v1/core_pb';
|
|
import { Platform } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
|
|
import { BoardDiscovery } from './board-discovery';
|
|
import { CoreClientAware } from './core-client-provider';
|
|
import { BoardDetailsRequest, BoardDetailsResponse, BoardSearchRequest } from './cli-protocol/cc/arduino/cli/commands/v1/board_pb';
|
|
import { ListProgrammersAvailableForUploadRequest, ListProgrammersAvailableForUploadResponse } from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
|
import { InstallWithProgress } from './grpc-installable';
|
|
|
|
@injectable()
|
|
export class BoardsServiceImpl extends CoreClientAware implements BoardsService {
|
|
|
|
@inject(ILogger)
|
|
protected logger: ILogger;
|
|
|
|
@inject(ILogger)
|
|
@named('discovery')
|
|
protected discoveryLogger: ILogger;
|
|
|
|
@inject(ResponseService)
|
|
protected readonly responseService: ResponseService;
|
|
|
|
@inject(NotificationServiceServer)
|
|
protected readonly notificationService: NotificationServiceServer;
|
|
|
|
@inject(BoardDiscovery)
|
|
protected readonly boardDiscovery: BoardDiscovery;
|
|
|
|
async getState(): Promise<AvailablePorts> {
|
|
return this.boardDiscovery.state;
|
|
}
|
|
|
|
async getAttachedBoards(): Promise<Board[]> {
|
|
return this.boardDiscovery.getAttachedBoards();
|
|
}
|
|
|
|
async getAvailablePorts(): Promise<Port[]> {
|
|
return this.boardDiscovery.getAvailablePorts();
|
|
}
|
|
|
|
async getBoardDetails(options: { fqbn: string }): Promise<BoardDetails | undefined> {
|
|
const coreClient = await this.coreClient();
|
|
const { client, instance } = coreClient;
|
|
const { fqbn } = options;
|
|
const detailsReq = new BoardDetailsRequest();
|
|
detailsReq.setInstance(instance);
|
|
detailsReq.setFqbn(fqbn);
|
|
const detailsResp = await new Promise<BoardDetailsResponse | undefined>((resolve, reject) => client.boardDetails(detailsReq, (err, resp) => {
|
|
if (err) {
|
|
// Required cores are not installed manually: https://github.com/arduino/arduino-cli/issues/954
|
|
if ((err.message.indexOf('missing platform release') !== -1 && err.message.indexOf('referenced by board') !== -1)
|
|
// Platform is not installed.
|
|
|| err.message.indexOf('platform') !== -1 && err.message.indexOf('not installed') !== -1) {
|
|
resolve(undefined);
|
|
return;
|
|
}
|
|
// It's a hack to handle https://github.com/arduino/arduino-cli/issues/1262 gracefully.
|
|
if (err.message.indexOf('unknown package') !== -1) {
|
|
resolve(undefined);
|
|
return;
|
|
}
|
|
reject(err);
|
|
return;
|
|
}
|
|
resolve(resp);
|
|
}));
|
|
|
|
if (!detailsResp) {
|
|
return undefined;
|
|
}
|
|
|
|
const debuggingSupported = detailsResp.getDebuggingSupported();
|
|
|
|
const requiredTools = detailsResp.getToolsDependenciesList().map(t => <Tool>{
|
|
name: t.getName(),
|
|
packager: t.getPackager(),
|
|
version: t.getVersion()
|
|
});
|
|
|
|
const configOptions = detailsResp.getConfigOptionsList().map(c => <ConfigOption>{
|
|
label: c.getOptionLabel(),
|
|
option: c.getOption(),
|
|
values: c.getValuesList().map(v => <ConfigValue>{
|
|
value: v.getValue(),
|
|
label: v.getValueLabel(),
|
|
selected: v.getSelected()
|
|
})
|
|
});
|
|
|
|
const listReq = new ListProgrammersAvailableForUploadRequest();
|
|
listReq.setInstance(instance);
|
|
listReq.setFqbn(fqbn);
|
|
const listResp = await new Promise<ListProgrammersAvailableForUploadResponse>((resolve, reject) => client.listProgrammersAvailableForUpload(listReq, (err, resp) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
resolve(resp);
|
|
}));
|
|
|
|
const programmers = listResp.getProgrammersList().map(p => <Programmer>{
|
|
id: p.getId(),
|
|
name: p.getName(),
|
|
platform: p.getPlatform()
|
|
});
|
|
|
|
let VID = 'N/A';
|
|
let PID = 'N/A';
|
|
const usbId = detailsResp.getIdentificationPrefsList().map(item => item.getUsbId()).find(notEmpty);
|
|
if (usbId) {
|
|
VID = usbId.getVid();
|
|
PID = usbId.getPid();
|
|
}
|
|
|
|
return {
|
|
fqbn,
|
|
requiredTools,
|
|
configOptions,
|
|
programmers,
|
|
debuggingSupported,
|
|
VID,
|
|
PID
|
|
};
|
|
}
|
|
|
|
async getBoardPackage(options: { id: string }): Promise<BoardsPackage | undefined> {
|
|
const { id: expectedId } = options;
|
|
if (!expectedId) {
|
|
return undefined;
|
|
}
|
|
const packages = await this.search({ query: expectedId });
|
|
return packages.find(({ id }) => id === expectedId);
|
|
}
|
|
|
|
async getContainerBoardPackage(options: { fqbn: string }): Promise<BoardsPackage | undefined> {
|
|
const { fqbn: expectedFqbn } = options;
|
|
if (!expectedFqbn) {
|
|
return undefined;
|
|
}
|
|
const packages = await this.search({});
|
|
return packages.find(({ boards }) => boards.some(({ fqbn }) => fqbn === expectedFqbn));
|
|
}
|
|
|
|
async searchBoards({ query }: { query?: string }): Promise<BoardWithPackage[]> {
|
|
const { instance, client } = await this.coreClient();
|
|
const req = new BoardSearchRequest();
|
|
req.setSearchArgs(query || '');
|
|
req.setInstance(instance);
|
|
const boards = await new Promise<BoardWithPackage[]>((resolve, reject) => {
|
|
client.boardSearch(req, (error, resp) => {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
const boards: Array<BoardWithPackage> = [];
|
|
for (const board of resp.getBoardsList()) {
|
|
const platform = board.getPlatform();
|
|
if (platform) {
|
|
boards.push({
|
|
name: board.getName(),
|
|
fqbn: board.getFqbn(),
|
|
packageId: platform.getId(),
|
|
packageName: platform.getName()
|
|
});
|
|
}
|
|
}
|
|
resolve(boards);
|
|
})
|
|
});
|
|
return boards;
|
|
}
|
|
|
|
async search(options: { query?: string }): Promise<BoardsPackage[]> {
|
|
const coreClient = await this.coreClient();
|
|
const { client, instance } = coreClient;
|
|
|
|
const installedPlatformsReq = new PlatformListRequest();
|
|
installedPlatformsReq.setInstance(instance);
|
|
const installedPlatformsResp = await new Promise<PlatformListResponse>((resolve, reject) =>
|
|
client.platformList(installedPlatformsReq, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp))
|
|
);
|
|
const installedPlatforms = installedPlatformsResp.getInstalledPlatformsList();
|
|
|
|
const req = new PlatformSearchRequest();
|
|
req.setSearchArgs(options.query || '');
|
|
req.setAllVersions(true);
|
|
req.setInstance(instance);
|
|
const resp = await new Promise<PlatformSearchResponse>((resolve, reject) => client.platformSearch(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
|
|
const packages = new Map<string, BoardsPackage>();
|
|
const toPackage = (platform: Platform) => {
|
|
let installedVersion: string | undefined;
|
|
const matchingPlatform = installedPlatforms.find(ip => ip.getId() === platform.getId());
|
|
if (!!matchingPlatform) {
|
|
installedVersion = matchingPlatform.getInstalled();
|
|
}
|
|
return {
|
|
id: platform.getId(),
|
|
name: platform.getName(),
|
|
author: platform.getMaintainer(),
|
|
availableVersions: [platform.getLatest()],
|
|
description: platform.getBoardsList().map(b => b.getName()).join(', '),
|
|
installable: true,
|
|
deprecated: platform.getDeprecated(),
|
|
summary: 'Boards included in this package:',
|
|
installedVersion,
|
|
boards: platform.getBoardsList().map(b => <Board>{ name: b.getName(), fqbn: b.getFqbn() }),
|
|
moreInfoLink: platform.getWebsite()
|
|
}
|
|
}
|
|
|
|
// We must group the cores by ID, and sort platforms by, first the installed version, then version alphabetical order.
|
|
// Otherwise we lose the FQBN information.
|
|
const groupedById: Map<string, Platform[]> = new Map();
|
|
for (const platform of resp.getSearchOutputList()) {
|
|
const id = platform.getId();
|
|
if (groupedById.has(id)) {
|
|
groupedById.get(id)!.push(platform);
|
|
} else {
|
|
groupedById.set(id, [platform]);
|
|
}
|
|
}
|
|
const installedAwareVersionComparator = (left: Platform, right: Platform) => {
|
|
// XXX: we cannot rely on `platform.getInstalled()`, it is always an empty string.
|
|
const leftInstalled = !!installedPlatforms.find(ip => ip.getId() === left.getId() && ip.getInstalled() === left.getLatest());
|
|
const rightInstalled = !!installedPlatforms.find(ip => ip.getId() === right.getId() && ip.getInstalled() === right.getLatest());
|
|
if (leftInstalled && !rightInstalled) {
|
|
return -1;
|
|
}
|
|
if (!leftInstalled && rightInstalled) {
|
|
return 1;
|
|
}
|
|
return Installable.Version.COMPARATOR(left.getLatest(), right.getLatest()); // Higher version comes first.
|
|
}
|
|
for (const id of groupedById.keys()) {
|
|
groupedById.get(id)!.sort(installedAwareVersionComparator);
|
|
}
|
|
|
|
for (const id of groupedById.keys()) {
|
|
for (const platform of groupedById.get(id)!) {
|
|
const id = platform.getId();
|
|
const pkg = packages.get(id);
|
|
if (pkg) {
|
|
pkg.availableVersions.push(platform.getLatest());
|
|
pkg.availableVersions.sort(Installable.Version.COMPARATOR).reverse();
|
|
} else {
|
|
packages.set(id, toPackage(platform));
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...packages.values()];
|
|
}
|
|
|
|
async install(options: { item: BoardsPackage, progressId?: string, version?: Installable.Version }): Promise<void> {
|
|
const item = options.item;
|
|
const version = !!options.version ? options.version : item.availableVersions[0];
|
|
const coreClient = await this.coreClient();
|
|
const { client, instance } = coreClient;
|
|
|
|
const [platform, architecture] = item.id.split(':');
|
|
|
|
const req = new PlatformInstallRequest();
|
|
req.setInstance(instance);
|
|
req.setArchitecture(architecture);
|
|
req.setPlatformPackage(platform);
|
|
req.setVersion(version);
|
|
|
|
console.info('>>> Starting boards package installation...', item);
|
|
const resp = client.platformInstall(req);
|
|
resp.on('data', InstallWithProgress.createDataCallback({ progressId: options.progressId, responseService: this.responseService }));
|
|
await new Promise<void>((resolve, reject) => {
|
|
resp.on('end', resolve);
|
|
resp.on('error', error => {
|
|
this.responseService.appendToOutput({ chunk: `Failed to install platform: ${item.id}.\n` });
|
|
this.responseService.appendToOutput({ chunk: error.toString() });
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
const items = await this.search({});
|
|
const updated = items.find(other => BoardsPackage.equals(other, item)) || item;
|
|
this.notificationService.notifyPlatformInstalled({ item: updated });
|
|
console.info('<<< Boards package installation done.', item);
|
|
}
|
|
|
|
async uninstall(options: { item: BoardsPackage, progressId?: string }): Promise<void> {
|
|
const { item, progressId } = options;
|
|
const coreClient = await this.coreClient();
|
|
const { client, instance } = coreClient;
|
|
|
|
const [platform, architecture] = item.id.split(':');
|
|
|
|
const req = new PlatformUninstallRequest();
|
|
req.setInstance(instance);
|
|
req.setArchitecture(architecture);
|
|
req.setPlatformPackage(platform);
|
|
|
|
console.info('>>> Starting boards package uninstallation...', item);
|
|
const resp = client.platformUninstall(req);
|
|
resp.on('data', InstallWithProgress.createDataCallback({ progressId, responseService: this.responseService }));
|
|
await new Promise<void>((resolve, reject) => {
|
|
resp.on('end', resolve);
|
|
resp.on('error', reject);
|
|
});
|
|
|
|
// Here, unlike at `install` we send out the argument `item`. Otherwise, we would not know about the board FQBN.
|
|
this.notificationService.notifyPlatformUninstalled({ item });
|
|
console.info('<<< Boards package uninstallation done.', item);
|
|
}
|
|
|
|
}
|