Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support linux and osx specific command properties #5579

Merged
merged 1 commit into from
Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions packages/task/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ Each task configuration looks like this:
"-alR"
],
"options": {
"cwd": "${workspaceFolder}",

"cwd": "${workspaceFolder}"
},
"windows": {
"command": "cmd.exe",
Expand All @@ -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
Expand All @@ -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/"
Expand All @@ -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"
]
}
},
Expand Down Expand Up @@ -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.

Expand Down
10 changes: 10 additions & 0 deletions packages/task/src/browser/process/process-task-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
121 changes: 68 additions & 53 deletions packages/task/src/browser/task-schema-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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
}
}

}
}
]
Expand Down
14 changes: 12 additions & 2 deletions packages/task/src/common/process/task-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface CommandOptions {
}

export interface CommandProperties<T = string> {
readonly command: string;
readonly command?: string;
readonly args?: T[];
readonly options?: CommandOptions;
}
Expand All @@ -62,9 +62,19 @@ export interface ProcessTaskConfiguration<T = string> extends TaskConfiguration,
readonly type: ProcessType;

/**
* Windows version of CommandProperties. Used in preference on Windows, if defined.
* Windows specific task configuration
*/
readonly windows?: CommandProperties<T>;

/**
* macOS specific task configuration
*/
readonly osx?: CommandProperties<T>;

/**
* Linux specific task configuration
*/
readonly linux?: CommandProperties<T>;
}

export interface ProcessTaskInfo extends TaskInfo {
Expand Down
101 changes: 68 additions & 33 deletions packages/task/src/node/process/process-task-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<string | QuotedString> | 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;

Expand Down Expand Up @@ -139,6 +109,71 @@ export class ProcessTaskRunner implements TaskRunner {
}
}

private getResolvedCommand(taskConfig: TaskConfiguration): {
command: string | undefined,
args: Array<string | QuotedString> | undefined,
options: CommandOptions
} {
let systemSpecificCommand: {
command: string | undefined,
args: Array<string | QuotedString> | 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 if (!isWindows && !isOSX && taskConfig.linux !== undefined) { // on linux, linux-specific options, if available, take precedence
systemSpecificCommand = this.getSystemSpecificCommand(taskConfig, 'linux');
} else { // system-specific options are unavailable, use the default
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<string | QuotedString> | undefined,
options: CommandOptions
} {
// initialise with default values from the `taskConfig`
let command: string | undefined = taskConfig.command;
let args: Array<string | QuotedString> | 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)
Expand Down
Loading