提交 41c02aa8 编写于 作者: D Dirk Baeumer

Fixes #62594: Resolving process task doesn't take task system into account

上级 fb9e1da1
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as fs from 'fs';
import * as cp from 'child_process';
import * as nls from 'vs/nls';
import { TPromise, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
......@@ -400,3 +401,51 @@ export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender
return { send };
}
export namespace win32 {
export function findExecutable(command: string, cwd?: string, paths?: string[]): string {
// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return command;
}
if (cwd === void 0) {
cwd = process.cwd();
}
let dir = path.dirname(command);
if (dir !== '.') {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
return path.join(cwd, command);
}
if (paths === void 0 && Types.isString(process.env.PATH)) {
paths = process.env.PATH.split(path.delimiter);
}
// No PATH environment. Make path absolute to the cwd.
if (paths === void 0 || paths.length === 0) {
return path.join(cwd, command);
}
// We have a simple file name. We get the path variable from the env
// and try to find the executable on the path.
for (let pathEntry of paths) {
// The path entry is absolute.
let fullPath: string;
if (path.isAbsolute(pathEntry)) {
fullPath = path.join(pathEntry, command);
} else {
fullPath = path.join(cwd, pathEntry, command);
}
if (fs.existsSync(fullPath)) {
return fullPath;
}
let withExtension = fullPath + '.com';
if (fs.existsSync(withExtension)) {
return withExtension;
}
withExtension = fullPath + '.exe';
if (fs.existsSync(withExtension)) {
return withExtension;
}
}
return path.join(cwd, command);
}
}
\ No newline at end of file
......@@ -29,6 +29,7 @@ import {
TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO,
ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO
} from 'vs/workbench/api/shared/tasks';
import { ResolveSet, ResolvedVariables } from 'vs/workbench/parts/tasks/common/taskSystem';
namespace TaskExecutionDTO {
export function from(value: TaskExecution): TaskExecutionDTO {
......@@ -505,12 +506,18 @@ export class MainThreadTask implements MainThreadTaskShape {
return URI.parse(`${info.scheme}://${info.authority}${path}`);
},
context: this._extHostContext,
resolveVariables: (workspaceFolder: IWorkspaceFolder, variables: Set<string>): Promise<Map<string, string>> => {
resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): TPromise<ResolvedVariables> => {
let vars: string[] = [];
variables.forEach(item => vars.push(item));
return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, vars)).then(values => {
let result = new Map<string, string>();
Object.keys(values).forEach(key => result.set(key, values[key]));
toResolve.variables.forEach(item => vars.push(item));
return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => {
let result = {
process: undefined as string,
variables: new Map<string, string>()
};
Object.keys(values.variables).forEach(key => result.variables.set(key, values[key]));
if (Types.isString(values.process)) {
result.process = values.process;
}
return result;
});
}
......
......@@ -914,7 +914,7 @@ export interface ExtHostTaskShape {
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;
$OnDidEndTask(execution: TaskExecutionDTO): void;
$resolveVariables(workspaceFolder: UriComponents, variables: string[]): Thenable<any>;
$resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string }, variables: string[] }): Thenable<{ process?: string; variables: { [key: string]: string } }>;
}
export interface IBreakpointDto {
......
......@@ -3,11 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { URI, UriComponents } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import * as Objects from 'vs/base/common/objects';
import { asThenable } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { win32 } from 'vs/base/node/processes';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import * as tasks from 'vs/workbench/parts/tasks/common/tasks';
......@@ -883,9 +886,12 @@ export class ExtHostTask implements ExtHostTaskShape {
});
}
public $resolveVariables(uriComponents: UriComponents, variables: string[]): any {
public $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Thenable<{ process?: string, variables: { [key: string]: string; } }> {
let uri: URI = URI.revive(uriComponents);
let result: { [key: string]: string; } = Object.create(null);
let result = {
process: undefined as string,
variables: Object.create(null)
};
let workspaceFolder = this._workspaceService.resolveWorkspaceFolder(uri);
let resolver = new ExtHostVariableResolverService(this._workspaceService, this._editorService, this._configurationService);
let ws: IWorkspaceFolder = {
......@@ -896,10 +902,24 @@ export class ExtHostTask implements ExtHostTaskShape {
throw new Error('Not implemented');
}
};
for (let variable of variables) {
result[variable] = resolver.resolve(ws, variable);
for (let variable of toResolve.variables) {
result.variables[variable] = resolver.resolve(ws, variable);
}
if (toResolve.process !== void 0) {
let paths: string[] | undefined = undefined;
if (toResolve.process.path !== void 0) {
paths = toResolve.process.path.split(path.delimiter);
for (let i = 0; i < paths.length; i++) {
paths[i] = resolver.resolve(ws, paths[i]);
}
}
result.process = win32.findExecutable(
resolver.resolve(ws, toResolve.process.name),
toResolve.process.cwd !== void 0 ? resolver.resolve(ws, toResolve.process.cwd) : undefined,
paths
);
}
return result;
return Promise.resolve(result);
}
private nextHandle(): number {
......
......@@ -102,11 +102,25 @@ export interface TaskTerminateResponse extends TerminateResponse {
task: Task | undefined;
}
export interface ResolveSet {
process?: {
name: string;
cwd?: string;
path?: string;
};
variables: Set<string>;
}
export interface ResolvedVariables {
process?: string;
variables: Map<string, string>;
}
export interface TaskSystemInfo {
platform: Platform;
context: any;
uriProvider: (this: void, path: string) => URI;
resolveVariables(workspaceFolder: IWorkspaceFolder, variables: Set<string>): TPromise<Map<string, string>>;
resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): TPromise<ResolvedVariables>;
}
export interface TaskSystemInfoResovler {
......
......@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
import * as nls from 'vs/nls';
......@@ -19,6 +18,8 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import * as TPath from 'vs/base/common/paths';
import { win32 } from 'vs/base/node/processes';
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IModelService } from 'vs/editor/common/services/modelService';
......@@ -36,7 +37,7 @@ import {
} from 'vs/workbench/parts/tasks/common/tasks';
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
TelemetryEvent, Triggers, TaskTerminateResponse, TaskSystemInfoResovler, TaskSystemInfo
TelemetryEvent, Triggers, TaskTerminateResponse, TaskSystemInfoResovler, TaskSystemInfo, ResolveSet
} from 'vs/workbench/parts/tasks/common/taskSystem';
interface TerminalData {
......@@ -72,6 +73,8 @@ export class TerminalTaskSystem implements ITaskSystem {
public static TelemetryEventName: string = 'taskService';
private static ProcessVarName = '${__process__}';
private static shellQuotes: IStringDictionary<ShellQuotingOptions> = {
'cmd': {
strong: '"'
......@@ -304,18 +307,60 @@ export class TerminalTaskSystem implements ITaskSystem {
if (workspaceFolder) {
taskSystemInfo = this.taskSystemInfoResolver(workspaceFolder);
}
let resolvedVariables: TPromise<Map<string, string>>;
let variableResolver: TPromise<VariableResolver>;
let isProcess = task.command && task.command.runtime === RuntimeType.Process;
let options = task.command && task.command.options ? task.command.options : undefined;
let cwd = options ? options.cwd : undefined;
let envPath: string | undefined = undefined;
if (options && options.env) {
for (let key of Object.keys(options.env)) {
if (key.toLowerCase() === 'path') {
if (Types.isString(options.env[key])) {
envPath = options.env[key];
}
break;
}
}
}
if (taskSystemInfo) {
resolvedVariables = taskSystemInfo.resolveVariables(workspaceFolder, variables);
let resolveSet: ResolveSet = {
variables
};
if (taskSystemInfo.platform === Platform.Platform.Windows && isProcess) {
resolveSet.process = { name: CommandString.value(task.command.name) };
if (cwd) {
resolveSet.process.cwd = cwd;
}
if (envPath) {
resolveSet.process.path = envPath;
}
}
variableResolver = taskSystemInfo.resolveVariables(workspaceFolder, resolveSet).then((resolved) => {
let result = new Map<string, string>();
resolved.variables.forEach(variable => {
result.set(variable, this.configurationResolverService.resolve(workspaceFolder, variable));
});
if (resolved.process !== void 0) {
result.set(TerminalTaskSystem.ProcessVarName, resolved.process);
}
return new VariableResolver(workspaceFolder, taskSystemInfo, result, undefined);
});
} else {
let result = new Map<string, string>();
variables.forEach(variable => {
result.set(variable, this.configurationResolverService.resolve(workspaceFolder, variable));
});
resolvedVariables = TPromise.as(result);
if (Platform.isWindows && isProcess) {
result.set(TerminalTaskSystem.ProcessVarName, win32.findExecutable(
this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name)),
cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined,
envPath ? envPath.split(path.delimiter).map(p => this.configurationResolverService.resolve(workspaceFolder, p)) : undefined
));
}
variableResolver = TPromise.as(new VariableResolver(workspaceFolder, taskSystemInfo, result, this.configurationResolverService));
}
return resolvedVariables.then((variables) => {
return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, taskSystemInfo, variables, this.configurationResolverService));
return variableResolver.then((resolver) => {
return this.executeInTerminal(task, trigger, resolver);
});
}
......@@ -563,7 +608,7 @@ export class TerminalTaskSystem implements ITaskSystem {
}
let shellArgs = <string[]>shellLaunchConfig.args.slice(0);
let toAdd: string[] = [];
let commandLine = this.buildShellCommandLine(shellLaunchConfig.executable, shellOptions, command, originalCommand, args);
let commandLine = this.buildShellCommandLine(platform, shellLaunchConfig.executable, shellOptions, command, originalCommand, args);
let windowsShellArgs: boolean = false;
if (platform === Platform.Platform.Windows) {
// Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's
......@@ -597,7 +642,7 @@ export class TerminalTaskSystem implements ITaskSystem {
} else {
if (!shellSpecified) {
// Under Mac remove -l to not start it as a login shell.
if (Platform.isMacintosh) {
if (platform === Platform.Platform.Mac) {
let index = shellArgs.indexOf('-l');
if (index !== -1) {
shellArgs.splice(index, 1);
......@@ -621,10 +666,9 @@ export class TerminalTaskSystem implements ITaskSystem {
}
}
} else {
let cwd = options && options.cwd ? options.cwd : process.cwd();
// On Windows executed process must be described absolute. Since we allowed command without an
// absolute path (e.g. "command": "node") we need to find the executable in the CWD or PATH.
let executable = Platform.isWindows && !isShellCommand ? this.findExecutable(commandExecutable, cwd, options) : commandExecutable;
let executable = !isShellCommand
? this.resolveVariable(resolver, TerminalTaskSystem.ProcessVarName)
: commandExecutable;
// When we have a process task there is no need to quote arguments. So we go ahead and take the string value.
shellLaunchConfig = {
......@@ -717,7 +761,7 @@ export class TerminalTaskSystem implements ITaskSystem {
return [result, commandExecutable, undefined];
}
private buildShellCommandLine(shellExecutable: string, shellOptions: ShellConfiguration, command: CommandString, originalCommand: CommandString, args: CommandString[]): string {
private buildShellCommandLine(platform: Platform.Platform, shellExecutable: string, shellOptions: ShellConfiguration, command: CommandString, originalCommand: CommandString, args: CommandString[]): string {
let basename = path.parse(shellExecutable).name.toLowerCase();
let shellQuoteOptions = this.getQuotingOptions(basename, shellOptions);
......@@ -805,7 +849,7 @@ export class TerminalTaskSystem implements ITaskSystem {
let commandLine = result.join(' ');
// There are special rules quoted command line in cmd.exe
if (Platform.isWindows) {
if (platform === Platform.Platform.Windows) {
if (basename === 'cmd' && commandQuoted && argQuoted) {
commandLine = '"' + commandLine + '"';
} else if (basename === 'powershell' && commandQuoted) {
......@@ -813,7 +857,7 @@ export class TerminalTaskSystem implements ITaskSystem {
}
}
if (basename === 'cmd' && Platform.isWindows && commandQuoted && argQuoted) {
if (basename === 'cmd' && platform === Platform.Platform.Windows && commandQuoted && argQuoted) {
commandLine = '"' + commandLine + '"';
}
return commandLine;
......@@ -904,62 +948,6 @@ export class TerminalTaskSystem implements ITaskSystem {
return { command, args };
}
private findExecutable(command: string, cwd: string, options: CommandOptions): string {
// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return command;
}
let dir = path.dirname(command);
if (dir !== '.') {
// We have a directory and the directory is relative (see above). Make the path absolute
// to the current working directory.
return path.join(cwd, command);
}
let paths: string[] | undefined = undefined;
// The options can override the PATH. So consider that PATH if present.
if (options && options.env) {
// Path can be named in many different ways and for the execution it doesn't matter
for (let key of Object.keys(options.env)) {
if (key.toLowerCase() === 'path') {
if (Types.isString(options.env[key])) {
paths = options.env[key].split(path.delimiter);
}
break;
}
}
}
if (paths === void 0 && Types.isString(process.env.PATH)) {
paths = process.env.PATH.split(path.delimiter);
}
// No PATH environment. Make path absolute to the cwd.
if (paths === void 0 || paths.length === 0) {
return path.join(cwd, command);
}
// We have a simple file name. We get the path variable from the env
// and try to find the executable on the path.
for (let pathEntry of paths) {
// The path entry is absolute.
let fullPath: string;
if (path.isAbsolute(pathEntry)) {
fullPath = path.join(pathEntry, command);
} else {
fullPath = path.join(cwd, pathEntry, command);
}
if (fs.existsSync(fullPath)) {
return fullPath;
}
let withExtension = fullPath + '.com';
if (fs.existsSync(withExtension)) {
return withExtension;
}
withExtension = fullPath + '.exe';
if (fs.existsSync(withExtension)) {
return withExtension;
}
}
return path.join(cwd, command);
}
private resolveVariables(resolver: VariableResolver, value: string[]): string[];
private resolveVariables(resolver: VariableResolver, value: CommandString[]): CommandString[];
private resolveVariables(resolver: VariableResolver, value: CommandString[]): CommandString[] {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册