Skip to content

feat: use Arduino CLI 0.36.0-rc.1 APIs #2334

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

Merged
merged 4 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
feat: use Arduino CLI 0.36.0-rc.1 APIs
Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
  • Loading branch information
Akos Kitta committed Feb 20, 2024
commit 405b4fe0f70e974d98ef80fc19ef36e1c9aefc29
8 changes: 6 additions & 2 deletions arduino-ide-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,17 @@
],
"arduino": {
"arduino-cli": {
"version": "0.35.3"
"version": "0.36.0-rc.1"
},
"arduino-fwuploader": {
"version": "2.4.1"
},
"arduino-language-server": {
"version": "0.7.6"
"version": {
"owner": "arduino",
"repo": "arduino-language-server",
"commitish": "91c2ba8"
}
},
"clangd": {
"version": "14.0.0"
Expand Down
6 changes: 5 additions & 1 deletion arduino-ide-extension/scripts/generate-protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
(async () => {
const os = require('node:os');
const path = require('node:path');
const { mkdirSync, promises: fs } = require('node:fs');
const { mkdirSync, promises: fs, rmSync } = require('node:fs');
const { exec } = require('./utils');
const glob = require('glob');
const { SemVer, gte, valid: validSemVer } = require('semver');
Expand Down Expand Up @@ -140,6 +140,10 @@

const rpc = path.join(repository, 'rpc');
const out = path.join(__dirname, '..', 'src', 'node', 'cli-protocol');
// Must wipe the gen output folder. Otherwise, dangling service implementation remain in IDE2 code,
// although it has been removed from the proto file.
// For example, https://github.com/arduino/arduino-cli/commit/50a8bf5c3e61d5b661ccfcd6a055e82eeb510859.
rmSync(out, { recursive: true, maxRetries: 5, force: true });
mkdirSync(out, { recursive: true });

const protos = await new Promise((resolve) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,7 @@ export class InoLanguage extends SketchContribution {
forceStart = false
): Promise<void> {
const port = await this.daemon.tryGetPort();
if (!port) {
return;
}
const portNumber = Number.parseInt(port, 10); // TODO: IDE2 APIs should provide a number and not string
if (Number.isNaN(portNumber)) {
if (typeof port !== 'number') {
return;
}
const release = await this.languageServerStartMutex.acquire();
Expand Down Expand Up @@ -280,7 +276,7 @@ export class InoLanguage extends SketchContribution {
lsPath,
daemonAddress: {
hostname: 'localhost',
port: portNumber,
port,
instance: 1, // TODO: get it from the backend
},
clangdPath,
Expand Down
4 changes: 2 additions & 2 deletions arduino-ide-extension/src/browser/notification-center.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class NotificationCenter
new Emitter<ProgressMessage>();
private readonly indexUpdateDidFailEmitter =
new Emitter<IndexUpdateDidFailParams>();
private readonly daemonDidStartEmitter = new Emitter<string>();
private readonly daemonDidStartEmitter = new Emitter<number>();
private readonly daemonDidStopEmitter = new Emitter<void>();
private readonly configDidChangeEmitter = new Emitter<ConfigState>();
private readonly platformDidInstallEmitter = new Emitter<{
Expand Down Expand Up @@ -136,7 +136,7 @@ export class NotificationCenter
this.indexUpdateDidFailEmitter.fire(params);
}

notifyDaemonDidStart(port: string): void {
notifyDaemonDidStart(port: number): void {
this.daemonDidStartEmitter.fire(port);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export class DaemonPort implements FrontendApplicationContribution {
@inject(NotificationCenter)
private readonly notificationCenter: NotificationCenter;

private readonly onPortDidChangeEmitter = new Emitter<string | undefined>();
private _port: string | undefined;
private readonly onPortDidChangeEmitter = new Emitter<number | undefined>();
private _port: number | undefined;

onStart(): void {
this.daemon.tryGetPort().then(
Expand All @@ -91,15 +91,15 @@ export class DaemonPort implements FrontendApplicationContribution {
this.onPortDidChangeEmitter.dispose();
}

get port(): string | undefined {
get port(): number | undefined {
return this._port;
}

get onDidChangePort(): Event<string | undefined> {
get onDidChangePort(): Event<number | undefined> {
return this.onPortDidChangeEmitter.event;
}

private setPort(port: string | undefined): void {
private setPort(port: number | undefined): void {
const oldPort = this._port;
this._port = port;
if (this._port !== oldPort) {
Expand Down
8 changes: 4 additions & 4 deletions arduino-ide-extension/src/common/protocol/arduino-daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ export interface ArduinoDaemon {
* Returns with a promise that resolves with the port
* of the CLI daemon when it's up and running.
*/
getPort(): Promise<string>;
getPort(): Promise<number>;
/**
* Unlike `getPort` this method returns with a promise
* that resolves to `undefined` when the daemon is not running.
* Otherwise resolves to the CLI daemon port.
*/
tryGetPort(): Promise<string | undefined>;
start(): Promise<string>;
tryGetPort(): Promise<number | undefined>;
start(): Promise<number>;
stop(): Promise<void>;
restart(): Promise<string>;
restart(): Promise<number>;
}
3 changes: 3 additions & 0 deletions arduino-ide-extension/src/common/protocol/boards-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export interface BoardsService
}): Promise<BoardsPackage | undefined>;
searchBoards({ query }: { query?: string }): Promise<BoardWithPackage[]>;
getInstalledBoards(): Promise<BoardWithPackage[]>;
/**
* Returns with all installed platforms including the manually installed ones.
*/
getInstalledPlatforms(): Promise<BoardsPackage[]>;
getBoardUserFields(options: {
fqbn: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export interface NotificationServiceClient {
notifyIndexUpdateDidFail(params: IndexUpdateDidFailParams): void;

// Daemon
notifyDaemonDidStart(port: string): void;
notifyDaemonDidStart(port: number): void;
notifyDaemonDidStop(): void;

// CLI config
Expand Down
53 changes: 53 additions & 0 deletions arduino-ide-extension/src/node/arduino-core-service-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { credentials, makeClientConstructor } from '@grpc/grpc-js';
import * as commandsGrpcPb from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';

export interface CreateClientOptions {
/**
* The port to the Arduino CLI daemon.
*/
readonly port: number;
/**
* Defaults to `'localhost'`.
*/
readonly host?: string;

/**
* gRCP channel options. Defaults to `createDefaultChannelOptions` with `'0.0.0'` `appVersion`
*/
readonly channelOptions?: Record<string, unknown>;
}

export function createDefaultChannelOptions(
appVersion = '0.0.0'
): Record<string, unknown> {
return {
'grpc.max_send_message_length': 512 * 1024 * 1024,
'grpc.max_receive_message_length': 512 * 1024 * 1024,
'grpc.primary_user_agent': `arduino-ide/${appVersion}`,
};
}

export function createArduinoCoreServiceClient(
options: CreateClientOptions
): ArduinoCoreServiceClient {
const {
port,
host = 'localhost',
channelOptions = createDefaultChannelOptions(),
} = options;
const address = `${host}:${port}`;
// https://github.com/agreatfool/grpc_tools_node_protoc_ts/blob/master/doc/grpcjs_support.md#usage
const ArduinoCoreServiceClient = makeClientConstructor(
// @ts-expect-error: ignore
commandsGrpcPb['cc.arduino.cli.commands.v1.ArduinoCoreService'],
'ArduinoCoreServiceService'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any;
const client = new ArduinoCoreServiceClient(
address,
credentials.createInsecure(),
channelOptions
) as ArduinoCoreServiceClient;
return client;
}
30 changes: 18 additions & 12 deletions arduino-ide-extension/src/node/arduino-daemon-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ export class ArduinoDaemonImpl
private readonly processUtils: ProcessUtils;

private readonly toDispose = new DisposableCollection();
private readonly onDaemonStartedEmitter = new Emitter<string>();
private readonly onDaemonStartedEmitter = new Emitter<number>();
private readonly onDaemonStoppedEmitter = new Emitter<void>();

private _running = false;
private _port = new Deferred<string>();
private _port = new Deferred<number>();

// Backend application lifecycle.

Expand All @@ -53,18 +53,18 @@ export class ArduinoDaemonImpl

// Daemon API

async getPort(): Promise<string> {
async getPort(): Promise<number> {
return this._port.promise;
}

async tryGetPort(): Promise<string | undefined> {
async tryGetPort(): Promise<number | undefined> {
if (this._running) {
return this._port.promise;
}
return undefined;
}

async start(): Promise<string> {
async start(): Promise<number> {
try {
this.toDispose.dispose(); // This will `kill` the previously started daemon process, if any.
const cliPath = this.getExecPath();
Expand Down Expand Up @@ -101,13 +101,13 @@ export class ArduinoDaemonImpl
this.toDispose.dispose();
}

async restart(): Promise<string> {
async restart(): Promise<number> {
return this.start();
}

// Backend only daemon API

get onDaemonStarted(): Event<string> {
get onDaemonStarted(): Event<number> {
return this.onDaemonStartedEmitter.event;
}

Expand Down Expand Up @@ -150,11 +150,11 @@ export class ArduinoDaemonImpl

protected async spawnDaemonProcess(): Promise<{
daemon: ChildProcess;
port: string;
port: number;
}> {
const args = await this.getSpawnArgs();
const cliPath = this.getExecPath();
const ready = new Deferred<{ daemon: ChildProcess; port: string }>();
const ready = new Deferred<{ daemon: ChildProcess; port: number }>();
const options = {
env: { ...deepClone(process.env), NO_COLOR: String(true) },
};
Expand Down Expand Up @@ -195,7 +195,13 @@ export class ArduinoDaemonImpl

if (port.length && address.length) {
grpcServerIsReady = true;
ready.resolve({ daemon, port });
const portNumber = Number.parseInt(port, 10);
if (Number.isNaN(portNumber)) {
ready.reject(
new Error(`Received a NaN port from the CLI: ${port}`)
);
}
ready.resolve({ daemon, port: portNumber });
}
}
});
Expand Down Expand Up @@ -225,7 +231,7 @@ export class ArduinoDaemonImpl
return ready.promise;
}

private fireDaemonStarted(port: string): void {
private fireDaemonStarted(port: number): void {
this._running = true;
this._port.resolve(port);
this.onDaemonStartedEmitter.fire(port);
Expand All @@ -238,7 +244,7 @@ export class ArduinoDaemonImpl
}
this._running = false;
this._port.reject(); // Reject all pending.
this._port = new Deferred<string>();
this._port = new Deferred<number>();
this.onDaemonStoppedEmitter.fire();
this.notificationService.notifyDaemonDidStop();
}
Expand Down
Loading