From f175c54a87a9b32394230d076113c14886a933ee Mon Sep 17 00:00:00 2001 From: elaihau Date: Tue, 25 Jun 2019 13:19:39 -0400 Subject: [PATCH] support linux and osx specific command properties - as per https://github.com/microsoft/vscode/blob/1.35.1/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts#L255, vsCode supports having separated command properties for Windows, OSX, and Linux. This change adds the same support to Theia. - changed the `command` property in CommandProperties interface from mandatory to optional. - part of #5516 Signed-off-by: elaihau --- packages/task/README.md | 32 ++++- .../browser/process/process-task-resolver.ts | 10 ++ .../task/src/browser/task-schema-updater.ts | 121 ++++++++++-------- .../task/src/common/process/task-protocol.ts | 14 +- .../src/node/process/process-task-runner.ts | 103 ++++++++++----- 5 files changed, 185 insertions(+), 95 deletions(-) diff --git a/packages/task/README.md b/packages/task/README.md index 5e8c08ddbb312..d89174b75ebb6 100644 --- a/packages/task/README.md +++ b/packages/task/README.md @@ -14,8 +14,7 @@ Each task configuration looks like this: "-alR" ], "options": { - "cwd": "${workspaceFolder}", - + "cwd": "${workspaceFolder}" }, "windows": { "command": "cmd.exe", @@ -41,8 +40,13 @@ Each task configuration looks like this: - *env*: the environment of the executed program or shell. If omitted the parent process' environment is used. - *shell*: configuration of the shell when task type is `shell`, where users can specify the shell to use with *shell*, and the arguments to be passed to the shell executable to run in command mode with *args*. +By default, *command* and *args* above are used on all platforms. However it's not always possible to express a task in the same way, both on Unix and Windows. The command and/or arguments may be different, for example. If a task needs to work on Linux, MacOS, and Windows, it is better to have separated command, command arguments, and options. + +*windows*: if *windows* is defined, its command, command arguments, and options (i.e., *windows.command*, *windows.args*, and *windows.options*) will take precedence over the *command*, *args*, and *options*, when the task is executed on a Windows backend. + +*osx*: if *osx* is defined, its command, command arguments, and options (i.e., *osx.command*, *osx.args*, and *osx.options*) will take precedence over the *command*, *args*, and *options*, when the task is executed on a MacOS backend. -*windows*: by default, *command* and *args* above are used on all platforms. However it's not always possible to express a task in the same way, both on Unix and Windows. The command and/or arguments may be different, for example. If a task needs to work on both Linux/MacOS and Windows, it can be better to have two separate process options. If *windows* is defined, it will be used instead of *command* and *args*, when a task is executed on a Windows backend. +*linux*: if *linux* is defined, its command, command arguments, and options (i.e., *linux.command*, *linux.args*, and *linux.options*) will take precedence over the *command*, *args*, and *options*, when the task is executed on a Linux backend. Here is a sample tasks.json that can be used to test tasks. Just add this content under the theia source directory, in directory `.theia`: ``` json @@ -54,9 +58,9 @@ Here is a sample tasks.json that can be used to test tasks. Just add this conten "type": "shell", "command": "./task", "args": [ - "1", - "2", - "3" + "default 1", + "default 2", + "default 3" ], "options": { "cwd": "${workspaceFolder}/packages/task/src/node/test-resources/" @@ -66,7 +70,14 @@ Here is a sample tasks.json that can be used to test tasks. Just add this conten "args": [ "/c", "task.bat", - "abc" + "windows abc" + ] + }, + "linux": { + "args": [ + "linux 1", + "linux 2", + "linux 3" ] } }, @@ -128,6 +139,13 @@ The variables are supported in the following properties, using `${variableName}` - `options.cwd` - `windows.command` - `windows.args` +- `windows.options.cwd` +- `osx.command` +- `osx.args` +- `osx.options.cwd` +- `linux.command` +- `linux.args` +- `linux.options.cwd` See [here](https://www.theia-ide.org/doc/index.html) for a detailed documentation. diff --git a/packages/task/src/browser/process/process-task-resolver.ts b/packages/task/src/browser/process/process-task-resolver.ts index 6cf1cc53a6bc3..511406951c3c9 100644 --- a/packages/task/src/browser/process/process-task-resolver.ts +++ b/packages/task/src/browser/process/process-task-resolver.ts @@ -49,6 +49,16 @@ export class ProcessTaskResolver implements TaskResolver { args: processTaskConfig.windows.args ? await this.variableResolverService.resolveArray(processTaskConfig.windows.args, variableResolverOptions) : undefined, options: processTaskConfig.windows.options } : undefined, + osx: processTaskConfig.osx ? { + command: await this.variableResolverService.resolve(processTaskConfig.osx.command, variableResolverOptions), + args: processTaskConfig.osx.args ? await this.variableResolverService.resolveArray(processTaskConfig.osx.args, variableResolverOptions) : undefined, + options: processTaskConfig.osx.options + } : undefined, + linux: processTaskConfig.linux ? { + command: await this.variableResolverService.resolve(processTaskConfig.linux.command, variableResolverOptions), + args: processTaskConfig.linux.args ? await this.variableResolverService.resolveArray(processTaskConfig.linux.args, variableResolverOptions) : undefined, + options: processTaskConfig.linux.options + } : undefined, options: { cwd: await this.variableResolverService.resolve(processTaskConfig.options && processTaskConfig.options.cwd || '${workspaceFolder}', variableResolverOptions), env: processTaskConfig.options && processTaskConfig.options.env, diff --git a/packages/task/src/browser/task-schema-updater.ts b/packages/task/src/browser/task-schema-updater.ts index 4678c6f6fa0a0..5dfaff4e1a164 100644 --- a/packages/task/src/browser/task-schema-updater.ts +++ b/packages/task/src/browser/task-schema-updater.ts @@ -59,10 +59,57 @@ export class TaskSchemaUpdater { } } +const commandSchema: IJSONSchema = { + type: 'string', + description: 'The actual command or script to execute' +}; + +const commandArgSchema: IJSONSchema = { + type: 'array', + description: 'A list of strings, each one being one argument to pass to the command', + items: { + type: 'string' + } +}; + +const commandOptionsSchema: IJSONSchema = { + type: 'object', + description: 'The command options used when the command is executed', + properties: { + cwd: { + type: 'string', + description: 'The directory in which the command will be executed', + default: '${workspaceFolder}' + }, + env: { + type: 'object', + description: 'The environment of the executed program or shell. If omitted the parent process\' environment is used' + }, + shell: { + type: 'object', + description: 'Configuration of the shell when task type is `shell`', + properties: { + executable: { + type: 'string', + description: 'The shell to use' + }, + args: { + type: 'array', + description: `The arguments to be passed to the shell executable to run in command mode + (e.g ['-c'] for bash or ['/S', '/C'] for cmd.exe)`, + items: { + type: 'string' + } + } + } + } + } +}; + const taskConfigurationSchema: IJSONSchema = { oneOf: [ { - 'allOf': [ + allOf: [ { type: 'object', required: ['type', 'label'], @@ -77,68 +124,36 @@ const taskConfigurationSchema: IJSONSchema = { default: 'shell', description: 'Determines what type of process will be used to execute the task. Only shell types will have output shown on the user interface' }, - command: { - type: 'string', - description: 'The actual command or script to execute' - }, - args: { - type: 'array', - description: 'A list of strings, each one being one argument to pass to the command', - items: { - type: 'string' + command: commandSchema, + args: commandArgSchema, + options: commandOptionsSchema, + windows: { + type: 'object', + description: 'Windows specific command configuration that overrides the command, args, and options', + properties: { + command: commandSchema, + args: commandArgSchema, + options: commandOptionsSchema } }, - options: { + osx: { type: 'object', - description: 'The command options used when the command is executed', + description: 'MacOS specific command configuration that overrides the command, args, and options', properties: { - cwd: { - type: 'string', - description: 'The directory in which the command will be executed', - default: '${workspaceFolder}' - }, - env: { - type: 'object', - description: 'The environment of the executed program or shell. If omitted the parent process\' environment is used' - }, - shell: { - type: 'object', - description: 'Configuration of the shell when task type is `shell`', - properties: { - executable: { - type: 'string', - description: 'The shell to use' - }, - args: { - type: 'array', - description: `The arguments to be passed to the shell executable to run in command mode - (e.g ['-c'] for bash or ['/S', '/C'] for cmd.exe)`, - items: { - type: 'string' - } - } - } - } + command: commandSchema, + args: commandArgSchema, + options: commandOptionsSchema } }, - windows: { + linux: { type: 'object', - description: 'Windows specific command configuration overrides command and args', + description: 'Linux specific command configuration that overrides the default command, args, and options', properties: { - command: { - type: 'string', - description: 'The actual command or script to execute' - }, - args: { - type: 'array', - description: 'A list of strings, each one being one argument to pass to the command', - items: { - type: 'string' - } - }, + command: commandSchema, + args: commandArgSchema, + options: commandOptionsSchema } } - } } ] diff --git a/packages/task/src/common/process/task-protocol.ts b/packages/task/src/common/process/task-protocol.ts index 23110af34dff7..7e02962c0d5e6 100644 --- a/packages/task/src/common/process/task-protocol.ts +++ b/packages/task/src/common/process/task-protocol.ts @@ -52,7 +52,7 @@ export interface CommandOptions { } export interface CommandProperties { - readonly command: string; + readonly command?: string; readonly args?: T[]; readonly options?: CommandOptions; } @@ -62,9 +62,19 @@ export interface ProcessTaskConfiguration extends TaskConfiguration, readonly type: ProcessType; /** - * Windows version of CommandProperties. Used in preference on Windows, if defined. + * Windows specific task configuration */ readonly windows?: CommandProperties; + + /** + * macOS specific task configuration + */ + readonly osx?: CommandProperties; + + /** + * Linux specific task configuration + */ + readonly linux?: CommandProperties; } export interface ProcessTaskInfo extends TaskInfo { diff --git a/packages/task/src/node/process/process-task-runner.ts b/packages/task/src/node/process/process-task-runner.ts index 47e4f4ce6b672..d7e7580830362 100644 --- a/packages/task/src/node/process/process-task-runner.ts +++ b/packages/task/src/node/process/process-task-runner.ts @@ -15,7 +15,7 @@ ********************************************************************************/ import { injectable, inject, named } from 'inversify'; -import { isWindows, ILogger } from '@theia/core'; +import { isWindows, isOSX, ILogger } from '@theia/core'; import { FileUri } from '@theia/core/lib/node'; import { TerminalProcessOptions, @@ -59,39 +59,9 @@ export class ProcessTaskRunner implements TaskRunner { throw new Error("Process task config must have 'command' property specified"); } - let command: string | undefined; - let args: Array | undefined; - let options: CommandOptions = {}; - - // on windows, windows-specific options, if available, takes precedence - if (isWindows && taskConfig.windows !== undefined) { - if (taskConfig.windows.command) { - command = taskConfig.windows.command; - args = taskConfig.windows.args; - options = taskConfig.windows.options; - } - } else { - command = taskConfig.command; - args = taskConfig.args; - options = taskConfig.options; - } - - // sanity checks: - // - we expect the cwd to be set by the client. - if (!options || !options.cwd) { - throw new Error("Can't run a task when 'cwd' is not provided by the client"); - } - - // Use task's cwd with spawned process and pass node env object to - // new process, so e.g. we can re-use the system path - if (options) { - options.env = { - ...process.env, - ...(options.env || {}) - }; - } - try { + const { command, args, options } = this.getResolvedCommand(taskConfig); + const processType = taskConfig.type === 'process' ? 'process' : 'shell'; let proc: Process; @@ -139,6 +109,73 @@ export class ProcessTaskRunner implements TaskRunner { } } + private getResolvedCommand(taskConfig: TaskConfiguration): { + command: string | undefined, + args: Array | undefined, + options: CommandOptions + } { + let systemSpecificCommand: { + command: string | undefined, + args: Array | undefined, + options: CommandOptions + }; + // on windows, windows-specific options, if available, take precedence + if (isWindows && taskConfig.windows !== undefined) { + systemSpecificCommand = this.getSystemSpecificCommand(taskConfig, 'windows'); + } else if (isOSX && taskConfig.osx !== undefined) { // on macOS, mac-specific options, if available, take precedence + systemSpecificCommand = this.getSystemSpecificCommand(taskConfig, 'osx'); + } else { // on linux + if (taskConfig.linux !== undefined) { // linux-specific options, if available, take precedence + systemSpecificCommand = this.getSystemSpecificCommand(taskConfig, 'linux'); + } else { + systemSpecificCommand = this.getSystemSpecificCommand(taskConfig, undefined); + } + } + + const options = systemSpecificCommand.options; + // sanity checks: + // - we expect the cwd to be set by the client. + if (!options || !options.cwd) { + throw new Error("Can't run a task when 'cwd' is not provided by the client"); + } + + // Use task's cwd with spawned process and pass node env object to + // new process, so e.g. we can re-use the system path + if (options) { + options.env = { + ...process.env, + ...(options.env || {}) + }; + } + + return systemSpecificCommand; + } + + private getSystemSpecificCommand(taskConfig: TaskConfiguration, system: 'windows' | 'linux' | 'osx' | undefined): { + command: string | undefined, + args: Array | undefined, + options: CommandOptions + } { + // initialise with default values from the `taskConfig` + let command: string | undefined = taskConfig.command; + let args: Array | undefined = taskConfig.args; + let options: CommandOptions = taskConfig.options || {}; + + if (system) { + if (taskConfig[system].command) { + command = taskConfig[system].command; + } + if (taskConfig[system].args) { + args = taskConfig[system].args; + } + if (taskConfig[system].options) { + options = taskConfig[system].options; + } + } + + return { command, args, options }; + } + protected asFsPath(uriOrPath: string) { return (uriOrPath.startsWith('file:/')) ? FileUri.fsPath(uriOrPath)