mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-19 01:06:39 +00:00
* 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:
parent
ed41b25889
commit
57841b3c0a
@ -165,7 +165,7 @@
|
||||
"version": "14.0.0"
|
||||
},
|
||||
"languageServer": {
|
||||
"version": "0.6.0"
|
||||
"version": "0.7.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
})();
|
||||
|
@ -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, {
|
||||
|
@ -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,
|
||||
|
92
arduino-ide-extension/scripts/utils.js
Normal file
92
arduino-ide-extension/scripts/utils.js
Normal 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}.`);
|
||||
};
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user