Skip to content

New debug info format+electron for linux #2282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {
'electron-app/src-gen/*',
'electron-app/gen-webpack*.js',
'!electron-app/webpack.config.js',
'plugins/*',
'electron-app/plugins/*',
'arduino-ide-extension/src/node/cli-protocol',
'**/lib/*',
],
Expand Down
4 changes: 2 additions & 2 deletions arduino-ide-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,13 @@
],
"arduino": {
"arduino-cli": {
"version": "0.34.0"
"version": "0.35.0-rc.2"
},
"arduino-fwuploader": {
"version": "2.4.1"
},
"arduino-language-server": {
"version": "0.7.4"
"version": "0.7.5"
},
"clangd": {
"version": "14.0.0"
Expand Down
14 changes: 7 additions & 7 deletions arduino-ide-extension/scripts/generate-protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
const { mkdirSync, promises: fs } = require('node:fs');
const { exec } = require('./utils');
const glob = require('glob');
const { SemVer, gte, valid: validSemVer } = require('semver');
const protoc = path.dirname(require('protoc/protoc'));

const repository = await fs.mkdtemp(path.join(os.tmpdir(), 'arduino-cli-'));
Expand Down Expand Up @@ -94,13 +95,12 @@
}
*/
const versionObject = JSON.parse(versionJson);
const version = versionObject.VersionString;
if (
version &&
!version.startsWith('nightly-') &&
version !== '0.0.0-git' &&
version !== 'git-snapshot'
) {
let version = versionObject.VersionString;
if (validSemVer(version)) {
// https://github.com/arduino/arduino-cli/pull/2374
if (gte(new SemVer(version, { loose: true }), new SemVer('0.35.0-rc.1'))) {
version = `v${version}`;
}
console.log(`>>> Checking out tagged version: '${version}'...`);
exec('git', ['-C', repository, 'fetch', '--all', '--tags'], {
logStdout: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ import { TerminalFrontendContribution as TheiaTerminalFrontendContribution } fro
import { SelectionService } from '@theia/core/lib/common/selection-service';
import { CommandService } from '@theia/core/lib/common/command';
import { CorePreferences } from '@theia/core/lib/browser/core-preferences';
import { SelectProgrammer } from './contributions/select-programmer';
import { AutoSelectProgrammer } from './contributions/auto-select-programmer';

// Hack to fix copy/cut/paste issue after electron version update in Theia.
// https://github.com/eclipse-theia/theia/issues/12487
Expand Down Expand Up @@ -753,6 +755,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
Contribution.configure(bind, CreateCloudCopy);
Contribution.configure(bind, UpdateArduinoState);
Contribution.configure(bind, BoardsDataMenuUpdater);
Contribution.configure(bind, SelectProgrammer);
Contribution.configure(bind, AutoSelectProgrammer);

bindContributionProvider(bind, StartupTaskProvider);
bind(StartupTaskProvider).toService(BoardsServiceProvider); // to reuse the boards config in another window
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,28 +48,33 @@ namespace BoardsConfigComponent {
}
}

export abstract class Item<T> extends React.Component<{
class Item<T> extends React.Component<{
item: T;
label: string;
selected: boolean;
onClick: (item: T) => void;
missing?: boolean;
details?: string;
title?: string | ((item: T) => string);
}> {
override render(): React.ReactNode {
const { selected, label, missing, details } = this.props;
const { selected, label, missing, details, item } = this.props;
const classNames = ['item'];
if (selected) {
classNames.push('selected');
}
if (missing === true) {
classNames.push('missing');
}
let title = this.props.title ?? `${label}${!details ? '' : details}`;
if (typeof title === 'function') {
title = title(item);
}
return (
<div
onClick={this.onClick}
className={classNames.join(' ')}
title={`${label}${!details ? '' : details}`}
title={title}
>
<div className="label">{label}</div>
{!details ? '' : <div className="details">{details}</div>}
Expand Down Expand Up @@ -234,16 +239,28 @@ export class BoardsConfigComponent extends React.Component<
distinctBoards.set(key, board);
}
}
const title = (board: Board.Detailed): string => {
const { details, manuallyInstalled } = board;
let label = board.name;
if (details) {
label += details;
}
if (manuallyInstalled) {
label += nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)');
}
return label;
};

const boardsList = Array.from(distinctBoards.values()).map((board) => (
<Item<BoardWithPackage>
<Item<Board.Detailed>
key={toKey(board)}
item={board}
label={board.name}
details={board.details}
selected={board.selected}
onClick={this.selectBoard}
missing={board.missing}
title={title}
/>
));

Expand Down
40 changes: 26 additions & 14 deletions arduino-ide-extension/src/browser/boards/boards-data-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { ILogger } from '@theia/core/lib/common/logger';
import { deepClone } from '@theia/core/lib/common/objects';
import type { Mutable } from '@theia/core/lib/common/types';
import { inject, injectable, named } from '@theia/core/shared/inversify';
import {
BoardDetails,
BoardsService,
ConfigOption,
ConfigValue,
Programmer,
isProgrammer,
} from '../../common/protocol';
import { notEmpty } from '../../common/utils';
import { NotificationCenter } from '../notification-center';
Expand Down Expand Up @@ -43,7 +46,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
const key = this.getStorageKey(fqbn);
let data = await this.storageService.getData<ConfigOption[]>(key);
if (!data || !data.length) {
const details = await this.getBoardDetailsSafe(fqbn);
const details = await this.loadBoardDetails(fqbn);
if (details) {
data = details.configOptions;
if (data.length) {
Expand Down Expand Up @@ -91,14 +94,15 @@ export class BoardsDataStore implements FrontendApplicationContribution {
return data;
}

const boardDetails = await this.getBoardDetailsSafe(fqbn);
const boardDetails = await this.loadBoardDetails(fqbn);
if (!boardDetails) {
return BoardsDataStore.Data.EMPTY;
}

data = {
configOptions: boardDetails.configOptions,
programmers: boardDetails.programmers,
selectedProgrammer: boardDetails.programmers.find((p) => p.default),
};
await this.storageService.setData(key, data);
return data;
Expand Down Expand Up @@ -142,11 +146,12 @@ export class BoardsDataStore implements FrontendApplicationContribution {
}
let updated = false;
for (const value of configOption.values) {
if (value.value === selectedValue) {
(value as any).selected = true;
const mutable: Mutable<ConfigValue> = value;
if (mutable.value === selectedValue) {
mutable.selected = true;
updated = true;
} else {
(value as any).selected = false;
mutable.selected = false;
}
}
if (!updated) {
Expand All @@ -172,9 +177,7 @@ export class BoardsDataStore implements FrontendApplicationContribution {
return `.arduinoIDE-configOptions-${fqbn}`;
}

protected async getBoardDetailsSafe(
fqbn: string
): Promise<BoardDetails | undefined> {
async loadBoardDetails(fqbn: string): Promise<BoardDetails | undefined> {
try {
const details = this.boardsService.getBoardDetails({ fqbn });
return details;
Expand Down Expand Up @@ -213,14 +216,23 @@ export namespace BoardsDataStore {
configOptions: [],
programmers: [],
};
export function is(arg: any): arg is Data {
export function is(arg: unknown): arg is Data {
return (
!!arg &&
'configOptions' in arg &&
Array.isArray(arg['configOptions']) &&
'programmers' in arg &&
Array.isArray(arg['programmers'])
typeof arg === 'object' &&
arg !== null &&
Array.isArray((<Data>arg).configOptions) &&
Array.isArray((<Data>arg).programmers) &&
((<Data>arg).selectedProgrammer === undefined ||
isProgrammer((<Data>arg).selectedProgrammer))
);
}
}
}

export function isEmptyData(data: BoardsDataStore.Data): boolean {
return (
Boolean(!data.configOptions.length) &&
Boolean(!data.programmers.length) &&
Boolean(!data.selectedProgrammer)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type { MaybePromise } from '@theia/core/lib/common/types';
import { inject, injectable } from '@theia/core/shared/inversify';
import {
BoardDetails,
Programmer,
isBoardIdentifierChangeEvent,
} from '../../common/protocol';
import { BoardsDataStore, isEmptyData } from '../boards/boards-data-store';
import { BoardsServiceProvider } from '../boards/boards-service-provider';
import { Contribution } from './contribution';

/**
* Before CLI 0.35.0-rc.3, there was no `programmer#default` property in the `board details` response.
* This method does the programmer migration in the data store. If there is a programmer selected, it's a noop.
* If no programmer is selected, it forcefully reloads the details from the CLI and updates it in the local storage.
*/
@injectable()
export class AutoSelectProgrammer extends Contribution {
@inject(BoardsServiceProvider)
private readonly boardsServiceProvider: BoardsServiceProvider;
@inject(BoardsDataStore)
private readonly boardsDataStore: BoardsDataStore;

override onStart(): void {
this.boardsServiceProvider.onBoardsConfigDidChange((event) => {
if (isBoardIdentifierChangeEvent(event)) {
this.ensureProgrammerIsSelected();
}
});
}

override onReady(): void {
this.boardsServiceProvider.ready.then(() =>
this.ensureProgrammerIsSelected()
);
}

private async ensureProgrammerIsSelected(): Promise<boolean> {
return ensureProgrammerIsSelected({
fqbn: this.boardsServiceProvider.boardsConfig.selectedBoard?.fqbn,
getData: (fqbn) => this.boardsDataStore.getData(fqbn),
loadBoardDetails: (fqbn) => this.boardsDataStore.loadBoardDetails(fqbn),
selectProgrammer: (arg) => this.boardsDataStore.selectProgrammer(arg),
});
}
}

interface EnsureProgrammerIsSelectedParams {
fqbn: string | undefined;
getData: (fqbn: string | undefined) => MaybePromise<BoardsDataStore.Data>;
loadBoardDetails: (fqbn: string) => MaybePromise<BoardDetails | undefined>;
selectProgrammer(options: {
fqbn: string;
selectedProgrammer: Programmer;
}): MaybePromise<boolean>;
}

export async function ensureProgrammerIsSelected(
params: EnsureProgrammerIsSelectedParams
): Promise<boolean> {
const { fqbn, getData, loadBoardDetails, selectProgrammer } = params;
if (!fqbn) {
return false;
}
console.debug(`Ensuring a programmer is selected for ${fqbn}...`);
const data = await getData(fqbn);
if (isEmptyData(data)) {
// For example, the platform is not installed.
console.debug(`Skipping. No boards data is available for ${fqbn}.`);
return false;
}
if (data.selectedProgrammer) {
console.debug(
`A programmer is already selected for ${fqbn}: '${data.selectedProgrammer.id}'.`
);
return true;
}
let programmer = data.programmers.find((p) => p.default);
if (programmer) {
// select the programmer if the default info is available
const result = await selectProgrammer({
fqbn,
selectedProgrammer: programmer,
});
if (result) {
console.debug(`Selected '${programmer.id}' programmer for ${fqbn}.`);
return result;
}
}
console.debug(`Reloading board details for ${fqbn}...`);
const reloadedData = await loadBoardDetails(fqbn);
if (!reloadedData) {
console.debug(`Skipping. No board details found for ${fqbn}.`);
return false;
}
if (!reloadedData.programmers.length) {
console.debug(`Skipping. ${fqbn} does not have programmers.`);
return false;
}
programmer = reloadedData.programmers.find((p) => p.default);
if (!programmer) {
console.debug(
`Skipping. Could not find a default programmer for ${fqbn}. Programmers were: `
);
return false;
}
const result = await selectProgrammer({
fqbn,
selectedProgrammer: programmer,
});
if (result) {
console.debug(`Selected '${programmer.id}' programmer for ${fqbn}.`);
} else {
console.debug(
`Could not select '${programmer.id}' programmer for ${fqbn}.`
);
}
return result;
}
Loading