diff --git a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts index 67a3fe2164d6643a267d189e37a1b6c5275dea14..7f03f1553efbcb5d559099792c51a5130aa4eb88 100644 --- a/src/vs/workbench/parts/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/common/taskConfiguration.ts @@ -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, diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index a77a951416436adc559b6e9d73adc91b7cac3e40..97c90306d00e683bb91855b7c1a27fdd0747e322 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -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 diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchemaCommon.ts b/src/vs/workbench/parts/tasks/electron-browser/jsonSchemaCommon.ts new file mode 100644 index 0000000000000000000000000000000000000000..9be9437a3bfabb2e3d34c6c718cc537a85083a6d --- /dev/null +++ b/src/vs/workbench/parts/tasks/electron-browser/jsonSchemaCommon.ts @@ -0,0 +1,417 @@ +/*--------------------------------------------------------------------------------------------- + * 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 diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts new file mode 100644 index 0000000000000000000000000000000000000000..24441e1eba0825ea5c89841dbca5a85eea3955ac --- /dev/null +++ b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * 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 diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts new file mode 100644 index 0000000000000000000000000000000000000000..441507853aabb5afd532f3ff0352980c81251b7a --- /dev/null +++ b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts @@ -0,0 +1,108 @@ +/*--------------------------------------------------------------------------------------------- + * 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 diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 14eaebec35bce2f512f0184186b4f5aaeedc82a0..d082de9d22d7ea4952f71435ab32b25c85e47008 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -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 = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); jsonRegistry.registerSchema(schemaId, schema); diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 4288f2d4defbf50b2ee2d3177799e16f54cb0032..25ac8e58b02dddf23ce6dd8b746a730fcad37523 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -99,17 +99,20 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { public static TelemetryEventName: string = 'taskService'; + private configuration: TaskRunnerConfiguration; + private identifier2Task: IStringDictionary; private outputChannel: IOutputChannel; private activeTasks: IStringDictionary; private primaryTerminal: PrimaryTerminal; private terminals: IStringDictionary; private idleTaskTerminals: IStringDictionary; - 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(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>, task: TaskDescription, trigger: string): TPromise { + let promises: TPromise[] = []; + 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 = undefined; - if (task.isBackground) { - promise = new TPromise((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 = 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((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 { + let terminal: ITerminalInstance = undefined; + let executedCommand: string = undefined; + let promise: TPromise = undefined; + if (task.isBackground) { + promise = new TPromise((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 = 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((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(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[] } { diff --git a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts index 255df6ee251ad58042a8209b56c7711c792db146..5560fa0e643a2c45b5837e2404472647ce217f3f 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts @@ -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 }); diff --git a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts index 1aed4095f106150807c84317e3f1262aac2d92cf..c74dd2576e06465d441cd869a645bbf5969309de 100644 --- a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts @@ -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;