#714: Use the build cache to speed up the LS (#1107)

* Notify the LS about the new `build_path` after verify.

Closes #714

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2022-07-18 10:19:00 +02:00 committed by GitHub
parent ed41b25889
commit 57841b3c0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 323 additions and 212 deletions

View File

@ -165,7 +165,7 @@
"version": "14.0.0"
},
"languageServer": {
"version": "0.6.0"
"version": "0.7.1"
}
}
}

View File

@ -1,141 +1,87 @@
// @ts-check
(async () => {
const path = require('path');
const shell = require('shelljs');
const semver = require('semver');
const moment = require('moment');
const downloader = require('./downloader');
const { goBuildFromGit } = require('./utils');
const fs = require('fs');
const path = require('path');
const temp = require('temp');
const shell = require('shelljs');
const semver = require('semver');
const moment = require('moment');
const downloader = require('./downloader');
const version = (() => {
const pkg = require(path.join(__dirname, '..', 'package.json'));
if (!pkg) {
return undefined;
}
const version = (() => {
const pkg = require(path.join(__dirname, '..', 'package.json'));
if (!pkg) {
return undefined;
const { arduino } = pkg;
if (!arduino) {
return undefined;
}
const { cli } = arduino;
if (!cli) {
return undefined;
}
const { version } = cli;
return version;
})();
if (!version) {
shell.echo(`Could not retrieve CLI version info from the 'package.json'.`);
shell.exit(1);
}
const { platform, arch } = process;
const buildFolder = path.join(__dirname, '..', 'build');
const cliName = `arduino-cli${platform === 'win32' ? '.exe' : ''}`;
const destinationPath = path.join(buildFolder, cliName);
if (typeof version === 'string') {
const suffix = (() => {
switch (platform) {
case 'darwin':
return 'macOS_64bit.tar.gz';
case 'win32':
return 'Windows_64bit.zip';
case 'linux': {
switch (arch) {
case 'arm':
return 'Linux_ARMv7.tar.gz';
case 'arm64':
return 'Linux_ARM64.tar.gz';
case 'x64':
return 'Linux_64bit.tar.gz';
default:
return undefined;
}
}
const { arduino } = pkg;
if (!arduino) {
return undefined;
}
const { cli } = arduino;
if (!cli) {
return undefined;
}
const { version } = cli;
return version;
default:
return undefined;
}
})();
if (!version) {
shell.echo(`Could not retrieve CLI version info from the 'package.json'.`);
shell.exit(1);
if (!suffix) {
shell.echo(`The CLI is not available for ${platform} ${arch}.`);
shell.exit(1);
}
const { platform, arch } = process;
const buildFolder = path.join(__dirname, '..', 'build');
const cliName = `arduino-cli${platform === 'win32' ? '.exe' : ''}`;
const destinationPath = path.join(buildFolder, cliName);
if (typeof version === 'string') {
const suffix = (() => {
switch (platform) {
case 'darwin': return 'macOS_64bit.tar.gz';
case 'win32': return 'Windows_64bit.zip';
case 'linux': {
switch (arch) {
case 'arm': return 'Linux_ARMv7.tar.gz';
case 'arm64': return 'Linux_ARM64.tar.gz';
case 'x64': return 'Linux_64bit.tar.gz';
default: return undefined;
}
}
default: return undefined;
}
})();
if (!suffix) {
shell.echo(`The CLI is not available for ${platform} ${arch}.`);
shell.exit(1);
}
if (semver.valid(version)) {
const url = `https://downloads.arduino.cc/arduino-cli/arduino-cli_${version}_${suffix}`;
shell.echo(`📦 Identified released version of the CLI. Downloading version ${version} from '${url}'`);
await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli');
} else if (moment(version, 'YYYYMMDD', true).isValid()) {
const url = `https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-${version}_${suffix}`;
shell.echo(`🌙 Identified nightly version of the CLI. Downloading version ${version} from '${url}'`);
await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli');
} else {
shell.echo(`🔥 Could not interpret 'version': ${version}`);
shell.exit(1);
}
if (semver.valid(version)) {
const url = `https://downloads.arduino.cc/arduino-cli/arduino-cli_${version}_${suffix}`;
shell.echo(
`📦 Identified released version of the CLI. Downloading version ${version} from '${url}'`
);
await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli');
} else if (moment(version, 'YYYYMMDD', true).isValid()) {
const url = `https://downloads.arduino.cc/arduino-cli/nightly/arduino-cli_nightly-${version}_${suffix}`;
shell.echo(
`🌙 Identified nightly version of the CLI. Downloading version ${version} from '${url}'`
);
await downloader.downloadUnzipFile(url, destinationPath, 'arduino-cli');
} else {
// We assume an object with `owner`, `repo`, commitish?` properties.
const { owner, repo, commitish } = version;
if (!owner) {
shell.echo(`Could not retrieve 'owner' from ${JSON.stringify(version)}`);
shell.exit(1);
}
if (!repo) {
shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
shell.exit(1);
}
const url = `https://github.com/${owner}/${repo}.git`;
shell.echo(`Building CLI from ${url}. Commitish: ${commitish ? commitish : 'HEAD'}`);
if (fs.existsSync(destinationPath)) {
shell.echo(`Skipping the CLI build because it already exists: ${destinationPath}`);
return;
}
if (shell.mkdir('-p', buildFolder).code !== 0) {
shell.echo('Could not create build folder.');
shell.exit(1);
}
const tempRepoPath = temp.mkdirSync();
shell.echo(`>>> Cloning CLI source to ${tempRepoPath}...`);
if (shell.exec(`git clone ${url} ${tempRepoPath}`).code !== 0) {
shell.exit(1);
}
shell.echo('<<< Cloned CLI repo.')
if (commitish) {
shell.echo(`>>> Checking out ${commitish}...`);
if (shell.exec(`git -C ${tempRepoPath} checkout ${commitish}`).code !== 0) {
shell.exit(1);
}
shell.echo(`<<< Checked out ${commitish}.`);
}
shell.echo(`>>> Building the CLI...`);
if (shell.exec('go build', { cwd: tempRepoPath }).code !== 0) {
shell.exit(1);
}
shell.echo('<<< CLI build done.')
if (!fs.existsSync(path.join(tempRepoPath, cliName))) {
shell.echo(`Could not find the CLI at ${path.join(tempRepoPath, cliName)}.`);
shell.exit(1);
}
const builtCliPath = path.join(tempRepoPath, cliName);
shell.echo(`>>> Copying CLI from ${builtCliPath} to ${destinationPath}...`);
if (shell.cp(builtCliPath, destinationPath).code !== 0) {
shell.exit(1);
}
shell.echo(`<<< Copied the CLI.`);
shell.echo('<<< Verifying CLI...');
if (!fs.existsSync(destinationPath)) {
shell.exit(1);
}
shell.echo('>>> Verified CLI.');
shell.echo(`🔥 Could not interpret 'version': ${version}`);
shell.exit(1);
}
} else {
goBuildFromGit(version, destinationPath, 'CLI');
}
})();

View File

@ -7,22 +7,23 @@
const path = require('path');
const shell = require('shelljs');
const downloader = require('./downloader');
const { goBuildFromGit } = require('./utils');
const [DEFAULT_ALS_VERSION, DEFAULT_CLANGD_VERSION] = (() => {
const [DEFAULT_LS_VERSION, DEFAULT_CLANGD_VERSION] = (() => {
const pkg = require(path.join(__dirname, '..', 'package.json'));
if (!pkg) return undefined;
if (!pkg) return [undefined, undefined];
const { arduino } = pkg;
if (!arduino) return undefined;
if (!arduino) return [undefined, undefined];
const { languageServer, clangd } = arduino;
if (!languageServer) return undefined;
if (!clangd) return undefined;
if (!languageServer) return [undefined, undefined];
if (!clangd) return [undefined, undefined];
return [languageServer.version, clangd.version];
})();
if (!DEFAULT_ALS_VERSION) {
if (!DEFAULT_LS_VERSION) {
shell.echo(
`Could not retrieve Arduino Language Server version info from the 'package.json'.`
);
@ -39,8 +40,8 @@
const yargs = require('yargs')
.option('ls-version', {
alias: 'lv',
default: DEFAULT_ALS_VERSION,
describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_ALS_VERSION}.`,
default: DEFAULT_LS_VERSION,
describe: `The version of the 'arduino-language-server' to download. Defaults to ${DEFAULT_LS_VERSION}.`,
})
.option('clangd-version', {
alias: 'cv',
@ -56,7 +57,7 @@
.version(false)
.parse();
const alsVersion = yargs['ls-version'];
const lsVersion = yargs['ls-version'];
const clangdVersion = yargs['clangd-version'];
const force = yargs['force-download'];
const { platform, arch } = process;
@ -87,6 +88,8 @@
lsSuffix = 'Windows_64bit.zip';
clangdSuffix = 'Windows_64bit';
break;
default:
throw new Error(`Unsupported platform/arch: ${platformArch}.`);
}
if (!lsSuffix || !clangdSuffix) {
shell.echo(
@ -95,12 +98,16 @@
shell.exit(1);
}
const alsUrl = `https://downloads.arduino.cc/arduino-language-server/${
alsVersion === 'nightly'
? 'nightly/arduino-language-server'
: 'arduino-language-server_' + alsVersion
}_${lsSuffix}`;
downloader.downloadUnzipAll(alsUrl, build, lsExecutablePath, force);
if (typeof lsVersion === 'string') {
const lsUrl = `https://downloads.arduino.cc/arduino-language-server/${
lsVersion === 'nightly'
? 'nightly/arduino-language-server'
: 'arduino-language-server_' + lsVersion
}_${lsSuffix}`;
downloader.downloadUnzipAll(lsUrl, build, lsExecutablePath, force);
} else {
goBuildFromGit(lsVersion, lsExecutablePath, 'language-server');
}
const clangdUrl = `https://downloads.arduino.cc/tools/clangd_${clangdVersion}_${clangdSuffix}.tar.bz2`;
downloader.downloadUnzipAll(clangdUrl, build, clangdExecutablePath, force, {

View File

@ -86,6 +86,7 @@ exports.downloadUnzipFile = async (
* @param targetDir {string} Directory into which to decompress the archive
* @param targetFile {string} Path to the main file expected after decompressing
* @param force {boolean} Whether to download even if the target file exists
* @param decompressOptions {import('decompress').DecompressOptions}
*/
exports.downloadUnzipAll = async (
url,

View File

@ -0,0 +1,92 @@
/**
* Clones something from GitHub and builds it with `Golang`.
*
* @param version {object} the version object.
* @param destinationPath {string} the absolute path of the output binary. For example, `C:\\folder\\arduino-cli.exe` or `/path/to/arduino-language-server`
* @param taskName {string} for the CLI logging . Can be `'CLI'` or `'language-server'`, etc.
*/
exports.goBuildFromGit = (version, destinationPath, taskName) => {
const fs = require('fs');
const path = require('path');
const temp = require('temp');
const shell = require('shelljs');
// We assume an object with `owner`, `repo`, commitish?` properties.
if (typeof version !== 'object') {
shell.echo(
`Expected a \`{ owner, repo, commitish }\` object. Got <${version}> instead.`
);
}
const { owner, repo, commitish } = version;
if (!owner) {
shell.echo(`Could not retrieve 'owner' from ${JSON.stringify(version)}`);
shell.exit(1);
}
if (!repo) {
shell.echo(`Could not retrieve 'repo' from ${JSON.stringify(version)}`);
shell.exit(1);
}
const url = `https://github.com/${owner}/${repo}.git`;
shell.echo(
`Building ${taskName} from ${url}. Commitish: ${
commitish ? commitish : 'HEAD'
}`
);
if (fs.existsSync(destinationPath)) {
shell.echo(
`Skipping the ${taskName} build because it already exists: ${destinationPath}`
);
return;
}
const buildFolder = path.join(__dirname, '..', 'build');
if (shell.mkdir('-p', buildFolder).code !== 0) {
shell.echo('Could not create build folder.');
shell.exit(1);
}
const tempRepoPath = temp.mkdirSync();
shell.echo(`>>> Cloning ${taskName} source to ${tempRepoPath}...`);
if (shell.exec(`git clone ${url} ${tempRepoPath}`).code !== 0) {
shell.exit(1);
}
shell.echo(`<<< Cloned ${taskName} repo.`);
if (commitish) {
shell.echo(`>>> Checking out ${commitish}...`);
if (shell.exec(`git -C ${tempRepoPath} checkout ${commitish}`).code !== 0) {
shell.exit(1);
}
shell.echo(`<<< Checked out ${commitish}.`);
}
shell.echo(`>>> Building the ${taskName}...`);
if (shell.exec('go build', { cwd: tempRepoPath }).code !== 0) {
shell.exit(1);
}
shell.echo(`<<< Done ${taskName} build.`);
const binName = path.basename(destinationPath);
if (!fs.existsSync(path.join(tempRepoPath, binName))) {
shell.echo(
`Could not find the ${taskName} at ${path.join(tempRepoPath, binName)}.`
);
shell.exit(1);
}
const binPath = path.join(tempRepoPath, binName);
shell.echo(
`>>> Copying ${taskName} from ${binPath} to ${destinationPath}...`
);
if (shell.cp(binPath, destinationPath).code !== 0) {
shell.exit(1);
}
shell.echo(`<<< Copied the ${taskName}.`);
shell.echo(`<<< Verifying ${taskName}...`);
if (!fs.existsSync(destinationPath)) {
shell.exit(1);
}
shell.echo(`>>> Verified ${taskName}.`);
};

View File

@ -251,11 +251,14 @@ export class ArduinoFrontendContribution
);
});
const start = async ({ selectedBoard }: BoardsConfig.Config) => {
const start = async (
{ selectedBoard }: BoardsConfig.Config,
forceStart = false
) => {
if (selectedBoard) {
const { name, fqbn } = selectedBoard;
if (fqbn) {
this.startLanguageServer(fqbn, name);
this.startLanguageServer(fqbn, name, forceStart);
}
}
};
@ -270,7 +273,8 @@ export class ArduinoFrontendContribution
if (event.newValue !== event.oldValue) {
switch (event.preferenceName) {
case 'arduino.language.log':
start(this.boardsServiceClientImpl.boardsConfig);
case 'arduino.language.realTimeDiagnostics':
start(this.boardsServiceClientImpl.boardsConfig, true);
break;
case 'arduino.window.zoomLevel':
if (typeof event.newValue === 'number') {
@ -318,7 +322,8 @@ export class ArduinoFrontendContribution
protected languageServerStartMutex = new Mutex();
protected async startLanguageServer(
fqbn: string,
name: string | undefined
name: string | undefined,
forceStart = false
): Promise<void> {
const port = await this.daemon.tryGetPort();
if (!port) {
@ -352,12 +357,15 @@ export class ArduinoFrontendContribution
}
return;
}
if (fqbn === this.languageServerFqbn) {
if (!forceStart && fqbn === this.languageServerFqbn) {
// NOOP
return;
}
this.logger.info(`Starting language server: ${fqbn}`);
const log = this.arduinoPreferences.get('arduino.language.log');
const realTimeDiagnostics = this.arduinoPreferences.get(
'arduino.language.realTimeDiagnostics'
);
let currentSketchPath: string | undefined = undefined;
if (log) {
const currentSketch = await this.sketchServiceClient.currentSketch();
@ -388,6 +396,7 @@ export class ArduinoFrontendContribution
clangdPath,
log: currentSketchPath ? currentSketchPath : log,
cliDaemonInstance: '1',
realTimeDiagnostics,
board: {
fqbn,
name: name ? `"${name}"` : undefined,

View File

@ -51,6 +51,14 @@ export const ArduinoConfigSchema: PreferenceSchema = {
),
default: false,
},
'arduino.language.realTimeDiagnostics': {
type: 'boolean',
description: nls.localize(
'arduino/preferences/language.realTimeDiagnostics',
"If true, the language server provides real-time diagnostics when typing in the editor. It's false by default."
),
default: false,
},
'arduino.compile.verbose': {
type: 'boolean',
description: nls.localize(
@ -238,6 +246,7 @@ export const ArduinoConfigSchema: PreferenceSchema = {
export interface ArduinoConfiguration {
'arduino.language.log': boolean;
'arduino.language.realTimeDiagnostics': boolean;
'arduino.compile.verbose': boolean;
'arduino.compile.experimental': boolean;
'arduino.compile.revealRange': ErrorRevealStrategy;

View File

@ -275,7 +275,7 @@ export class CompilerErrors
}
private async handleCompilerErrorsDidChange(
errors: CoreError.Compiler[]
errors: CoreError.ErrorLocation[]
): Promise<void> {
this.toDisposeOnCompilerErrorDidChange.dispose();
const compilerErrorsPerResource = this.groupByResource(
@ -312,8 +312,8 @@ export class CompilerErrors
}
private async filter(
errors: CoreError.Compiler[]
): Promise<CoreError.Compiler[]> {
errors: CoreError.ErrorLocation[]
): Promise<CoreError.ErrorLocation[]> {
if (!errors.length) {
return [];
}
@ -326,7 +326,7 @@ export class CompilerErrors
}
private async decorateEditors(
errors: Map<string, CoreError.Compiler[]>
errors: Map<string, CoreError.ErrorLocation[]>
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
const composite = await Promise.all(
[...errors.entries()].map(([uri, errors]) =>
@ -346,7 +346,7 @@ export class CompilerErrors
private async decorateEditor(
uri: string,
errors: CoreError.Compiler[]
errors: CoreError.ErrorLocation[]
): Promise<{ dispose: Disposable; errors: ErrorDecoration[] }> {
const editor = await this.editorManager.getByUri(new URI(uri));
if (!editor) {
@ -523,7 +523,7 @@ export class CompilerErrors
}
private async trackEditors(
errors: Map<string, CoreError.Compiler[]>,
errors: Map<string, CoreError.ErrorLocation[]>,
...track: ((editor: EditorWidget) => Disposable)[]
): Promise<Disposable> {
return new DisposableCollection(
@ -605,8 +605,8 @@ export class CompilerErrors
}
private groupByResource(
errors: CoreError.Compiler[]
): Map<string, CoreError.Compiler[]> {
errors: CoreError.ErrorLocation[]
): Map<string, CoreError.ErrorLocation[]> {
return errors.reduce((acc, curr) => {
const {
location: { uri },
@ -618,7 +618,7 @@ export class CompilerErrors
}
errors.push(curr);
return acc;
}, new Map<string, CoreError.Compiler[]>());
}, new Map<string, CoreError.ErrorLocation[]>());
}
private monacoEditor(widget: EditorWidget): MonacoEditor | undefined;

View File

@ -4,29 +4,29 @@ import { CoreError } from '../../common/protocol/core-service';
@injectable()
export class CoreErrorHandler {
private readonly compilerErrors: CoreError.Compiler[] = [];
private readonly errors: CoreError.ErrorLocation[] = [];
private readonly compilerErrorsDidChangeEmitter = new Emitter<
CoreError.Compiler[]
CoreError.ErrorLocation[]
>();
tryHandle(error: unknown): void {
if (CoreError.is(error)) {
this.compilerErrors.length = 0;
this.compilerErrors.push(...error.data.filter(CoreError.Compiler.is));
this.errors.length = 0;
this.errors.push(...error.data);
this.fireCompilerErrorsDidChange();
}
}
reset(): void {
this.compilerErrors.length = 0;
this.errors.length = 0;
this.fireCompilerErrorsDidChange();
}
get onCompilerErrorsDidChange(): Event<CoreError.Compiler[]> {
get onCompilerErrorsDidChange(): Event<CoreError.ErrorLocation[]> {
return this.compilerErrorsDidChangeEmitter.event;
}
private fireCompilerErrorsDidChange(): void {
this.compilerErrorsDidChangeEmitter.fire(this.compilerErrors.slice());
this.compilerErrorsDidChangeEmitter.fire(this.errors.slice());
}
}

View File

@ -5,7 +5,6 @@ import type {
BoardUserField,
Port,
} from '../../common/protocol/boards-service';
import type { ErrorInfo as CliErrorInfo } from '../../node/cli-error-parser';
import type { Programmer } from './boards-service';
import type { Sketch } from './sketches-service';
@ -17,16 +16,10 @@ export const CompilerWarningLiterals = [
] as const;
export type CompilerWarnings = typeof CompilerWarningLiterals[number];
export namespace CoreError {
export type ErrorInfo = CliErrorInfo;
export interface Compiler extends ErrorInfo {
export interface ErrorLocation {
readonly message: string;
readonly location: Location;
}
export namespace Compiler {
export function is(error: ErrorInfo): error is Compiler {
const { message, location } = error;
return !!message && !!location;
}
readonly details?: string;
}
export const Codes = {
Verify: 4001,
@ -42,7 +35,7 @@ export namespace CoreError {
export const BurnBootloaderFailed = create(Codes.BurnBootloader);
export function is(
error: unknown
): error is ApplicationError<number, ErrorInfo[]> {
): error is ApplicationError<number, ErrorLocation[]> {
return (
error instanceof Error &&
ApplicationError.is(error) &&
@ -51,10 +44,10 @@ export namespace CoreError {
}
function create(
code: number
): ApplicationError.Constructor<number, ErrorInfo[]> {
): ApplicationError.Constructor<number, ErrorLocation[]> {
return ApplicationError.declare(
code,
(message: string, data: ErrorInfo[]) => {
(message: string, data: ErrorLocation[]) => {
return {
data,
message,

View File

@ -1,24 +1,19 @@
import { notEmpty } from '@theia/core';
import { notEmpty } from '@theia/core/lib/common/objects';
import { nls } from '@theia/core/lib/common/nls';
import { FileUri } from '@theia/core/lib/node/file-uri';
import {
Location,
Range,
Position,
} from '@theia/core/shared/vscode-languageserver-protocol';
import { Sketch } from '../common/protocol';
import type { CoreError } from '../common/protocol';
import { Sketch } from '../common/protocol/sketches-service';
export interface ErrorInfo {
readonly message?: string;
readonly location?: Location;
readonly details?: string;
}
export interface ErrorSource {
readonly content: string | ReadonlyArray<Uint8Array>;
readonly sketch?: Sketch;
}
export function tryParseError(source: ErrorSource): ErrorInfo[] {
export function tryParseError(source: ErrorSource): CoreError.ErrorLocation[] {
const { content, sketch } = source;
const err =
typeof content === 'string'
@ -28,7 +23,7 @@ export function tryParseError(source: ErrorSource): ErrorInfo[] {
return tryParse(err)
.map(remapErrorMessages)
.filter(isLocationInSketch(sketch))
.map(errorInfo());
.map(toErrorInfo);
}
return [];
}
@ -50,9 +45,7 @@ namespace ParseResult {
}
}
function isLocationInSketch(
sketch: Sketch
): (value: ParseResult, index: number, array: ParseResult[]) => unknown {
function isLocationInSketch(sketch: Sketch): (result: ParseResult) => boolean {
return (result) => {
const uri = FileUri.create(result.path).toString();
if (!Sketch.isInSketch(uri, sketch)) {
@ -65,15 +58,21 @@ function isLocationInSketch(
};
}
function errorInfo(): (value: ParseResult) => ErrorInfo {
return ({ error, message, path, line, column }) => ({
function toErrorInfo({
error,
message,
path,
line,
column,
}: ParseResult): CoreError.ErrorLocation {
return {
message: error,
details: message,
location: {
uri: FileUri.create(path).toString(),
range: range(line, column),
},
});
};
}
function range(line: number, column?: number): Range {

View File

@ -26,7 +26,7 @@ import { ResponseService } from '../common/protocol/response-service';
import { Board, OutputMessage, Port, Status } from '../common/protocol';
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
import { Port as GrpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
import { ApplicationError, Disposable, nls } from '@theia/core';
import { ApplicationError, CommandService, Disposable, nls } from '@theia/core';
import { MonitorManager } from './monitor-manager';
import { AutoFlushingBuffer } from './utils/buffers';
import { tryParseError } from './cli-error-parser';
@ -42,6 +42,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
@inject(MonitorManager)
private readonly monitorManager: MonitorManager;
@inject(CommandService)
private readonly commandService: CommandService;
async compile(
options: CoreService.Compile.Options & {
exportBinaries?: boolean;
@ -50,7 +53,19 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
): Promise<void> {
const coreClient = await this.coreClient;
const { client, instance } = coreClient;
const handler = this.createOnDataHandler();
let buildPath: string | undefined = undefined;
const handler = this.createOnDataHandler<CompileResponse>((response) => {
const currentBuildPath = response.getBuildPath();
if (!buildPath && currentBuildPath) {
buildPath = currentBuildPath;
} else {
if (!!currentBuildPath && currentBuildPath !== buildPath) {
throw new Error(
`The CLI has already provided a build path: <${buildPath}>, and there is a new build path value: <${currentBuildPath}>.`
);
}
}
});
const request = this.compileRequest(options, instance);
return new Promise<void>((resolve, reject) => {
client
@ -84,7 +99,36 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
}
})
.on('end', resolve);
}).finally(() => handler.dispose());
}).finally(() => {
handler.dispose();
if (!buildPath) {
console.error(
`Have not received the build path from the CLI while running the compilation.`
);
} else {
this.fireBuildDidComplete(FileUri.create(buildPath).toString());
}
});
}
// This executes on the frontend, the VS Code extension receives it, and sends an `ino/buildDidComplete` notification to the language server.
private fireBuildDidComplete(buildOutputUri: string): void {
const params = {
buildOutputUri,
};
console.info(
`Executing 'arduino.languageserver.notifyBuildDidComplete' with ${JSON.stringify(
params
)}`
);
this.commandService
.executeCommand('arduino.languageserver.notifyBuildDidComplete', params)
.catch((err) =>
console.error(
`Unexpected error when firing event on build did complete. ${buildOutputUri}`,
err
)
);
}
private compileRequest(
@ -124,8 +168,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
options,
() => new UploadRequest(),
(client, req) => client.upload(req),
(message: string, info: CoreError.ErrorInfo[]) =>
CoreError.UploadFailed(message, info),
(message: string, locations: CoreError.ErrorLocation[]) =>
CoreError.UploadFailed(message, locations),
'upload'
);
}
@ -137,8 +181,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
options,
() => new UploadUsingProgrammerRequest(),
(client, req) => client.uploadUsingProgrammer(req),
(message: string, info: CoreError.ErrorInfo[]) =>
CoreError.UploadUsingProgrammerFailed(message, info),
(message: string, locations: CoreError.ErrorLocation[]) =>
CoreError.UploadUsingProgrammerFailed(message, locations),
'upload using programmer'
);
}
@ -152,8 +196,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
) => ClientReadableStream<UploadResponse | UploadUsingProgrammerResponse>,
errorHandler: (
message: string,
info: CoreError.ErrorInfo[]
) => ApplicationError<number, CoreError.ErrorInfo[]>,
locations: CoreError.ErrorLocation[]
) => ApplicationError<number, CoreError.ErrorLocation[]>,
task: string
): Promise<void> {
await this.compile(Object.assign(options, { exportBinaries: false }));
@ -285,7 +329,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
return request;
}
private createOnDataHandler<R extends StreamingResponse>(): Disposable & {
private createOnDataHandler<R extends StreamingResponse>(
onResponse?: (response: R) => void
): Disposable & {
stderr: Buffer[];
onData: (response: R) => void;
} {
@ -297,10 +343,14 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
}
});
});
const onData = StreamingResponse.createOnDataHandler(stderr, (out, err) => {
buffer.addChunk(out);
buffer.addChunk(err, OutputMessage.Severity.Error);
});
const onData = StreamingResponse.createOnDataHandler(
stderr,
(out, err) => {
buffer.addChunk(out);
buffer.addChunk(err, OutputMessage.Severity.Error);
},
onResponse
);
return {
dispose: () => buffer.dispose(),
stderr,
@ -369,13 +419,17 @@ namespace StreamingResponse {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function createOnDataHandler<R extends StreamingResponse>(
stderr: Uint8Array[],
onData: (out: Uint8Array, err: Uint8Array) => void
onData: (out: Uint8Array, err: Uint8Array) => void,
onResponse?: (response: R) => void
): (response: R) => void {
return (response: R) => {
const out = response.getOutStream_asU8();
const err = response.getErrStream_asU8();
stderr.push(err);
onData(out, err);
if (onResponse) {
onResponse(response);
}
};
}
}

View File

@ -141,7 +141,7 @@
"theiaPluginsDir": "plugins",
"theiaPlugins": {
"vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix",
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.2.vsix",
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.4.vsix",
"vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix",
"vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
"cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix",

View File

@ -271,6 +271,7 @@
"invalid.sketchbook.location": "Invalid sketchbook location: {0}",
"invalid.theme": "Invalid theme.",
"language.log": "True if the Arduino Language Server should generate log files into the sketch folder. Otherwise, false. It's false by default.",
"language.realTimeDiagnostics": "If true, the language server provides real-time diagnostics when typing in the editor. It's false by default.",
"manualProxy": "Manual proxy configuration",
"network": "Network",
"newSketchbookLocation": "Select new sketchbook location",

View File

@ -75,7 +75,7 @@
"theiaPluginsDir": "plugins",
"theiaPlugins": {
"vscode-builtin-cpp": "https://open-vsx.org/api/vscode/cpp/1.52.1/file/vscode.cpp-1.52.1.vsix",
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.2.vsix",
"vscode-arduino-tools": "https://downloads.arduino.cc/vscode-arduino-tools/vscode-arduino-tools-0.0.2-beta.4.vsix",
"vscode-builtin-json": "https://open-vsx.org/api/vscode/json/1.46.1/file/vscode.json-1.46.1.vsix",
"vscode-builtin-json-language-features": "https://open-vsx.org/api/vscode/json-language-features/1.46.1/file/vscode.json-language-features-1.46.1.vsix",
"cortex-debug": "https://open-vsx.org/api/marus25/cortex-debug/0.3.10/file/marus25.cortex-debug-0.3.10.vsix",