diff --git a/src/cli.ts b/src/cli.ts index d2ec1aa5..ccc4ea90 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,6 @@ 'use strict'; -import { spawn, spawnSync } from 'child_process'; +import { CommonSpawnOptions, spawn, spawnSync } from 'child_process'; import { platform } from 'os'; import { window } from 'vscode'; import { GaugeCommands, GRADLE_COMMAND, MAVEN_COMMAND } from './constants'; @@ -23,12 +23,26 @@ export class CLI { this._gaugePlugins = manifest.plugins; } + public static getDefaultSpawnOptions(): CommonSpawnOptions { + // should only deal with platform specific options + let options: CommonSpawnOptions = {}; + if(platform() === "win32") { + options.shell = true; + } + return options; + } + public static instance(): CLI { const gaugeCommand = this.getCommand(GaugeCommands.Gauge); let mvnCommand = this.getCommand(MAVEN_COMMAND); let gradleCommand = this.getGradleCommand(); if (!gaugeCommand || gaugeCommand === '') return new CLI(gaugeCommand, {}, mvnCommand, gradleCommand); - let gv = spawnSync(gaugeCommand, [GaugeCommands.Version, GaugeCommands.MachineReadable]); + let options = this.getDefaultSpawnOptions(); + let gv = spawnSync( + gaugeCommand, + [GaugeCommands.Version, GaugeCommands.MachineReadable], + options + ); let gaugeVersionInfo; try { gaugeVersionInfo = JSON.parse(gv.stdout.toString()); @@ -67,7 +81,12 @@ export class CLI { let oc = window.createOutputChannel("Gauge Install"); let chan = new OutputChannel(oc, `Installing gauge ${language} plugin ...\n`, ""); return new Promise((resolve, reject) => { - let childProcess = spawn(this._gaugeCommand, [GaugeCommands.Install, language]); + let options = CLI.getDefaultSpawnOptions(); + let childProcess = spawn( + this._gaugeCommand, + [GaugeCommands.Install, language], + options + ); childProcess.stdout.on('data', (chunk) => chan.appendOutBuf(chunk.toString())); childProcess.stderr.on('data', (chunk) => chan.appendErrBuf(chunk.toString())); childProcess.on('exit', (code) => { @@ -100,12 +119,23 @@ export class CLI { return `${v}\n${cm}\n\n${plugins}`; } - private static getCommand(command: string): string { + public static getCommandCandidates(command: string): string[] { let validExecExt = [""]; - if (platform() === 'win32') validExecExt.push(".bat", ".exe", ".cmd"); - for (const ext of validExecExt) { - let executable = `${command}${ext}`; - if (!spawnSync(executable).error) return executable; + if (platform() === 'win32') { + validExecExt.push(".bat", ".exe", ".cmd"); + } + return validExecExt.map((ext) => `${command}${ext}`); + } + + public static checkSpawnable(command: string): boolean { + const result = spawnSync(command, [], CLI.getDefaultSpawnOptions()); + return result.status === 0 && !result.error; + } + + private static getCommand(command: string): string { + let possiableCommands = this.getCommandCandidates(command); + for (const possiableCommand of possiableCommands) { + if (this.checkSpawnable(possiableCommand)) return possiableCommand; } } diff --git a/src/execution/gaugeExecutor.ts b/src/execution/gaugeExecutor.ts index 4739e928..8d34fafd 100644 --- a/src/execution/gaugeExecutor.ts +++ b/src/execution/gaugeExecutor.ts @@ -1,6 +1,6 @@ 'use strict'; -import { ChildProcess, spawn } from 'child_process'; +import { ChildProcess, spawn, SpawnOptions } from 'child_process'; import { platform } from 'os'; import { CancellationTokenSource, commands, Disposable, Position, @@ -67,9 +67,11 @@ export class GaugeExecutor extends Disposable { const relPath = relative(config.getProject().root(), config.getStatus()); this.preExecute.forEach((f) => { f.call(null, env, relPath); }); this.aborted = false; - let options = { cwd: config.getProject().root(), env: env , detached: false}; + let options: SpawnOptions = { cwd: config.getProject().root(), env: env , detached: false }; if (platform() !== 'win32') { options.detached = true; + } else { + options.shell = true; } this.childProcess = spawn(cmd, args, options); this.childProcess.stdout.on('data', this.filterStdoutDataDumpsToTextLines((lineText: string) => { diff --git a/src/gaugeWorkspace.ts b/src/gaugeWorkspace.ts index 3da3ff2a..15a4bcbe 100644 --- a/src/gaugeWorkspace.ts +++ b/src/gaugeWorkspace.ts @@ -6,7 +6,7 @@ import { CancellationTokenSource, commands, Disposable, OutputChannel, Uri, window, workspace, WorkspaceConfiguration, WorkspaceFoldersChangeEvent } from "vscode"; -import { DynamicFeature, LanguageClient, LanguageClientOptions, RevealOutputChannelOn } from "vscode-languageclient/node"; +import { DynamicFeature, LanguageClient, LanguageClientOptions, RevealOutputChannelOn, ServerOptions } from "vscode-languageclient/node"; import { CLI } from './cli'; import GaugeConfig from './config/gaugeConfig'; import { GaugeJavaProjectConfig } from './config/gaugeProjectConfig'; @@ -146,12 +146,16 @@ export class GaugeWorkspace extends Disposable { let project = ProjectFactory.get(folder); if (this._clientsMap.has(project.root())) return; process.env.GAUGE_IGNORE_RUNNER_BUILD_FAILURES = "true"; - let serverOptions = { - command: this.cli.gaugeCommand(), - args: ["daemon", "--lsp", "--dir=" + project.root()], - options: { env: { ...process.env, ...project.envs(this.cli) } } + let serverOptions: ServerOptions = { + command: this.cli.gaugeCommand(), + args: ["daemon", "--lsp", "--dir=" + project.root()], + options: { env: { ...process.env, ...project.envs(this.cli) } }, }; + if (platform() === "win32") { + serverOptions.options.shell = true; + } + this._launchConfig = workspace.getConfiguration(GAUGE_LAUNCH_CONFIG); if (this._launchConfig.get(DEBUG_LOG_LEVEL_CONFIG)) { serverOptions.args.push("-l"); diff --git a/src/init/projectInit.ts b/src/init/projectInit.ts index ed7690eb..72fe774f 100644 --- a/src/init/projectInit.ts +++ b/src/init/projectInit.ts @@ -1,12 +1,13 @@ 'use strict'; -import { spawn, spawnSync } from 'child_process'; +import { CommonSpawnOptions, spawn, spawnSync } from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; import { commands, Disposable, Progress, Uri, window, workspace } from 'vscode'; import { CLI } from '../cli'; import { GaugeCommands, GaugeVSCodeCommands, INSTALL_INSTRUCTION_URI, VSCodeCommands } from "../constants"; import { FileListItem } from '../types/fileListItem'; +import { platform } from 'os'; export class ProjectInitializer extends Disposable { private readonly _disposable: Disposable; @@ -67,7 +68,7 @@ export class ProjectInitializer extends Disposable { private async createFromCommandLine(template: FileListItem, projectFolder: Uri, p: ProgressHandler) { let args = [GaugeCommands.Init, template.label]; - let options = { cwd: projectFolder.fsPath, env: process.env }; + let options: CommonSpawnOptions = { cwd: projectFolder.fsPath, env: process.env, ...CLI.getDefaultSpawnOptions() }; p.report("Initializing project..."); let proc = spawn(this.cli.gaugeCommand(), args, options); proc.addListener('error', async (err) => { @@ -82,7 +83,8 @@ export class ProjectInitializer extends Disposable { private async getTemplatesList(): Promise> { let args = ["template", "--list", "--machine-readable"]; - let cp = spawnSync(this.cli.gaugeCommand(), args, { env: process.env }); + let options: CommonSpawnOptions = { env: process.env, ...CLI.getDefaultSpawnOptions() }; + let cp = spawnSync(this.cli.gaugeCommand(), args, options); try { let _templates = JSON.parse(cp.stdout.toString()); return _templates.map((tmpl) => new FileListItem(tmpl.key, tmpl.Description, tmpl.value)); diff --git a/test/cli.test.ts b/test/cli.test.ts index 77b3a04e..f6494a20 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -1,5 +1,8 @@ import * as assert from 'assert'; import { CLI } from '../src/cli'; +import path = require('path'); + +let testCommandsPath = path.join(__dirname, '..', '..', 'test', 'commands'); suite('CLI', () => { test('.isPluginInstalled should tell a gauge plugin is installed or not', () => { @@ -137,4 +140,40 @@ ruby (1.2.0)`; assert.ok(!cli.isGaugeVersionGreaterOrEqual('1.3.0')); done(); }); + + test('.getCommandCandidates choices all valid', (done) => { + let candidates = CLI.getCommandCandidates('test_command'); + const originalPath = process.env.PATH; + process.env.PATH = testCommandsPath; + let invalid_candidates = []; + try { + for (const candidate of candidates) { + if (!CLI.checkSpawnable(candidate)) { + invalid_candidates.push(candidate); + } + } + assert.ok(invalid_candidates.length === 0, `invalid candidates: ${invalid_candidates.join(', ')}, those should be valid`); + } finally { + process.env.PATH = originalPath; + } + done(); + }); + + test('.getCommandCandidates choices are all not valid', (done) => { + let candidates = CLI.getCommandCandidates('test_command_not_found'); + const originalPath = process.env.PATH; + process.env.PATH = testCommandsPath; + let valid_candidates = []; + try { + for (const candidate of candidates) { + if (CLI.checkSpawnable(candidate)) { + valid_candidates.push(candidate); + } + } + assert.ok(valid_candidates.length === 0, `valid candidates: ${valid_candidates.join(', ')}, those should not be valid`); + } finally { + process.env.PATH = originalPath; + } + done(); + }); }); diff --git a/test/commands/test_command b/test/commands/test_command new file mode 100755 index 00000000..c5020a64 --- /dev/null +++ b/test/commands/test_command @@ -0,0 +1,2 @@ +#!/bin/bash +echo success \ No newline at end of file diff --git a/test/commands/test_command.bat b/test/commands/test_command.bat new file mode 100755 index 00000000..cfd72d1f --- /dev/null +++ b/test/commands/test_command.bat @@ -0,0 +1 @@ +echo hello \ No newline at end of file diff --git a/test/commands/test_command.cmd b/test/commands/test_command.cmd new file mode 100755 index 00000000..74b3ee83 --- /dev/null +++ b/test/commands/test_command.cmd @@ -0,0 +1,2 @@ +@echo off +echo success \ No newline at end of file diff --git a/test/commands/test_command.exe b/test/commands/test_command.exe new file mode 100755 index 00000000..87b0dc08 Binary files /dev/null and b/test/commands/test_command.exe differ