diff --git a/arduino-ide-extension/src/node/arduino-cli.ts b/arduino-ide-extension/src/node/arduino-cli.ts index dac000d3..8b67e44c 100644 --- a/arduino-ide-extension/src/node/arduino-cli.ts +++ b/arduino-ide-extension/src/node/arduino-cli.ts @@ -1,12 +1,8 @@ -import * as os from 'os'; -import * as which from 'which'; -import * as semver from 'semver'; -import { spawn } from 'child_process'; -import { join } from 'path'; import { injectable, inject } from 'inversify'; import { ILogger } from '@theia/core'; import { FileUri } from '@theia/core/lib/node/file-uri'; import { Config } from '../common/protocol/config-service'; +import { spawnCommand, getExecPath } from './exec-util'; @injectable() export class ArduinoCli { @@ -20,33 +16,19 @@ export class ArduinoCli { if (this.execPath) { return this.execPath; } - const version = /\d+\.\d+\.\d+/; - const cli = `arduino-cli${os.platform() === 'win32' ? '.exe' : ''}`; - const buildCli = join(__dirname, '..', '..', 'build', cli); - const buildVersion = await this.spawn(`"${buildCli}"`, ['version']); - const buildShortVersion = (buildVersion.match(version) || [])[0]; - this.execPath = buildCli; - const pathCli = await new Promise(resolve => which(cli, (error, path) => resolve(error ? undefined : path))); - if (!pathCli) { - return buildCli; - } - const pathVersion = await this.spawn(`"${pathCli}"`, ['version']); - const pathShortVersion = (pathVersion.match(version) || [])[0]; - if (semver.gt(pathShortVersion, buildShortVersion)) { - this.execPath = pathCli; - return pathCli; - } - return buildCli; + const path = await getExecPath('arduino-cli', this.logger, 'version'); + this.execPath = path; + return path; } async getVersion(): Promise { const execPath = await this.getExecPath(); - return this.spawn(`"${execPath}"`, ['version']); + return spawnCommand(`"${execPath}"`, ['version'], this.logger); } async getDefaultConfig(): Promise { const execPath = await this.getExecPath(); - const result = await this.spawn(`"${execPath}"`, ['config', 'dump', '--format', 'json']); + const result = await spawnCommand(`"${execPath}"`, ['config', 'dump', '--format', 'json'], this.logger); const { directories } = JSON.parse(result); if (!directories) { throw new Error(`Could not parse config. 'directories' was missing from: ${result}`); @@ -64,33 +46,4 @@ export class ArduinoCli { }; } - private spawn(command: string, args?: string[]): Promise { - return new Promise((resolve, reject) => { - const buffers: Buffer[] = []; - const cp = spawn(command, args, { windowsHide: true, shell: true }); - cp.stdout.on('data', (b: Buffer) => buffers.push(b)); - cp.on('error', error => { - this.logger.error(`Error executing ${command} with args: ${JSON.stringify(args)}.`, error); - reject(error); - }); - cp.on('exit', (code, signal) => { - if (code === 0) { - const result = Buffer.concat(buffers).toString('utf8').trim() - resolve(result); - return; - } - if (signal) { - this.logger.error(`Unexpected signal '${signal}' when executing ${command} with args: ${JSON.stringify(args)}.`); - reject(new Error(`Process exited with signal: ${signal}`)); - return; - } - if (code) { - this.logger.error(`Unexpected exit code '${code}' when executing ${command} with args: ${JSON.stringify(args)}.`); - reject(new Error(`Process exited with exit code: ${code}`)); - return; - } - }); - }); - } - } diff --git a/arduino-ide-extension/src/node/exec-util.ts b/arduino-ide-extension/src/node/exec-util.ts new file mode 100644 index 00000000..cb865602 --- /dev/null +++ b/arduino-ide-extension/src/node/exec-util.ts @@ -0,0 +1,60 @@ +import * as os from 'os'; +import * as which from 'which'; +import * as semver from 'semver'; +import { spawn } from 'child_process'; +import { join } from 'path'; +import { ILogger } from '@theia/core'; + +export async function getExecPath(commandName: string, logger: ILogger, versionArg?: string, inBinDir?: boolean): Promise { + const execName = `${commandName}${os.platform() === 'win32' ? '.exe' : ''}`; + const relativePath = ['..', '..', 'build']; + if (inBinDir) { + relativePath.push('bin'); + } + const buildCommand = join(__dirname, ...relativePath, execName); + if (!versionArg) { + return buildCommand; + } + const versionRegexp = /\d+\.\d+\.\d+/; + const buildVersion = await spawnCommand(`"${buildCommand}"`, [versionArg], logger); + const buildShortVersion = (buildVersion.match(versionRegexp) || [])[0]; + const pathCommand = await new Promise(resolve => which(execName, (error, path) => resolve(error ? undefined : path))); + if (!pathCommand) { + return buildCommand; + } + const pathVersion = await spawnCommand(`"${pathCommand}"`, [versionArg], logger); + const pathShortVersion = (pathVersion.match(versionRegexp) || [])[0]; + if (semver.gt(pathShortVersion, buildShortVersion)) { + return pathCommand; + } + return buildCommand; +} + +export function spawnCommand(command: string, args: string[], logger: ILogger): Promise { + return new Promise((resolve, reject) => { + const cp = spawn(command, args, { windowsHide: true, shell: true }); + const buffers: Buffer[] = []; + cp.stdout.on('data', (b: Buffer) => buffers.push(b)); + cp.on('error', error => { + logger.error(`Error executing ${command} with args: ${JSON.stringify(args)}.`, error); + reject(error); + }); + cp.on('exit', (code, signal) => { + if (code === 0) { + const result = Buffer.concat(buffers).toString('utf8').trim() + resolve(result); + return; + } + if (signal) { + logger.error(`Unexpected signal '${signal}' when executing ${command} with args: ${JSON.stringify(args)}.`); + reject(new Error(`Process exited with signal: ${signal}`)); + return; + } + if (code) { + logger.error(`Unexpected exit code '${code}' when executing ${command} with args: ${JSON.stringify(args)}.`); + reject(new Error(`Process exited with exit code: ${code}`)); + return; + } + }); + }); +} diff --git a/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts index 24de4ef2..00620c49 100644 --- a/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts +++ b/arduino-ide-extension/src/node/language/arduino-language-server-contribution.ts @@ -1,9 +1,9 @@ -import * as which from 'which'; import * as os from 'os'; -import { join, delimiter } from 'path'; -import { injectable } from 'inversify'; +import { injectable, inject } from 'inversify'; +import { ILogger } from '@theia/core'; import { BaseLanguageServerContribution, IConnection, LanguageServerStartOptions } from '@theia/languages/lib/node'; import { Board } from '../../common/protocol/boards-service'; +import { getExecPath } from '../exec-util'; @injectable() export class ArduinoLanguageServerContribution extends BaseLanguageServerContribution { @@ -23,10 +23,13 @@ export class ArduinoLanguageServerContribution extends BaseLanguageServerContrib return this.description.name; } + @inject(ILogger) + protected logger: ILogger; + async start(clientConnection: IConnection, options: LanguageServerStartOptions): Promise { - const languageServer = await this.resolveExecutable('arduino-language-server'); - const clangd = await this.resolveExecutable('clangd'); - const cli = await this.resolveExecutable('arduino-cli'); + const languageServer = await getExecPath('arduino-language-server', this.logger); + const clangd = await getExecPath('clangd', this.logger, '--version', os.platform() !== 'win32'); + const cli = await getExecPath('arduino-cli', this.logger, 'version'); // Add '-log' argument to enable logging to files const args: string[] = ['-clangd', clangd, '-cli', cli]; if (options.parameters && options.parameters.selectedBoard) { @@ -45,21 +48,4 @@ export class ArduinoLanguageServerContribution extends BaseLanguageServerContrib serverConnection.onClose(() => (clientConnection as any).reader.socket.close()); } - protected resolveExecutable(name: string): Promise { - return new Promise((resolve, reject) => { - const segments = ['..', '..', '..', 'build']; - if (name === 'clangd' && os.platform() !== 'win32') { - segments.push('bin'); - } - const path = `${process.env.PATH}${delimiter}${join(__dirname, ...segments)}`; - const suffix = os.platform() === 'win32' ? '.exe' : ''; - which(name + suffix, { path }, (err, execPath) => { - if (err) { - reject(err); - } else { - resolve(execPath); - } - }); - }); - } }