提交 46e4970f 编写于 作者: D Dirk Baeumer

Add support for dependent tasks

上级 f4bb8881
......@@ -87,6 +87,12 @@ export interface TaskDescription extends PlatformTaskDescription {
*/
taskName: string;
/**
* A unique optional identifier in case the name
* can't be used as such.
*/
identifier?: string;
/**
* Windows specific task configuration
*/
......@@ -145,9 +151,9 @@ export interface TaskDescription extends PlatformTaskDescription {
suppressTaskName?: boolean;
/**
* The command this task is bound to.
* The other tasks the task depend on
*/
bindTo?: CommandBinding;
dependsOn?: string | string[];
/**
* The problem matcher(s) to use to capture problems in the tasks
......@@ -706,9 +712,11 @@ namespace TaskDescription {
let command: TaskSystem.CommandConfiguration = externalTask.command !== void 0
? CommandConfiguration.from(externalTask, context)
: externalTask.echoCommand !== void 0 ? { echo: !!externalTask.echoCommand } : undefined;
let identifer = Types.isString(externalTask.identifier) ? externalTask.identifier : taskName;
let task: TaskSystem.TaskDescription = {
id: UUID.generateUuid(),
name: taskName,
identifier: identifer,
command,
showOutput: undefined
};
......@@ -734,33 +742,58 @@ namespace TaskDescription {
} else if (externalTask.suppressTaskName !== void 0) {
task.suppressTaskName = !!externalTask.suppressTaskName;
}
if (externalTask.bindTo) {
task.bindTo = CommandBinding.from(externalTask.bindTo, context);
if (externalTask.dependsOn !== void 0) {
if (Types.isString(externalTask.dependsOn)) {
task.dependsOn = [externalTask.dependsOn];
} else if (Types.isStringArray(externalTask.dependsOn)) {
task.dependsOn = externalTask.dependsOn.slice();
}
}
if (problemMatchers) {
task.problemMatchers = problemMatchers;
}
mergeGlobals(task, globals);
fillDefaults(task);
parsedTasks[task.id] = task;
let addTask: boolean = true;
if (context.isTermnial && task.command && task.command.isShellCommand && task.command.args && task.command.args.length > 0) {
context.validationStatus.state = ValidationState.Warning;
context.logger.log(nls.localize('ConfigurationParser.shellArgs', 'The task {0} is a shell command and specifies arguments. To ensure correct command line quoting please merge args into the command.', task.name));
context.logger.log(nls.localize('taskConfiguration.shellArgs', 'Warning: the task {0} is a shell command and specifies arguments. To ensure correct command line quoting please merge args into the command.', task.name));
}
if (!Types.isUndefined(externalTask.isBuildCommand) && externalTask.isBuildCommand && defaultBuildTask.exact < 2) {
defaultBuildTask.id = task.id;
defaultBuildTask.exact = 2;
} else if (taskName === 'build' && defaultBuildTask.exact < 2) {
defaultBuildTask.id = task.id;
defaultBuildTask.exact = 1;
if (context.isTermnial) {
if ((task.command === void 0 || task.command.name === void 0) && (task.dependsOn === void 0 || task.dependsOn.length === 0)) {
context.validationStatus.state = ValidationState.Error;
context.logger.log(nls.localize(
'taskConfiguration.noCommandOrDependsOn', 'Error: the task {0} neither specifies a command or a dependsOn property. The task will be ignored. Its definition is:\n{1}',
task.name, JSON.stringify(externalTask, undefined, 4)
));
addTask = false;
}
} else {
if (task.command === void 0 || task.command.name === void 0) {
context.validationStatus.state = ValidationState.Warning;
context.logger.log(nls.localize(
'taskConfiguration.noCommand', 'Error: the task {0} doesn\'t define a command. The task will be ignored. Its definition is:\n{1}',
task.name, JSON.stringify(externalTask, undefined, 4)
));
addTask = false;
}
}
if (!Types.isUndefined(externalTask.isTestCommand) && externalTask.isTestCommand && defaultTestTask.exact < 2) {
defaultTestTask.id = task.id;
defaultTestTask.exact = 2;
} else if (taskName === 'test' && defaultTestTask.exact < 2) {
defaultTestTask.id = task.id;
defaultTestTask.exact = 1;
if (addTask) {
parsedTasks[task.id] = task;
if (!Types.isUndefined(externalTask.isBuildCommand) && externalTask.isBuildCommand && defaultBuildTask.exact < 2) {
defaultBuildTask.id = task.id;
defaultBuildTask.exact = 2;
} else if (taskName === 'build' && defaultBuildTask.exact < 2) {
defaultBuildTask.id = task.id;
defaultBuildTask.exact = 1;
}
if (!Types.isUndefined(externalTask.isTestCommand) && externalTask.isTestCommand && defaultTestTask.exact < 2) {
defaultTestTask.id = task.id;
defaultTestTask.exact = 2;
} else if (taskName === 'test' && defaultTestTask.exact < 2) {
defaultTestTask.id = task.id;
defaultTestTask.exact = 1;
}
}
});
let buildTask: string;
......@@ -813,16 +846,19 @@ namespace TaskDescription {
}
export function mergeGlobals(task: TaskSystem.TaskDescription, globals: Globals): void {
if (CommandConfiguration.isEmpty(task.command) && !CommandConfiguration.isEmpty(globals.command)) {
task.command = globals.command;
}
if (CommandConfiguration.onlyEcho(task.command)) {
// The globals can have a echo set which would override the local echo
// Saves the need of a additional fill method. But might be necessary
// at some point.
let oldEcho = task.command.echo;
CommandConfiguration.merge(task.command, globals.command);
task.command.echo = oldEcho;
// We only merge a command from a global definition if there is no dependsOn
if (task.dependsOn === void 0) {
if (CommandConfiguration.isEmpty(task.command) && !CommandConfiguration.isEmpty(globals.command) && globals.command.name !== void 0) {
task.command = globals.command;
}
if (CommandConfiguration.onlyEcho(task.command)) {
// The globals can have a echo set which would override the local echo
// Saves the need of a additional fill method. But might be necessary
// at some point.
let oldEcho = task.command.echo;
CommandConfiguration.merge(task.command, globals.command);
task.command.echo = oldEcho;
}
}
// promptOnClose is inferred from isBackground if available
if (task.promptOnClose === void 0 && task.isBackground === void 0 && globals.promptOnClose !== void 0) {
......@@ -1002,6 +1038,7 @@ class ConfigurationParser {
let task: TaskSystem.TaskDescription = {
id: UUID.generateUuid(),
name: globals.command.name,
identifier: globals.command.name,
command: undefined,
isBackground: isBackground,
showOutput: undefined,
......
......@@ -44,6 +44,9 @@ export interface TelemetryEvent {
// Whether the task ran successful
success: boolean;
// The exit code
exitCode?: number;
}
export namespace Triggers {
......@@ -168,6 +171,11 @@ export interface TaskDescription {
*/
name: string;
/**
* The task's identifier.
*/
identifier: string;
/**
* The command configuration
*/
......@@ -201,9 +209,9 @@ export interface TaskDescription {
showOutput: ShowOutput;
/**
* The command this task is bound to.
* The other tasks this task depends on.
*/
bindTo?: CommandBinding;
dependsOn?: string[];
/**
* The problem watchers to use for this task
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
const schema: IJSONSchema = {
definitions: {
showOutputType: {
type: 'string',
enum: ['always', 'silent', 'never']
},
options: {
type: 'object',
description: nls.localize('JsonSchema.options', 'Additional command options'),
properties: {
cwd: {
type: 'string',
description: nls.localize('JsonSchema.options.cwd', 'The current working directory of the executed program or script. If omitted Code\'s current workspace root is used.')
},
env: {
type: 'object',
additionalProperties: {
type: 'string'
},
description: nls.localize('JsonSchema.options.env', 'The environment of the executed program or shell. If omitted the parent process\' environment is used.')
}
},
additionalProperties: {
type: ['string', 'array', 'object']
}
},
patternType: {
anyOf: [
{
type: 'string',
enum: ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$cpp', '$csc', '$vb', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go']
},
{
$ref: '#/definitions/pattern'
},
{
type: 'array',
items: {
$ref: '#/definitions/pattern'
}
}
]
},
pattern: {
default: {
regexp: '^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$',
file: 1,
location: 2,
message: 3
},
additionalProperties: false,
properties: {
regexp: {
type: 'string',
description: nls.localize('JsonSchema.pattern.regexp', 'The regular expression to find an error, warning or info in the output.')
},
file: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.file', 'The match group index of the filename. If omitted 1 is used.')
},
location: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.location', 'The match group index of the problem\'s location. Valid location patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn). If omitted (line,column) is assumed.')
},
line: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.line', 'The match group index of the problem\'s line. Defaults to 2')
},
column: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.column', 'The match group index of the problem\'s line character. Defaults to 3')
},
endLine: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.endLine', 'The match group index of the problem\'s end line. Defaults to undefined')
},
endColumn: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.endColumn', 'The match group index of the problem\'s end line character. Defaults to undefined')
},
severity: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.severity', 'The match group index of the problem\'s severity. Defaults to undefined')
},
code: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.code', 'The match group index of the problem\'s code. Defaults to undefined')
},
message: {
type: 'integer',
description: nls.localize('JsonSchema.pattern.message', 'The match group index of the message. If omitted it defaults to 4 if location is specified. Otherwise it defaults to 5.')
},
loop: {
type: 'boolean',
description: nls.localize('JsonSchema.pattern.loop', 'In a multi line matcher loop indicated whether this pattern is executed in a loop as long as it matches. Can only specified on a last pattern in a multi line pattern.')
}
}
},
problemMatcherType: {
oneOf: [
{
type: 'string',
enum: ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go']
},
{
$ref: '#/definitions/problemMatcher'
},
{
type: 'array',
items: {
anyOf: [
{
$ref: '#/definitions/problemMatcher'
},
{
type: 'string',
enum: ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go']
}
]
}
}
]
},
watchingPattern: {
type: 'object',
additionalProperties: false,
properties: {
regexp: {
type: 'string',
description: nls.localize('JsonSchema.watchingPattern.regexp', 'The regular expression to detect the begin or end of a watching task.')
},
file: {
type: 'integer',
description: nls.localize('JsonSchema.watchingPattern.file', 'The match group index of the filename. Can be omitted.')
},
}
},
problemMatcher: {
type: 'object',
additionalProperties: false,
properties: {
base: {
type: 'string',
enum: ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go'],
description: nls.localize('JsonSchema.problemMatcher.base', 'The name of a base problem matcher to use.')
},
owner: {
type: 'string',
description: nls.localize('JsonSchema.problemMatcher.owner', 'The owner of the problem inside Code. Can be omitted if base is specified. Defaults to \'external\' if omitted and base is not specified.')
},
severity: {
type: 'string',
enum: ['error', 'warning', 'info'],
description: nls.localize('JsonSchema.problemMatcher.severity', 'The default severity for captures problems. Is used if the pattern doesn\'t define a match group for severity.')
},
applyTo: {
type: 'string',
enum: ['allDocuments', 'openDocuments', 'closedDocuments'],
description: nls.localize('JsonSchema.problemMatcher.applyTo', 'Controls if a problem reported on a text document is applied only to open, closed or all documents.')
},
pattern: {
$ref: '#/definitions/patternType',
description: nls.localize('JsonSchema.problemMatcher.pattern', 'A problem pattern or the name of a predefined problem pattern. Can be omitted if base is specified.')
},
fileLocation: {
oneOf: [
{
type: 'string',
enum: ['absolute', 'relative']
},
{
type: 'array',
items: {
type: 'string'
}
}
],
description: nls.localize('JsonSchema.problemMatcher.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted.')
},
watching: {
type: 'object',
additionalProperties: false,
properties: {
activeOnStart: {
type: 'boolean',
description: nls.localize('JsonSchema.problemMatcher.watching.activeOnStart', 'If set to true the watcher is in active mode when the task starts. This is equals of issuing a line that matches the beginPattern')
},
beginsPattern: {
oneOf: [
{
type: 'string'
},
{
type: '#/definitions/watchingPattern'
}
],
description: nls.localize('JsonSchema.problemMatcher.watching.beginsPattern', 'If matched in the output the start of a watching task is signaled.')
},
endsPattern: {
oneOf: [
{
type: 'string'
},
{
type: '#/definitions/watchingPattern'
}
],
description: nls.localize('JsonSchema.problemMatcher.watching.endsPattern', 'If matched in the output the end of a watching task is signaled.')
}
}
},
watchedTaskBeginsRegExp: {
type: 'string',
description: nls.localize('JsonSchema.problemMatcher.watchedBegin', 'A regular expression signaling that a watched tasks begins executing triggered through file watching.')
},
watchedTaskEndsRegExp: {
type: 'string',
description: nls.localize('JsonSchema.problemMatcher.watchedEnd', 'A regular expression signaling that a watched tasks ends executing.')
}
}
},
shellConfiguration: {
type: 'object',
additionalProperties: false,
properties: {
executable: {
type: 'string',
description: nls.localize('JsonSchema.shell.executable', 'The shell to be used.')
},
args: {
type: 'array',
description: nls.localize('JsonSchema.shell.args', 'The shell arguments.'),
items: {
type: 'string'
}
}
}
},
commandConfiguration: {
type: 'object',
additionalProperties: false,
properties: {
command: {
type: 'string',
description: nls.localize('JsonSchema.command', 'The command to be executed. Can be an external program or a shell command.')
},
args: {
type: 'array',
description: nls.localize('JsonSchema.tasks.args', 'Arguments passed to the command when this task is invoked.'),
items: {
type: 'string'
}
},
options: {
$ref: '#/definitions/options'
}
}
},
taskDescription: {
type: 'object',
required: ['taskName'],
additionalProperties: false,
properties: {
taskName: {
type: 'string',
description: nls.localize('JsonSchema.tasks.taskName', "The task's name")
},
command: {
type: 'string',
description: nls.localize('JsonSchema.command', 'The command to be executed. Can be an external program or a shell command.')
},
args: {
type: 'array',
description: nls.localize('JsonSchema.tasks.args', 'Arguments passed to the command when this task is invoked.'),
items: {
type: 'string'
}
},
options: {
$ref: '#/definitions/options'
},
windows: {
$ref: '#/definitions/commandConfiguration',
description: nls.localize('JsonSchema.tasks.windows', 'Windows specific command configuration')
},
osx: {
$ref: '#/definitions/commandConfiguration',
description: nls.localize('JsonSchema.tasks.mac', 'Mac specific command configuration')
},
linux: {
$ref: '#/definitions/commandConfiguration',
description: nls.localize('JsonSchema.tasks.linux', 'Linux specific command configuration')
},
suppressTaskName: {
type: 'boolean',
description: nls.localize('JsonSchema.tasks.suppressTaskName', 'Controls whether the task name is added as an argument to the command. If omitted the globally defined value is used.'),
default: true
},
showOutput: {
$ref: '#/definitions/showOutputType',
description: nls.localize('JsonSchema.tasks.showOutput', 'Controls whether the output of the running task is shown or not. If omitted the globally defined value is used.')
},
echoCommand: {
type: 'boolean',
description: nls.localize('JsonSchema.echoCommand', 'Controls whether the executed command is echoed to the output. Default is false.'),
default: true
},
isWatching: {
type: 'boolean',
deprecationMessage: nls.localize('JsonSchema.tasks.watching.deprecation', 'Deprecated. Use isBackground instead.'),
description: nls.localize('JsonSchema.tasks.watching', 'Whether the executed task is kept alive and is watching the file system.'),
default: true
},
isBackground: {
type: 'boolean',
description: nls.localize('JsonSchema.tasks.background', 'Whether the executed task is kept alive and is running in the background.'),
default: true
},
promptOnClose: {
type: 'boolean',
description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when VS Code closes with a running task.'),
default: false
},
isBuildCommand: {
type: 'boolean',
description: nls.localize('JsonSchema.tasks.build', 'Maps this task to Code\'s default build command.'),
default: true
},
isTestCommand: {
type: 'boolean',
description: nls.localize('JsonSchema.tasks.test', 'Maps this task to Code\'s default test command.'),
default: true
},
problemMatcher: {
$ref: '#/definitions/problemMatcherType',
description: nls.localize('JsonSchema.tasks.matchers', 'The problem matcher(s) to use. Can either be a string or a problem matcher definition or an array of strings and problem matchers.')
}
}
},
taskRunnerConfiguration: {
type: 'object',
properties: {
command: {
type: 'string',
description: nls.localize('JsonSchema.command', 'The command to be executed. Can be an external program or a shell command.')
},
args: {
type: 'array',
description: nls.localize('JsonSchema.args', 'Additional arguments passed to the command.'),
items: {
type: 'string'
}
},
options: {
$ref: '#/definitions/options'
},
showOutput: {
$ref: '#/definitions/showOutputType',
description: nls.localize('JsonSchema.showOutput', 'Controls whether the output of the running task is shown or not. If omitted \'always\' is used.')
},
isWatching: {
type: 'boolean',
deprecationMessage: nls.localize('JsonSchema.watching.deprecation', 'Deprecated. Use isBackground instead.'),
description: nls.localize('JsonSchema.watching', 'Whether the executed task is kept alive and is watching the file system.'),
default: true
},
isBackground: {
type: 'boolean',
description: nls.localize('JsonSchema.background', 'Whether the executed task is kept alive and is running in the background.'),
default: true
},
promptOnClose: {
type: 'boolean',
description: nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when VS Code closes with a running background task.'),
default: false
},
echoCommand: {
type: 'boolean',
description: nls.localize('JsonSchema.echoCommand', 'Controls whether the executed command is echoed to the output. Default is false.'),
default: true
},
suppressTaskName: {
type: 'boolean',
description: nls.localize('JsonSchema.suppressTaskName', 'Controls whether the task name is added as an argument to the command. Default is false.'),
default: true
},
taskSelector: {
type: 'string',
description: nls.localize('JsonSchema.taskSelector', 'Prefix to indicate that an argument is task.')
},
problemMatcher: {
$ref: '#/definitions/problemMatcherType',
description: nls.localize('JsonSchema.matchers', 'The problem matcher(s) to use. Can either be a string or a problem matcher definition or an array of strings and problem matchers.')
},
tasks: {
type: 'array',
description: nls.localize('JsonSchema.tasks', 'The task configurations. Usually these are enrichments of task already defined in the external task runner.'),
items: {
type: 'object',
$ref: '#/definitions/taskDescription'
}
}
}
}
}
};
export default schema;
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import * as Objects from 'vs/base/common/objects';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import commonSchema from './jsonSchemaCommon';
const schema: IJSONSchema = {
oneOf: [
{
'allOf': [
{
'type': 'object',
'required': ['version'],
'properties': {
'version': {
'type': 'string',
'enum': ['0.1.0'],
'description': nls.localize('JsonSchema.version', 'The config\'s version number')
},
'windows': {
'$ref': '#/definitions/taskRunnerConfiguration',
'description': nls.localize('JsonSchema.windows', 'Windows specific command configuration')
},
'osx': {
'$ref': '#/definitions/taskRunnerConfiguration',
'description': nls.localize('JsonSchema.mac', 'Mac specific command configuration')
},
'linux': {
'$ref': '#/definitions/taskRunnerConfiguration',
'description': nls.localize('JsonSchema.linux', 'Linux specific command configuration')
}
}
},
{
'$ref': '#/definitions/taskRunnerConfiguration'
}
]
}
]
};
const shellCommand: IJSONSchema = {
type: 'boolean',
default: true,
description: nls.localize('JsonSchema.shell', 'Specifies whether the command is a shell command or an external program. Defaults to false if omitted.')
};
schema.definitions = Objects.deepClone(commonSchema.definitions);
let definitions = schema.definitions;
definitions['commandConfiguration']['properties']['isShellCommand'] = Objects.deepClone(shellCommand);
definitions['taskDescription']['properties']['isShellCommand'] = Objects.deepClone(shellCommand);
definitions['taskRunnerConfiguration']['properties']['isShellCommand'] = Objects.deepClone(shellCommand);
Object.getOwnPropertyNames(definitions).forEach(key => {
let newKey = key + '1';
definitions[newKey] = definitions[key];
delete definitions[key];
});
function fixReferences(literal: any) {
if (Array.isArray(literal)) {
literal.forEach(fixReferences);
} else if (typeof literal === 'object') {
if (literal['$ref']) {
literal['$ref'] = literal['$ref'] + '1';
}
Object.getOwnPropertyNames(literal).forEach(property => {
let value = literal[property];
if (Array.isArray(value) || typeof value === 'object') {
fixReferences(value);
}
});
}
}
fixReferences(schema);
export default schema;
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import * as Objects from 'vs/base/common/objects';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import commonSchema from './jsonSchemaCommon';
const schema: IJSONSchema = {
oneOf: [
{
'allOf': [
{
'type': 'object',
'required': ['version'],
'properties': {
'version': {
'type': 'string',
'enum': ['2.0.0'],
'description': nls.localize('JsonSchema.version', 'The config\'s version number')
},
'windows': {
'$ref': '#/definitions/taskRunnerConfiguration',
'description': nls.localize('JsonSchema.windows', 'Windows specific command configuration')
},
'osx': {
'$ref': '#/definitions/taskRunnerConfiguration',
'description': nls.localize('JsonSchema.mac', 'Mac specific command configuration')
},
'linux': {
'$ref': '#/definitions/taskRunnerConfiguration',
'description': nls.localize('JsonSchema.linux', 'Linux specific command configuration')
}
}
},
{
'$ref': '#/definitions/taskRunnerConfiguration'
}
]
}
]
};
const shellCommand: IJSONSchema = {
anyOf: [
{
type: 'boolean',
default: true,
description: nls.localize('JsonSchema.shell', 'Specifies whether the command is a shell command or an external program. Defaults to false if omitted.')
},
{
$ref: '#definitions/shellConfiguration'
}
]
};
const dependsOn: IJSONSchema = {
anyOf: [
{
type: 'string',
default: true,
description: nls.localize('JsonSchema.tasks.dependsOn.string', 'Another task this task depends on.')
},
{
type: 'array',
description: nls.localize('JsonSchema.tasks.dependsOn.array', 'The other tasks this task depends on.'),
items: {
type: 'string'
}
}
]
};
schema.definitions = Objects.deepClone(commonSchema.definitions);
let definitions = schema.definitions;
definitions['commandConfiguration']['properties']['isShellCommand'] = Objects.deepClone(shellCommand);
definitions['taskDescription']['properties']['isShellCommand'] = Objects.deepClone(shellCommand);
definitions['taskDescription']['properties']['dependsOn'] = dependsOn;
definitions['taskRunnerConfiguration']['properties']['isShellCommand'] = Objects.deepClone(shellCommand);
Object.getOwnPropertyNames(definitions).forEach(key => {
let newKey = key + '2';
definitions[newKey] = definitions[key];
delete definitions[key];
});
function fixReferences(literal: any) {
if (Array.isArray(literal)) {
literal.forEach(fixReferences);
} else if (typeof literal === 'object') {
if (literal['$ref']) {
literal['$ref'] = literal['$ref'] + '2';
}
Object.getOwnPropertyNames(literal).forEach(property => {
let value = literal[property];
if (Array.isArray(value) || typeof value === 'object') {
fixReferences(value);
}
});
}
}
fixReferences(schema);
export default schema;
\ No newline at end of file
......@@ -1006,7 +1006,7 @@ class TaskService extends EventEmitter implements ITaskService {
if (Types.isString(arg)) {
this.tasks().then(tasks => {
for (let task of tasks) {
if (task.name === arg || (task.bindTo && task.bindTo.identifier === arg)) {
if (task.identifier === arg) {
this.run(task.id);
}
}
......@@ -1081,502 +1081,35 @@ outputChannelRegistry.registerChannel(TaskService.OutputChannelId, TaskService.O
// tasks.json validation
let schemaId = 'vscode://schemas/tasks';
let schema: IJSONSchema =
{
'id': schemaId,
'description': 'Task definition file',
'type': 'object',
'default': {
'version': '0.1.0',
'command': 'myCommand',
'isShellCommand': false,
'args': [],
'showOutput': 'always',
'tasks': [
{
'taskName': 'build',
'showOutput': 'silent',
'isBuildCommand': true,
'problemMatcher': ['$tsc', '$lessCompile']
}
]
},
'definitions': {
'showOutputType': {
'type': 'string',
'enum': ['always', 'silent', 'never']
},
'options': {
'type': 'object',
'description': nls.localize('JsonSchema.options', 'Additional command options'),
'properties': {
'cwd': {
'type': 'string',
'description': nls.localize('JsonSchema.options.cwd', 'The current working directory of the executed program or script. If omitted Code\'s current workspace root is used.')
},
'env': {
'type': 'object',
'additionalProperties': {
'type': 'string'
},
'description': nls.localize('JsonSchema.options.env', 'The environment of the executed program or shell. If omitted the parent process\' environment is used.')
}
},
'additionalProperties': {
'type': ['string', 'array', 'object']
}
},
'patternType': {
'anyOf': [
{
'type': 'string',
'enum': ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$cpp', '$csc', '$vb', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go']
},
{
'$ref': '#/definitions/pattern'
},
{
'type': 'array',
'items': {
'$ref': '#/definitions/pattern'
}
}
]
},
'pattern': {
'default': {
'regexp': '^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$',
'file': 1,
'location': 2,
'message': 3
},
'additionalProperties': false,
'properties': {
'regexp': {
'type': 'string',
'description': nls.localize('JsonSchema.pattern.regexp', 'The regular expression to find an error, warning or info in the output.')
},
'file': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.file', 'The match group index of the filename. If omitted 1 is used.')
},
'location': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.location', 'The match group index of the problem\'s location. Valid location patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn). If omitted (line,column) is assumed.')
},
'line': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.line', 'The match group index of the problem\'s line. Defaults to 2')
},
'column': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.column', 'The match group index of the problem\'s line character. Defaults to 3')
},
'endLine': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.endLine', 'The match group index of the problem\'s end line. Defaults to undefined')
},
'endColumn': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.endColumn', 'The match group index of the problem\'s end line character. Defaults to undefined')
},
'severity': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.severity', 'The match group index of the problem\'s severity. Defaults to undefined')
},
'code': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.code', 'The match group index of the problem\'s code. Defaults to undefined')
},
'message': {
'type': 'integer',
'description': nls.localize('JsonSchema.pattern.message', 'The match group index of the message. If omitted it defaults to 4 if location is specified. Otherwise it defaults to 5.')
},
'loop': {
'type': 'boolean',
'description': nls.localize('JsonSchema.pattern.loop', 'In a multi line matcher loop indicated whether this pattern is executed in a loop as long as it matches. Can only specified on a last pattern in a multi line pattern.')
}
}
},
'problemMatcherType': {
'oneOf': [
{
'type': 'string',
'enum': ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go']
},
{
'$ref': '#/definitions/problemMatcher'
},
{
'type': 'array',
'items': {
'anyOf': [
{
'$ref': '#/definitions/problemMatcher'
},
{
'type': 'string',
'enum': ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go']
}
]
}
}
]
},
'watchingPattern': {
'type': 'object',
'additionalProperties': false,
'properties': {
'regexp': {
'type': 'string',
'description': nls.localize('JsonSchema.watchingPattern.regexp', 'The regular expression to detect the begin or end of a watching task.')
},
'file': {
'type': 'integer',
'description': nls.localize('JsonSchema.watchingPattern.file', 'The match group index of the filename. Can be omitted.')
},
}
},
'problemMatcher': {
'type': 'object',
'additionalProperties': false,
'properties': {
'base': {
'type': 'string',
'enum': ['$tsc', '$tsc-watch', '$msCompile', '$lessCompile', '$gulp-tsc', '$jshint', '$jshint-stylish', '$eslint-compact', '$eslint-stylish', '$go'],
'description': nls.localize('JsonSchema.problemMatcher.base', 'The name of a base problem matcher to use.')
},
'owner': {
'type': 'string',
'description': nls.localize('JsonSchema.problemMatcher.owner', 'The owner of the problem inside Code. Can be omitted if base is specified. Defaults to \'external\' if omitted and base is not specified.')
},
'severity': {
'type': 'string',
'enum': ['error', 'warning', 'info'],
'description': nls.localize('JsonSchema.problemMatcher.severity', 'The default severity for captures problems. Is used if the pattern doesn\'t define a match group for severity.')
},
'applyTo': {
'type': 'string',
'enum': ['allDocuments', 'openDocuments', 'closedDocuments'],
'description': nls.localize('JsonSchema.problemMatcher.applyTo', 'Controls if a problem reported on a text document is applied only to open, closed or all documents.')
},
'pattern': {
'$ref': '#/definitions/patternType',
'description': nls.localize('JsonSchema.problemMatcher.pattern', 'A problem pattern or the name of a predefined problem pattern. Can be omitted if base is specified.')
},
'fileLocation': {
'oneOf': [
{
'type': 'string',
'enum': ['absolute', 'relative']
},
{
'type': 'array',
'items': {
'type': 'string'
}
}
],
'description': nls.localize('JsonSchema.problemMatcher.fileLocation', 'Defines how file names reported in a problem pattern should be interpreted.')
},
'watching': {
'type': 'object',
'additionalProperties': false,
'properties': {
'activeOnStart': {
'type': 'boolean',
'description': nls.localize('JsonSchema.problemMatcher.watching.activeOnStart', 'If set to true the watcher is in active mode when the task starts. This is equals of issuing a line that matches the beginPattern')
},
'beginsPattern': {
'oneOf': [
{
'type': 'string'
},
{
'type': '#/definitions/watchingPattern'
}
],
'description': nls.localize('JsonSchema.problemMatcher.watching.beginsPattern', 'If matched in the output the start of a watching task is signaled.')
},
'endsPattern': {
'oneOf': [
{
'type': 'string'
},
{
'type': '#/definitions/watchingPattern'
}
],
'description': nls.localize('JsonSchema.problemMatcher.watching.endsPattern', 'If matched in the output the end of a watching task is signaled.')
}
}
},
'watchedTaskBeginsRegExp': {
'type': 'string',
'description': nls.localize('JsonSchema.problemMatcher.watchedBegin', 'A regular expression signaling that a watched tasks begins executing triggered through file watching.')
},
'watchedTaskEndsRegExp': {
'type': 'string',
'description': nls.localize('JsonSchema.problemMatcher.watchedEnd', 'A regular expression signaling that a watched tasks ends executing.')
}
}
},
'baseTaskRunnerConfiguration': {
'type': 'object',
'properties': {
'command': {
'type': 'string',
'description': nls.localize('JsonSchema.command', 'The command to be executed. Can be an external program or a shell command.')
},
'isShellCommand': {
'anyOf': [
{
'type': 'boolean',
'default': true,
'description': nls.localize('JsonSchema.shell', 'Specifies whether the command is a shell command or an external program. Defaults to false if omitted.')
},
{
'$ref': '#definitions/shellConfiguration'
}
]
},
'args': {
'type': 'array',
'description': nls.localize('JsonSchema.args', 'Additional arguments passed to the command.'),
'items': {
'type': 'string'
}
},
'options': {
'$ref': '#/definitions/options'
},
'showOutput': {
'$ref': '#/definitions/showOutputType',
'description': nls.localize('JsonSchema.showOutput', 'Controls whether the output of the running task is shown or not. If omitted \'always\' is used.')
},
'isWatching': {
'type': 'boolean',
'deprecationMessage': nls.localize('JsonSchema.watching.deprecation', 'Deprecated. Use isBackground instead.'),
'description': nls.localize('JsonSchema.watching', 'Whether the executed task is kept alive and is watching the file system.'),
'default': true
},
'isBackground': {
'type': 'boolean',
'description': nls.localize('JsonSchema.background', 'Whether the executed task is kept alive and is running in the background.'),
'default': true
},
'promptOnClose': {
'type': 'boolean',
'description': nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when VS Code closes with a running background task.'),
'default': false
},
'echoCommand': {
'type': 'boolean',
'description': nls.localize('JsonSchema.echoCommand', 'Controls whether the executed command is echoed to the output. Default is false.'),
'default': true
},
'suppressTaskName': {
'type': 'boolean',
'description': nls.localize('JsonSchema.suppressTaskName', 'Controls whether the task name is added as an argument to the command. Default is false.'),
'default': true
},
'taskSelector': {
'type': 'string',
'description': nls.localize('JsonSchema.taskSelector', 'Prefix to indicate that an argument is task.')
},
'problemMatcher': {
'$ref': '#/definitions/problemMatcherType',
'description': nls.localize('JsonSchema.matchers', 'The problem matcher(s) to use. Can either be a string or a problem matcher definition or an array of strings and problem matchers.')
},
'tasks': {
'type': 'array',
'description': nls.localize('JsonSchema.tasks', 'The task configurations. Usually these are enrichments of task already defined in the external task runner.'),
'items': {
'type': 'object',
'$ref': '#/definitions/taskDescription'
}
}
}
},
'shellConfiguration': {
'type': 'object',
'additionalProperties': false,
'properties': {
'executable': {
'type': 'string',
'description': nls.localize('JsonSchema.shell.executable', 'The shell to be used.')
},
'args': {
'type': 'array',
'description': nls.localize('JsonSchema.shell.args', 'The shell arguments.'),
'items': {
'type': 'string'
}
}
}
},
'commandConfiguration': {
'type': 'object',
'additionalProperties': false,
'properties': {
'command': {
'type': 'string',
'description': nls.localize('JsonSchema.command', 'The command to be executed. Can be an external program or a shell command.')
},
'isShellCommand': {
'anyOf': [
{
'type': 'boolean',
'default': true,
'description': nls.localize('JsonSchema.shell', 'Specifies whether the command is a shell command or an external program. Defaults to false if omitted.')
},
{
'$ref': '#definitions/shellConfiguration'
}
]
},
'args': {
'type': 'array',
'description': nls.localize('JsonSchema.tasks.args', 'Arguments passed to the command when this task is invoked.'),
'items': {
'type': 'string'
}
},
'options': {
'$ref': '#/definitions/options'
}
}
},
'taskDescription': {
'type': 'object',
'required': ['taskName'],
'additionalProperties': false,
'properties': {
'taskName': {
'type': 'string',
'description': nls.localize('JsonSchema.tasks.taskName', "The task's name")
},
'command': {
'type': 'string',
'description': nls.localize('JsonSchema.command', 'The command to be executed. Can be an external program or a shell command.')
},
'isShellCommand': {
'anyOf': [
{
'type': 'boolean',
'default': true,
'description': nls.localize('JsonSchema.shell', 'Specifies whether the command is a shell command or an external program. Defaults to false if omitted.')
},
{
'$ref': '#definitions/shellConfiguration'
}
]
},
'args': {
'type': 'array',
'description': nls.localize('JsonSchema.tasks.args', 'Arguments passed to the command when this task is invoked.'),
'items': {
'type': 'string'
}
},
'options': {
'$ref': '#/definitions/options'
},
'windows': {
'$ref': '#/definitions/commandConfiguration',
'description': nls.localize('JsonSchema.tasks.windows', 'Windows specific command configuration')
},
'osx': {
'$ref': '#/definitions/commandConfiguration',
'description': nls.localize('JsonSchema.tasks.mac', 'Mac specific command configuration')
},
'linux': {
'$ref': '#/definitions/commandConfiguration',
'description': nls.localize('JsonSchema.tasks.linux', 'Linux specific command configuration')
},
'suppressTaskName': {
'type': 'boolean',
'description': nls.localize('JsonSchema.tasks.suppressTaskName', 'Controls whether the task name is added as an argument to the command. If omitted the globally defined value is used.'),
'default': true
},
'showOutput': {
'$ref': '#/definitions/showOutputType',
'description': nls.localize('JsonSchema.tasks.showOutput', 'Controls whether the output of the running task is shown or not. If omitted the globally defined value is used.')
},
'echoCommand': {
'type': 'boolean',
'description': nls.localize('JsonSchema.echoCommand', 'Controls whether the executed command is echoed to the output. Default is false.'),
'default': true
},
'isWatching': {
'type': 'boolean',
'deprecationMessage': nls.localize('JsonSchema.tasks.watching.deprecation', 'Deprecated. Use isBackground instead.'),
'description': nls.localize('JsonSchema.tasks.watching', 'Whether the executed task is kept alive and is watching the file system.'),
'default': true
},
'isBackground': {
'type': 'boolean',
'description': nls.localize('JsonSchema.tasks.background', 'Whether the executed task is kept alive and is running in the background.'),
'default': true
},
'promptOnClose': {
'type': 'boolean',
'description': nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when VS Code closes with a running task.'),
'default': false
},
'isBuildCommand': {
'type': 'boolean',
'description': nls.localize('JsonSchema.tasks.build', 'Maps this task to Code\'s default build command.'),
'default': true
},
'isTestCommand': {
'type': 'boolean',
'description': nls.localize('JsonSchema.tasks.test', 'Maps this task to Code\'s default test command.'),
'default': true
},
'problemMatcher': {
'$ref': '#/definitions/problemMatcherType',
'description': nls.localize('JsonSchema.tasks.matchers', 'The problem matcher(s) to use. Can either be a string or a problem matcher definition or an array of strings and problem matchers.')
}
},
'defaultSnippets': [
{
'label': 'Empty task',
'body': {
'taskName': '${1:taskName}'
}
}
]
}
},
'allOf': [
let schema: IJSONSchema = {
id: schemaId,
description: 'Task definition file',
type: 'object',
default: {
version: '0.1.0',
command: 'myCommand',
isShellCommand: false,
args: [],
showOutput: 'always',
tasks: [
{
'type': 'object',
'required': ['version'],
'properties': {
'version': {
'type': 'string',
'enum': ['0.1.0'],
'description': nls.localize('JsonSchema.version', 'The config\'s version number')
},
'windows': {
'$ref': '#/definitions/baseTaskRunnerConfiguration',
'description': nls.localize('JsonSchema.windows', 'Windows specific command configuration')
},
'osx': {
'$ref': '#/definitions/baseTaskRunnerConfiguration',
'description': nls.localize('JsonSchema.mac', 'Mac specific command configuration')
},
'linux': {
'$ref': '#/definitions/baseTaskRunnerConfiguration',
'description': nls.localize('JsonSchema.linux', 'Linux specific command configuration')
}
}
},
{
'$ref': '#/definitions/baseTaskRunnerConfiguration'
taskName: 'build',
showOutput: 'silent',
isBuildCommand: true,
problemMatcher: ['$tsc', '$lessCompile']
}
]
};
}
};
import schemaVersion1 from './jsonSchema_v1';
import schemaVersion2 from './jsonSchema_v2';
schema.definitions = {
...schemaVersion1.definitions,
...schemaVersion2.definitions,
};
schema.oneOf = [...schemaVersion1.oneOf, ...schemaVersion2.oneOf];
let jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>Registry.as(jsonContributionRegistry.Extensions.JSONContribution);
jsonRegistry.registerSchema(schemaId, schema);
......@@ -99,17 +99,20 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
public static TelemetryEventName: string = 'taskService';
private configuration: TaskRunnerConfiguration;
private identifier2Task: IStringDictionary<TaskDescription>;
private outputChannel: IOutputChannel;
private activeTasks: IStringDictionary<ActiveTerminalData>;
private primaryTerminal: PrimaryTerminal;
private terminals: IStringDictionary<TerminalData>;
private idleTaskTerminals: IStringDictionary<string>;
constructor(private configuration: TaskRunnerConfiguration, private terminalService: ITerminalService, private outputService: IOutputService,
constructor(configuration: TaskRunnerConfiguration, private terminalService: ITerminalService, private outputService: IOutputService,
private markerService: IMarkerService, private modelService: IModelService, private configurationResolverService: IConfigurationResolverService,
private telemetryService: ITelemetryService, outputChannelId: string) {
super();
this.setConfiguration(configuration);
this.outputChannel = this.outputService.getChannel(outputChannelId);
this.activeTasks = Object.create(null);
this.terminals = Object.create(null);
......@@ -118,6 +121,11 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
public setConfiguration(configuration: TaskRunnerConfiguration) {
this.configuration = configuration;
this.identifier2Task = Object.create(null);
Object.keys(this.configuration.tasks).forEach((key) => {
let task = this.configuration.tasks[key];
this.identifier2Task[task.identifier] = task;
});
}
public log(value: string): void {
......@@ -155,25 +163,17 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
if (!task) {
throw new TaskError(Severity.Info, nls.localize('TerminalTaskSystem.noTask', 'Task \'{0}\' not found', taskIdentifier), TaskErrors.TaskNotFound);
}
let telemetryEvent: TelemetryEvent = {
trigger: trigger,
command: 'other',
success: true
};
let terminalData = this.activeTasks[task.id];
if (terminalData && terminalData.promise) {
if (task.showOutput === ShowOutput.Always) {
terminalData.terminal.setVisible(true);
}
return { kind: TaskExecuteKind.Active, active: { same: true, background: task.isBackground }, promise: terminalData.promise };
}
try {
let result = this.executeTask(task, telemetryEvent);
result.promise = result.promise.then((summary) => {
this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent);
return summary;
}, (error) => {
telemetryEvent.success = false;
this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent);
return TPromise.wrapError<ITaskSummary>(error);
});
return result;
return { kind: TaskExecuteKind.Started, started: {}, promise: this.executeTask(Object.create(null), task, trigger) };
} catch (error) {
telemetryEvent.success = false;
this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent);
if (error instanceof TaskError) {
throw error;
} else if (error instanceof Error) {
......@@ -217,105 +217,158 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
return TPromise.as(result);
}
private executeTask(task: TaskDescription, telemetryEvent: TelemetryEvent): ITaskExecuteResult {
let terminalData = this.activeTasks[task.id];
if (terminalData && terminalData.promise) {
if (task.showOutput === ShowOutput.Always) {
terminalData.terminal.setVisible(true);
}
return { kind: TaskExecuteKind.Active, active: { same: true, background: task.isBackground }, promise: terminalData.promise };
private executeTask(startedTasks: IStringDictionary<TPromise<ITaskSummary>>, task: TaskDescription, trigger: string): TPromise<ITaskSummary> {
let promises: TPromise<ITaskSummary>[] = [];
if (task.dependsOn) {
task.dependsOn.forEach((identifier) => {
let task = this.identifier2Task[identifier];
if (task) {
let promise = startedTasks[task.id];
if (!promise) {
promise = this.executeTask(startedTasks, task, trigger);
startedTasks[task.id] = promise;
}
promises.push(promise);
}
});
}
if (task.command) {
return TPromise.join(promises).then((summaries): ITaskSummary => {
for (let summary of summaries) {
if (summary.exitCode !== 0) {
return { exitCode: summary.exitCode };
}
}
return this.executeCommand(task, trigger);
});
} else {
let terminal: ITerminalInstance = undefined;
let promise: TPromise<ITaskSummary> = undefined;
if (task.isBackground) {
promise = new TPromise<ITaskSummary>((resolve, reject) => {
let watchingProblemMatcher = new WatchingProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService);
let toUnbind: IDisposable[] = [];
let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.Watching };
let eventCounter: number = 0;
toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingBeginDetected, () => {
eventCounter++;
this.emit(TaskSystemEvents.Active, event);
}));
toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingEndDetected, () => {
eventCounter--;
this.emit(TaskSystemEvents.Inactive, event);
}));
watchingProblemMatcher.aboutToStart();
let delayer: Async.Delayer<any> = null;
let decoder = new TerminalDecoder();
terminal = this.createTerminal(task);
const onData = terminal.onData((data: string) => {
decoder.write(data).forEach(line => {
watchingProblemMatcher.processLine(line);
if (delayer === null) {
delayer = new Async.Delayer(3000);
}
delayer.trigger(() => {
watchingProblemMatcher.forceDelivery();
delayer = null;
});
});
});
const onExit = terminal.onExit((exitCode) => {
onData.dispose();
onExit.dispose();
delete this.activeTasks[task.id];
if (this.primaryTerminal && this.primaryTerminal.terminal === terminal) {
this.primaryTerminal.busy = false;
}
this.idleTaskTerminals[task.id] = terminal.id.toString();
watchingProblemMatcher.dispose();
toUnbind = dispose(toUnbind);
toUnbind = null;
for (let i = 0; i < eventCounter; i++) {
this.emit(TaskSystemEvents.Inactive, event);
}
eventCounter = 0;
if (exitCode && exitCode === 1 && watchingProblemMatcher.numberOfMatches === 0 && task.showOutput !== ShowOutput.Never) {
this.terminalService.setActiveInstance(terminal);
this.terminalService.showPanel(false);
}
resolve({ exitCode });
});
});
} else {
promise = new TPromise<ITaskSummary>((resolve, reject) => {
terminal = this.createTerminal(task);
return TPromise.join(promises).then((summaries): ITaskSummary => {
for (let summary of summaries) {
if (summary.exitCode !== 0) {
return { exitCode: summary.exitCode };
}
}
return { exitCode: 0 };
});
}
}
private executeCommand(task: TaskDescription, trigger: string): TPromise<ITaskSummary> {
let terminal: ITerminalInstance = undefined;
let executedCommand: string = undefined;
let promise: TPromise<ITaskSummary> = undefined;
if (task.isBackground) {
promise = new TPromise<ITaskSummary>((resolve, reject) => {
let watchingProblemMatcher = new WatchingProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService);
let toUnbind: IDisposable[] = [];
let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.Watching };
let eventCounter: number = 0;
toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingBeginDetected, () => {
eventCounter++;
this.emit(TaskSystemEvents.Active, event);
let decoder = new TerminalDecoder();
let startStopProblemMatcher = new StartStopProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService);
const onData = terminal.onData((data: string) => {
decoder.write(data).forEach((line) => {
startStopProblemMatcher.processLine(line);
}));
toUnbind.push(watchingProblemMatcher.addListener2(ProblemCollectorEvents.WatchingEndDetected, () => {
eventCounter--;
this.emit(TaskSystemEvents.Inactive, event);
}));
watchingProblemMatcher.aboutToStart();
let delayer: Async.Delayer<any> = null;
let decoder = new TerminalDecoder();
[terminal, executedCommand] = this.createTerminal(task);
const onData = terminal.onData((data: string) => {
decoder.write(data).forEach(line => {
watchingProblemMatcher.processLine(line);
if (delayer === null) {
delayer = new Async.Delayer(3000);
}
delayer.trigger(() => {
watchingProblemMatcher.forceDelivery();
delayer = null;
});
});
const onExit = terminal.onExit((exitCode) => {
onData.dispose();
onExit.dispose();
delete this.activeTasks[task.id];
if (this.primaryTerminal && this.primaryTerminal.terminal === terminal) {
this.primaryTerminal.busy = false;
}
this.idleTaskTerminals[task.id] = terminal.id.toString();
startStopProblemMatcher.processLine(decoder.end());
startStopProblemMatcher.done();
startStopProblemMatcher.dispose();
});
const onExit = terminal.onExit((exitCode) => {
onData.dispose();
onExit.dispose();
delete this.activeTasks[task.id];
if (this.primaryTerminal && this.primaryTerminal.terminal === terminal) {
this.primaryTerminal.busy = false;
}
this.idleTaskTerminals[task.id] = terminal.id.toString();
watchingProblemMatcher.dispose();
toUnbind = dispose(toUnbind);
toUnbind = null;
for (let i = 0; i < eventCounter; i++) {
this.emit(TaskSystemEvents.Inactive, event);
resolve({ exitCode });
}
eventCounter = 0;
if (exitCode && exitCode === 1 && watchingProblemMatcher.numberOfMatches === 0 && task.showOutput !== ShowOutput.Never) {
this.terminalService.setActiveInstance(terminal);
this.terminalService.showPanel(false);
}
resolve({ exitCode });
});
});
} else {
promise = new TPromise<ITaskSummary>((resolve, reject) => {
[terminal, executedCommand] = this.createTerminal(task);
this.emit(TaskSystemEvents.Active, event);
let decoder = new TerminalDecoder();
let startStopProblemMatcher = new StartStopProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService);
const onData = terminal.onData((data: string) => {
decoder.write(data).forEach((line) => {
startStopProblemMatcher.processLine(line);
});
});
const onExit = terminal.onExit((exitCode) => {
onData.dispose();
onExit.dispose();
delete this.activeTasks[task.id];
if (this.primaryTerminal && this.primaryTerminal.terminal === terminal) {
this.primaryTerminal.busy = false;
}
this.idleTaskTerminals[task.id] = terminal.id.toString();
startStopProblemMatcher.processLine(decoder.end());
startStopProblemMatcher.done();
startStopProblemMatcher.dispose();
this.emit(TaskSystemEvents.Inactive, event);
resolve({ exitCode });
});
});
}
this.terminalService.setActiveInstance(terminal);
if (task.showOutput === ShowOutput.Always) {
this.terminalService.showPanel(false);
}
this.activeTasks[task.id] = { terminal, promise };
return promise.then((summary) => {
try {
let telemetryEvent: TelemetryEvent = {
trigger: trigger,
command: this.getSanitizedCommand(executedCommand),
success: true,
exitCode: summary.exitCode
};
this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent);
} catch (error) {
}
this.terminalService.setActiveInstance(terminal);
if (task.showOutput === ShowOutput.Always) {
this.terminalService.showPanel(false);
return summary;
}, (error) => {
try {
let telemetryEvent: TelemetryEvent = {
trigger: trigger,
command: this.getSanitizedCommand(executedCommand),
success: false
};
this.telemetryService.publicLog(TerminalTaskSystem.TelemetryEventName, telemetryEvent);
} catch (error) {
}
this.activeTasks[task.id] = { terminal, promise };
return { kind: TaskExecuteKind.Started, started: {}, promise: promise };
}
return TPromise.wrapError<ITaskSummary>(error);
});
}
private createTerminal(task: TaskDescription): ITerminalInstance {
private createTerminal(task: TaskDescription): [ITerminalInstance, string] {
let options = this.resolveOptions(task.command.options);
let { command, args } = this.resolveCommandAndArgs(task);
let terminalName = nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', task.name);
......@@ -395,13 +448,13 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
if (taskTerminal) {
delete this.idleTaskTerminals[task.id];
taskTerminal.terminal.reuseTerminal(shellLaunchConfig);
return taskTerminal.terminal;
return [taskTerminal.terminal, command];
}
}
if (this.primaryTerminal && !this.primaryTerminal.busy) {
this.primaryTerminal.terminal.reuseTerminal(shellLaunchConfig);
this.primaryTerminal.busy = true;
return this.primaryTerminal.terminal;
return [this.primaryTerminal.terminal, command];
}
const result = this.terminalService.createInstance(shellLaunchConfig);
const key = result.id.toString();
......@@ -419,7 +472,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem {
if (!task.isBackground && !this.primaryTerminal) {
this.primaryTerminal = { terminal: result, busy: true };
}
return result;
return [result, command];
}
private resolveCommandAndArgs(task: TaskDescription): { command: string, args: string[] } {
......
......@@ -320,6 +320,7 @@ export class ProcessRunnerDetector {
tasks.forEach((task) => {
taskConfigs.push({
taskName: task,
identifier: task,
args: [],
isWatching: false
});
......@@ -338,6 +339,7 @@ export class ProcessRunnerDetector {
this._stdout.push(nls.localize('TaskSystemDetector.buildTaskDetected', 'Build task named \'{0}\' detected.', name));
taskConfigs.push({
taskName: name,
identifier: name,
args: [],
isBuildCommand: true,
isWatching: false,
......@@ -349,6 +351,7 @@ export class ProcessRunnerDetector {
this._stdout.push(nls.localize('TaskSystemDetector.testTaskDetected', 'Test task named \'{0}\' detected.', name));
taskConfigs.push({
taskName: name,
identifier: name,
args: [],
isTestCommand: true
});
......
......@@ -108,6 +108,7 @@ class TaskBuilder {
this.commandBuilder = new CommandConfigurationBuilder(this, command);
this.result = {
id: name,
identifier: name,
name: name,
command: this.commandBuilder.result,
showOutput: TaskSystem.ShowOutput.Always,
......@@ -118,6 +119,11 @@ class TaskBuilder {
};
}
public identifier(value: string): TaskBuilder {
this.result.identifier = value;
return this;
}
public args(value: string[]): TaskBuilder {
this.result.args = value;
return this;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册