Skip to content

Commit 6618816

Browse files
committed
Align language server spawning with arduino-cli
1 parent 2577451 commit 6618816

File tree

3 files changed

+75
-76
lines changed

3 files changed

+75
-76
lines changed
Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import * as os from 'os';
2-
import * as which from 'which';
3-
import * as semver from 'semver';
4-
import { spawn } from 'child_process';
5-
import { join } from 'path';
61
import { injectable, inject } from 'inversify';
72
import { ILogger } from '@theia/core';
83
import { FileUri } from '@theia/core/lib/node/file-uri';
94
import { Config } from '../common/protocol/config-service';
5+
import { spawnCommand, getExecPath } from './exec-util';
106

117
@injectable()
128
export class ArduinoCli {
@@ -20,33 +16,19 @@ export class ArduinoCli {
2016
if (this.execPath) {
2117
return this.execPath;
2218
}
23-
const version = /\d+\.\d+\.\d+/;
24-
const cli = `arduino-cli${os.platform() === 'win32' ? '.exe' : ''}`;
25-
const buildCli = join(__dirname, '..', '..', 'build', cli);
26-
const buildVersion = await this.spawn(`"${buildCli}"`, ['version']);
27-
const buildShortVersion = (buildVersion.match(version) || [])[0];
28-
this.execPath = buildCli;
29-
const pathCli = await new Promise<string | undefined>(resolve => which(cli, (error, path) => resolve(error ? undefined : path)));
30-
if (!pathCli) {
31-
return buildCli;
32-
}
33-
const pathVersion = await this.spawn(`"${pathCli}"`, ['version']);
34-
const pathShortVersion = (pathVersion.match(version) || [])[0];
35-
if (semver.gt(pathShortVersion, buildShortVersion)) {
36-
this.execPath = pathCli;
37-
return pathCli;
38-
}
39-
return buildCli;
19+
const path = await getExecPath('arduino-cli', this.logger, 'version');
20+
this.execPath = path;
21+
return path;
4022
}
4123

4224
async getVersion(): Promise<string> {
4325
const execPath = await this.getExecPath();
44-
return this.spawn(`"${execPath}"`, ['version']);
26+
return spawnCommand(`"${execPath}"`, ['version'], this.logger);
4527
}
4628

4729
async getDefaultConfig(): Promise<Config> {
4830
const execPath = await this.getExecPath();
49-
const result = await this.spawn(`"${execPath}"`, ['config', 'dump', '--format', 'json']);
31+
const result = await spawnCommand(`"${execPath}"`, ['config', 'dump', '--format', 'json'], this.logger);
5032
const { directories } = JSON.parse(result);
5133
if (!directories) {
5234
throw new Error(`Could not parse config. 'directories' was missing from: ${result}`);
@@ -64,33 +46,4 @@ export class ArduinoCli {
6446
};
6547
}
6648

67-
private spawn(command: string, args?: string[]): Promise<string> {
68-
return new Promise<string>((resolve, reject) => {
69-
const buffers: Buffer[] = [];
70-
const cp = spawn(command, args, { windowsHide: true, shell: true });
71-
cp.stdout.on('data', (b: Buffer) => buffers.push(b));
72-
cp.on('error', error => {
73-
this.logger.error(`Error executing ${command} with args: ${JSON.stringify(args)}.`, error);
74-
reject(error);
75-
});
76-
cp.on('exit', (code, signal) => {
77-
if (code === 0) {
78-
const result = Buffer.concat(buffers).toString('utf8').trim()
79-
resolve(result);
80-
return;
81-
}
82-
if (signal) {
83-
this.logger.error(`Unexpected signal '${signal}' when executing ${command} with args: ${JSON.stringify(args)}.`);
84-
reject(new Error(`Process exited with signal: ${signal}`));
85-
return;
86-
}
87-
if (code) {
88-
this.logger.error(`Unexpected exit code '${code}' when executing ${command} with args: ${JSON.stringify(args)}.`);
89-
reject(new Error(`Process exited with exit code: ${code}`));
90-
return;
91-
}
92-
});
93-
});
94-
}
95-
9649
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as os from 'os';
2+
import * as which from 'which';
3+
import * as semver from 'semver';
4+
import { spawn } from 'child_process';
5+
import { join } from 'path';
6+
import { ILogger } from '@theia/core';
7+
8+
export async function getExecPath(commandName: string, logger: ILogger, versionArg?: string, inBinDir?: boolean): Promise<string> {
9+
const execName = `${commandName}${os.platform() === 'win32' ? '.exe' : ''}`;
10+
const relativePath = ['..', '..', 'build'];
11+
if (inBinDir) {
12+
relativePath.push('bin');
13+
}
14+
const buildCommand = join(__dirname, ...relativePath, execName);
15+
if (!versionArg) {
16+
return buildCommand;
17+
}
18+
const versionRegexp = /\d+\.\d+\.\d+/;
19+
const buildVersion = await spawnCommand(`"${buildCommand}"`, [versionArg], logger);
20+
const buildShortVersion = (buildVersion.match(versionRegexp) || [])[0];
21+
const pathCommand = await new Promise<string | undefined>(resolve => which(execName, (error, path) => resolve(error ? undefined : path)));
22+
if (!pathCommand) {
23+
return buildCommand;
24+
}
25+
const pathVersion = await spawnCommand(`"${pathCommand}"`, [versionArg], logger);
26+
const pathShortVersion = (pathVersion.match(versionRegexp) || [])[0];
27+
if (semver.gt(pathShortVersion, buildShortVersion)) {
28+
return pathCommand;
29+
}
30+
return buildCommand;
31+
}
32+
33+
export function spawnCommand(command: string, args: string[], logger: ILogger): Promise<string> {
34+
return new Promise<string>((resolve, reject) => {
35+
const cp = spawn(command, args, { windowsHide: true, shell: true });
36+
const buffers: Buffer[] = [];
37+
cp.stdout.on('data', (b: Buffer) => buffers.push(b));
38+
cp.on('error', error => {
39+
logger.error(`Error executing ${command} with args: ${JSON.stringify(args)}.`, error);
40+
reject(error);
41+
});
42+
cp.on('exit', (code, signal) => {
43+
if (code === 0) {
44+
const result = Buffer.concat(buffers).toString('utf8').trim()
45+
resolve(result);
46+
return;
47+
}
48+
if (signal) {
49+
logger.error(`Unexpected signal '${signal}' when executing ${command} with args: ${JSON.stringify(args)}.`);
50+
reject(new Error(`Process exited with signal: ${signal}`));
51+
return;
52+
}
53+
if (code) {
54+
logger.error(`Unexpected exit code '${code}' when executing ${command} with args: ${JSON.stringify(args)}.`);
55+
reject(new Error(`Process exited with exit code: ${code}`));
56+
return;
57+
}
58+
});
59+
});
60+
}
Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import * as which from 'which';
21
import * as os from 'os';
3-
import { join, delimiter } from 'path';
4-
import { injectable } from 'inversify';
2+
import { injectable, inject } from 'inversify';
3+
import { ILogger } from '@theia/core';
54
import { BaseLanguageServerContribution, IConnection, LanguageServerStartOptions } from '@theia/languages/lib/node';
65
import { Board } from '../../common/protocol/boards-service';
6+
import { getExecPath } from '../exec-util';
77

88
@injectable()
99
export class ArduinoLanguageServerContribution extends BaseLanguageServerContribution {
@@ -23,10 +23,13 @@ export class ArduinoLanguageServerContribution extends BaseLanguageServerContrib
2323
return this.description.name;
2424
}
2525

26+
@inject(ILogger)
27+
protected logger: ILogger;
28+
2629
async start(clientConnection: IConnection, options: LanguageServerStartOptions): Promise<void> {
27-
const languageServer = await this.resolveExecutable('arduino-language-server');
28-
const clangd = await this.resolveExecutable('clangd');
29-
const cli = await this.resolveExecutable('arduino-cli');
30+
const languageServer = await getExecPath('arduino-language-server', this.logger);
31+
const clangd = await getExecPath('clangd', this.logger, '--version', os.platform() !== 'win32');
32+
const cli = await getExecPath('arduino-cli', this.logger, 'version');
3033
// Add '-log' argument to enable logging to files
3134
const args: string[] = ['-clangd', clangd, '-cli', cli];
3235
if (options.parameters && options.parameters.selectedBoard) {
@@ -45,21 +48,4 @@ export class ArduinoLanguageServerContribution extends BaseLanguageServerContrib
4548
serverConnection.onClose(() => (clientConnection as any).reader.socket.close());
4649
}
4750

48-
protected resolveExecutable(name: string): Promise<string> {
49-
return new Promise<string>((resolve, reject) => {
50-
const segments = ['..', '..', '..', 'build'];
51-
if (name === 'clangd' && os.platform() !== 'win32') {
52-
segments.push('bin');
53-
}
54-
const path = `${process.env.PATH}${delimiter}${join(__dirname, ...segments)}`;
55-
const suffix = os.platform() === 'win32' ? '.exe' : '';
56-
which(name + suffix, { path }, (err, execPath) => {
57-
if (err) {
58-
reject(err);
59-
} else {
60-
resolve(execPath);
61-
}
62-
});
63-
});
64-
}
6551
}

0 commit comments

Comments
 (0)