diff --git a/src/vs/workbench/api/electron-browser/mainThreadTask.ts b/src/vs/workbench/api/electron-browser/mainThreadTask.ts index 03e2883a944..ab2c0f3e367 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTask.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTask.ts @@ -15,7 +15,7 @@ import * as Platform from 'vs/base/common/platform'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { - ContributedTask, ExtensionTaskSourceTransfer, TaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind, + ContributedTask, ExtensionTaskSourceTransfer, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind, PresentationOptions, CommandOptions, CommandConfiguration, RuntimeType, CustomTask, TaskScope, TaskSource, TaskSourceKind, ExtensionTaskSource, RevealKind, PanelKind } from 'vs/workbench/parts/tasks/common/tasks'; @@ -29,7 +29,6 @@ import { TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO, ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks'; -import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; namespace TaskExecutionDTO { export function from(value: TaskExecution): TaskExecutionDTO { @@ -65,17 +64,13 @@ namespace TaskProcessEndedDTO { } namespace TaskDefinitionDTO { - export function from(value: TaskIdentifier): TaskDefinitionDTO { + export function from(value: KeyedTaskIdentifier): TaskDefinitionDTO { let result = Objects.assign(Object.create(null), value); delete result._key; return result; } - export function to(value: TaskDefinitionDTO): TaskIdentifier { - let taskDefinition = TaskDefinitionRegistry.get(value.type); - if (taskDefinition === void 0) { - return undefined; - } - return TaskDefinition.createTaskIdentifier(taskDefinition, value, console); + export function to(value: TaskDefinitionDTO): KeyedTaskIdentifier { + return TaskDefinition.createTaskIdentifier(value, console); } } @@ -399,8 +394,7 @@ export class MainThreadTask implements MainThreadTaskShape { if (taskTransfer.__workspaceFolder !== void 0 && taskTransfer.__definition !== void 0) { (task._source as any).workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(taskTransfer.__workspaceFolder)); delete taskTransfer.__workspaceFolder; - let taskDefinition = TaskDefinitionRegistry.get(taskTransfer.__definition.type); - let taskIdentifier = TaskDefinition.createTaskIdentifier(taskDefinition, taskTransfer.__definition, console); + let taskIdentifier = TaskDefinition.createTaskIdentifier(taskTransfer.__definition, console); delete taskTransfer.__definition; if (taskIdentifier !== void 0) { (task as ContributedTask).defines = taskIdentifier; diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index 7a055bf14a9..38407b78949 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -12,7 +12,7 @@ import { Platform } from 'vs/base/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { Task, TaskEvent } from './tasks'; +import { Task, TaskEvent, KeyedTaskIdentifier } from './tasks'; export enum TaskErrors { NotConfigured, @@ -95,7 +95,7 @@ export interface ITaskExecuteResult { } export interface ITaskResolver { - resolve(workspaceFolder: IWorkspaceFolder, identifier: string): Task; + resolve(workspaceFolder: IWorkspaceFolder, identifier: string | KeyedTaskIdentifier): Task; } export interface TaskTerminateResponse extends TerminateResponse { diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index 4933f5b5609..c41e4ca76dc 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -312,7 +312,7 @@ export interface WorkspaceTaskSource { readonly kind: 'workspace'; readonly label: string; readonly config: TaskSourceConfigElement; - readonly customizes?: TaskIdentifier; + readonly customizes?: KeyedTaskIdentifier; } export interface ExtensionTaskSource { @@ -336,14 +336,17 @@ export interface InMemoryTaskSource { export type TaskSource = WorkspaceTaskSource | ExtensionTaskSource | InMemoryTaskSource; export interface TaskIdentifier { - _key: string; type: string; [name: string]: any; } +export interface KeyedTaskIdentifier extends TaskIdentifier { + _key: string; +} + export interface TaskDependency { workspaceFolder: IWorkspaceFolder; - task: string; + task: string | KeyedTaskIdentifier; } export enum GroupType { @@ -445,21 +448,21 @@ export namespace CustomTask { let candidate: CustomTask = value; return candidate && candidate.type === 'custom'; } - export function getDefinition(task: CustomTask): TaskIdentifier { + export function getDefinition(task: CustomTask): KeyedTaskIdentifier { let type: string; if (task.command !== void 0) { type = task.command.runtime === RuntimeType.Shell ? 'shell' : 'process'; } else { type = '$composite'; } - let result: TaskIdentifier = { + let result: KeyedTaskIdentifier = { type, _key: task._id, id: task._id }; return result; } - export function customizes(task: CustomTask): TaskIdentifier { + export function customizes(task: CustomTask): KeyedTaskIdentifier { if (task._source && task._source.customizes) { return task._source.customizes; } @@ -474,7 +477,7 @@ export interface ConfiguringTask extends CommonTask, ConfigurationProperties { */ _source: WorkspaceTaskSource; - configures: TaskIdentifier; + configures: KeyedTaskIdentifier; } export namespace ConfiguringTask { @@ -491,7 +494,7 @@ export interface ContributedTask extends CommonTask, ConfigurationProperties { */ _source: ExtensionTaskSource; - defines: TaskIdentifier; + defines: KeyedTaskIdentifier; hasDefinedMatchers: boolean; @@ -606,7 +609,7 @@ export namespace Task { } } - export function matches(task: Task, key: string | TaskIdentifier, compareId: boolean = false): boolean { + export function matches(task: Task, key: string | KeyedTaskIdentifier, compareId: boolean = false): boolean { if (key === void 0) { return false; } @@ -626,11 +629,15 @@ export namespace Task { } } - export function getTaskDefinition(task: Task): TaskIdentifier { + export function getTaskDefinition(task: Task, useSource: boolean = false): KeyedTaskIdentifier { if (ContributedTask.is(task)) { return task.defines; } else if (CustomTask.is(task)) { - return CustomTask.getDefinition(task); + if (useSource && task._source.customizes !== void 0) { + return task._source.customizes; + } else { + return CustomTask.getDefinition(task); + } } else { return undefined; } diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts index 4f3218fda37..7ad25b8f045 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts @@ -43,18 +43,34 @@ const shellCommand: IJSONSchema = { deprecationMessage: nls.localize('JsonSchema.tasks.isShellCommand.deprecated', 'The property isShellCommand is deprecated. Use the type property of the task and the shell property in the options instead. See also the 1.14 release notes.') }; +const taskIdentifier: IJSONSchema = { + type: 'object', + additionalProperties: true, + properties: { + type: { + type: 'string', + description: nls.localize('JsonSchema.tasks.dependsOn.identifier', 'The task indentifier.') + } + } +}; + const dependsOn: IJSONSchema = { anyOf: [ { type: 'string', - default: true, description: nls.localize('JsonSchema.tasks.dependsOn.string', 'Another task this task depends on.') }, + taskIdentifier, { type: 'array', description: nls.localize('JsonSchema.tasks.dependsOn.array', 'The other tasks this task depends on.'), items: { - type: 'string' + anyOf: [ + { + type: 'string', + }, + taskIdentifier + ] } } ] 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 e1dfab6f052..ceea8827331 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -75,13 +75,14 @@ import { ITaskSystem, ITaskResolver, ITaskSummary, TaskExecuteKind, TaskError, T import { Task, CustomTask, ConfiguringTask, ContributedTask, InMemoryTask, TaskEvent, TaskEventKind, TaskSet, TaskGroup, GroupType, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, - TaskSorter + TaskSorter, TaskIdentifier, KeyedTaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskService, ITaskProvider, RunOptions, CustomizationProperties, TaskFilter } from 'vs/workbench/parts/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; +import { KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/parts/tasks/node/tasks'; + import * as TaskConfig from '../node/taskConfiguration'; -import { TaskIdentifier } from 'vs/workbench/parts/tasks/node/tasks'; import { ProcessTaskSystem } from 'vs/workbench/parts/tasks/node/processTaskSystem'; import { TerminalTaskSystem } from './terminalTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunnerDetector'; @@ -675,18 +676,27 @@ class TaskService implements ITaskService { this._taskSystemInfos.set(key, info); } - public getTask(folder: IWorkspaceFolder | string, alias: string, compareId: boolean = false): TPromise { + public getTask(folder: IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): TPromise { let name = Types.isString(folder) ? folder : folder.name; if (this.ignoredWorkspaceFolders.some(ignored => ignored.name === name)) { return TPromise.wrapError(new Error(nls.localize('TaskServer.folderIgnored', 'The folder {0} is ignored since it uses task version 0.1.0', name))); } + let key: string | KeyedTaskIdentifier; + if (!Types.isString(identifier)) { + key = TaskDefinition.createTaskIdentifier(identifier, console); + } else { + key = identifier; + } + if (key === void 0) { + return TPromise.as(undefined); + } return this.getGroupedTasks().then((map) => { let values = map.get(folder); if (!values) { return undefined; } for (let task of values) { - if (Task.matches(task, alias, compareId)) { + if (Task.matches(task, key, compareId)) { return task; } } @@ -1156,27 +1166,37 @@ class TaskService implements ITaskService { interface ResolverData { label: Map; identifier: Map; + taskIdentifier: Map; } let resolverData: Map = new Map(); grouped.forEach((tasks, folder) => { let data = resolverData.get(folder); if (!data) { - data = { label: new Map(), identifier: new Map() }; + data = { label: new Map(), identifier: new Map(), taskIdentifier: new Map() }; resolverData.set(folder, data); } for (let task of tasks) { data.label.set(task._label, task); data.identifier.set(task.identifier, task); + let keyedIdentifier = Task.getTaskDefinition(task, true); + if (keyedIdentifier !== void 0) { + data.taskIdentifier.set(keyedIdentifier._key, task); + } } }); return { - resolve: (workspaceFolder: IWorkspaceFolder, alias: string) => { + resolve: (workspaceFolder: IWorkspaceFolder, identifier: string | TaskIdentifier) => { let data = resolverData.get(workspaceFolder.uri.toString()); if (!data) { return undefined; } - return data.label.get(alias) || data.identifier.get(alias); + if (Types.isString(identifier)) { + return data.label.get(identifier) || data.identifier.get(identifier); + } else { + let key = TaskDefinition.createTaskIdentifier(identifier, console); + return key !== void 0 ? data.taskIdentifier.get(key._key) : undefined; + } } }; } @@ -1429,7 +1449,7 @@ class TaskService implements ITaskService { // This is for backwards compatibility with the 0.1.0 task annotation code // if we had a gulp, jake or grunt command a task specification was a annotation if (commandName === 'gulp' || commandName === 'grunt' || commandName === 'jake') { - let identifier = TaskIdentifier.create({ + let identifier = NKeyedTaskIdentifier.create({ type: commandName, task: task.name }); @@ -1900,12 +1920,18 @@ class TaskService implements ITaskService { if (!this.canRunCommand()) { return; } + let identifier: string | KeyedTaskIdentifier = undefined; if (Types.isString(arg)) { + identifier = arg; + } else if (arg && Types.isString((arg as TaskIdentifier).type)) { + identifier = TaskDefinition.createTaskIdentifier(arg as TaskIdentifier, console); + } + if (identifier !== void 0) { this.getGroupedTasks().then((grouped) => { let resolver = this.createResolver(grouped); let folders = this.contextService.getWorkspace().folders; for (let folder of folders) { - let task = resolver.resolve(folder, arg); + let task = resolver.resolve(folder, identifier); if (task) { this.run(task); return; diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 3d7718062da..72d858d7b51 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -266,7 +266,11 @@ export class TerminalTaskSystem implements ITaskSystem { } promises.push(promise); } else { - this.log(nls.localize('dependencyFailed', 'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'', dependency.task, dependency.workspaceFolder.name)); + this.log(nls.localize('dependencyFailed', + 'Couldn\'t resolve dependent task \'{0}\' in workspace folder \'{1}\'', + Types.isString(dependency.task) ? dependency.task : JSON.stringify(dependency.task, undefined, 0), + dependency.workspaceFolder.name + )); this.showOutput(); } }); diff --git a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts index 20508f6362f..4bbc30ff45b 100644 --- a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts @@ -112,6 +112,14 @@ export interface PresentationOptions { export interface TaskIdentifier { type?: string; + [name: string]: any; +} + +export namespace TaskIdentifier { + export function is(value: any): value is TaskIdentifier { + let candidate: TaskIdentifier = value; + return candidate !== void 0 && Types.isString(value.type); + } } export interface LegacyTaskProperties { @@ -279,7 +287,7 @@ export interface ConfigurationProperties { /** * The other tasks the task depend on */ - dependsOn?: string | string[]; + dependsOn?: string | TaskIdentifier | (string | TaskIdentifier)[]; /** * Controls the behavior of the used terminal @@ -1103,6 +1111,18 @@ namespace GroupKind { } } +namespace TaskDependency { + export function from(this: void, external: string | TaskIdentifier, context: ParseContext): Tasks.TaskDependency { + if (Types.isString(external)) { + return { workspaceFolder: context.workspaceFolder, task: external }; + } else if (TaskIdentifier.is(external)) { + return { workspaceFolder: context.workspaceFolder, task: TaskDefinition.createTaskIdentifier(external as Tasks.TaskIdentifier, context.problemReporter) }; + } else { + return undefined; + } + } +} + namespace ConfigurationProperties { const properties: MetaData[] = [ @@ -1145,10 +1165,10 @@ namespace ConfigurationProperties { } } if (external.dependsOn !== void 0) { - if (Types.isString(external.dependsOn)) { - result.dependsOn = [{ workspaceFolder: context.workspaceFolder, task: external.dependsOn }]; - } else if (Types.isStringArray(external.dependsOn)) { - result.dependsOn = external.dependsOn.map((task) => { return { workspaceFolder: context.workspaceFolder, task: task }; }); + if (Types.isArray(external.dependsOn)) { + result.dependsOn = external.dependsOn.map(item => TaskDependency.from(item, context)); + } else { + result.dependsOn = [TaskDependency.from(external, context)]; } } if (includeCommandOptions && (external.presentation !== void 0 || (external as LegacyCommandProperties).terminal !== void 0)) { @@ -1196,27 +1216,37 @@ namespace ConfiguringTask { context.problemReporter.error(message); return undefined; } - let taskIdentifier: Tasks.TaskIdentifier; + let identifier: Tasks.TaskIdentifier; if (Types.isString(customize)) { - let identifier: TaskIdentifier; if (customize.indexOf(grunt) === 0) { - identifier = { type: 'grunt', task: customize.substring(grunt.length) } as TaskIdentifier; + identifier = { type: 'grunt', task: customize.substring(grunt.length) }; } else if (customize.indexOf(jake) === 0) { - identifier = { type: 'jake', task: customize.substring(jake.length) } as TaskIdentifier; + identifier = { type: 'jake', task: customize.substring(jake.length) }; } else if (customize.indexOf(gulp) === 0) { - identifier = { type: 'gulp', task: customize.substring(gulp.length) } as TaskIdentifier; + identifier = { type: 'gulp', task: customize.substring(gulp.length) }; } else if (customize.indexOf(npm) === 0) { - identifier = { type: 'npm', script: customize.substring(npm.length + 4) } as TaskIdentifier; + identifier = { type: 'npm', script: customize.substring(npm.length + 4) }; } else if (customize.indexOf(typescript) === 0) { - identifier = { type: 'typescript', tsconfig: customize.substring(typescript.length + 6) } as TaskIdentifier; - } - if (identifier !== void 0) { - taskIdentifier = TaskDefinition.createTaskIdentifier(typeDeclaration, identifier, context.problemReporter); + identifier = { type: 'typescript', tsconfig: customize.substring(typescript.length + 6) }; } } else { - taskIdentifier = TaskDefinition.createTaskIdentifier(typeDeclaration, external, context.problemReporter); + if (Types.isString(external.type)) { + identifier = external as Tasks.TaskIdentifier; + } } + if (identifier === void 0) { + context.problemReporter.error(nls.localize( + 'ConfigurationParser.missingType', + 'Error: the task configuration \'{0}\' is missing the required property \'type\'. The task configuration will be ignored.', JSON.stringify(external, undefined, 0) + )); + return undefined; + } + let taskIdentifier: Tasks.KeyedTaskIdentifier = TaskDefinition.createTaskIdentifier(identifier, context.problemReporter); if (taskIdentifier === void 0) { + context.problemReporter.error(nls.localize( + 'ConfigurationParser.incorrectType', + 'Error: the task configuration \'{0}\' is using and unknown type. The task configuration will be ignored.', JSON.stringify(external, undefined, 0) + )); return undefined; } let configElement: Tasks.TaskSourceConfigElement = { diff --git a/src/vs/workbench/parts/tasks/node/tasks.ts b/src/vs/workbench/parts/tasks/node/tasks.ts index be3e242bb53..c0cd15ad06d 100644 --- a/src/vs/workbench/parts/tasks/node/tasks.ts +++ b/src/vs/workbench/parts/tasks/node/tasks.ts @@ -10,10 +10,11 @@ import * as crypto from 'crypto'; import * as Objects from 'vs/base/common/objects'; -import { TaskIdentifier, TaskDefinition } from 'vs/workbench/parts/tasks/common/tasks'; +import { TaskIdentifier, KeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/parts/tasks/common/tasks'; +import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; -namespace TaskIdentifier { - export function create(value: { type: string;[name: string]: any }): TaskIdentifier { +namespace KeyedTaskIdentifier { + export function create(value: TaskIdentifier): KeyedTaskIdentifier { const hash = crypto.createHash('md5'); hash.update(JSON.stringify(value)); let result = { _key: hash.digest('hex'), type: value.taskType }; @@ -23,14 +24,12 @@ namespace TaskIdentifier { } namespace TaskDefinition { - export function createTaskIdentifier(definition: TaskDefinition, external: { type?: string;[name: string]: any }, reporter: { error(message: string): void; }): TaskIdentifier | undefined { - if (definition.taskType !== external.type) { - reporter.error(nls.localize( - 'TaskDefinition.missingType', - 'Error: the task configuration \'{0}\' is missing the required property \'type\'. The task configuration will be ignored.', JSON.stringify(external, undefined, 0) - )); + export function createTaskIdentifier(external: TaskIdentifier, reporter: { error(message: string): void; }): KeyedTaskIdentifier | undefined { + let definition = TaskDefinitionRegistry.get(external.type); + if (definition === void 0) { return undefined; } + let literal: { type: string;[name: string]: any } = Object.create(null); literal.type = definition.taskType; let required: Set = new Set(); @@ -60,15 +59,15 @@ namespace TaskDefinition { default: reporter.error(nls.localize( 'TaskDefinition.missingRequiredProperty', - 'Error: the task configuration \'{0}\' is missing the required property \'{1}\'. The task configuration will be ignored.', JSON.stringify(external, undefined, 0), property + 'Error: the task identifier \'{0}\' is missing the required property \'{1}\'. The task identifier will be ignored.', JSON.stringify(external, undefined, 0), property )); return undefined; } } } } - return TaskIdentifier.create(literal); + return KeyedTaskIdentifier.create(literal); } } -export { TaskIdentifier, TaskDefinition }; +export { KeyedTaskIdentifier, TaskDefinition };