Fixes #50799: Support querying tasks by task definition

This commit is contained in:
Dirk Baeumer 2018-06-06 18:31:01 +02:00
parent 87e824b4c1
commit c1a6fc3522
8 changed files with 140 additions and 64 deletions

View file

@ -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;

View file

@ -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 {

View file

@ -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;
}

View file

@ -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
]
}
}
]

View file

@ -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<Task> {
public getTask(folder: IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): TPromise<Task> {
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<string, Task>;
identifier: Map<string, Task>;
taskIdentifier: Map<string, Task>;
}
let resolverData: Map<string, ResolverData> = new Map();
grouped.forEach((tasks, folder) => {
let data = resolverData.get(folder);
if (!data) {
data = { label: new Map<string, Task>(), identifier: new Map<string, Task>() };
data = { label: new Map<string, Task>(), identifier: new Map<string, Task>(), taskIdentifier: new Map<string, Task>() };
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;

View file

@ -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();
}
});

View file

@ -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<Tasks.ConfigurationProperties, any>[] = [
@ -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 = {

View file

@ -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<string> = 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 };