From 48a286f962e3f417e377dbb9e61c05a3ac4c98c7 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Tue, 12 Sep 2017 21:56:36 +0200 Subject: [PATCH] Making tasks.json parser workspace folder aware --- src/vs/workbench/parts/tasks/common/tasks.ts | 6 + .../electron-browser/task.contribution.ts | 160 +++++++++++++++++- .../parts/tasks/node/taskConfiguration.ts | 47 ++--- .../electron-browser/configuration.test.ts | 6 +- 4 files changed, 174 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index fd9b28ce2d5..fab0ff45015 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import URI from 'vs/base/common/uri'; import * as Types from 'vs/base/common/types'; import { IJSONSchemaMap } from 'vs/base/common/jsonSchema'; @@ -221,7 +222,12 @@ export namespace TaskSourceKind { export const Composite: 'composite' = 'composite'; } +export interface WorkspaceFolder { + uri: URI; +} + export interface TaskSourceConfigElement { + workspaceFolder: WorkspaceFolder; file: string; index: number; element: any; 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 5d30f4a319b..35223caa5fc 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -76,7 +76,7 @@ import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; import { ITaskSystem, ITaskResolver, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskSystemEvents, TaskTerminateResponse } from 'vs/workbench/parts/tasks/common/taskSystem'; -import { Task, CustomTask, ConfiguringTask, ContributedTask, CompositeTask, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks'; +import { Task, CustomTask, ConfiguringTask, ContributedTask, CompositeTask, TaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskIdentifier, WorkspaceFolder } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskService, TaskServiceEvents, ITaskProvider, TaskEvent, RunOptions, CustomizationProperties } from 'vs/workbench/parts/tasks/common/taskService'; import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; @@ -618,7 +618,12 @@ interface WorkspaceTaskResult { hasErrors: boolean; } -interface WorkspaceConfigurationResult { +interface WorkspaceFolderTaskResult extends WorkspaceTaskResult { + workspaceFolder: WorkspaceFolder; +} + +interface WorkspaceFolderConfigurationResult { + workspaceFolder: WorkspaceFolder; config: TaskConfig.ExternalTaskRunnerConfiguration; hasErrors: boolean; } @@ -657,6 +662,9 @@ class TaskService extends EventEmitter implements ITaskService { private quickOpenService: IQuickOpenService; private _configHasErrors: boolean; + private _schemaVersion: JsonSchemaVersion; + private _executionEngine: ExecutionEngine; + private _workspaceFolders: WorkspaceFolder[]; private _providers: Map; private _workspaceTasksPromise: TPromise; @@ -736,6 +744,7 @@ class TaskService extends EventEmitter implements ITaskService { ); } }); + this.updateWorkspaceFolders(); lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown())); this.registerCommands(); } @@ -1449,8 +1458,9 @@ class TaskService extends EventEmitter implements ITaskService { }); } - private computeWorkspaceTasks(): TPromise { - let configPromise: TPromise; + /* + private computeWorkspaceTasks2(): TPromise { + let configPromise: TPromise; { let { config, hasParseErrors } = this.getConfiguration(); if (hasParseErrors) { @@ -1461,7 +1471,7 @@ class TaskService extends EventEmitter implements ITaskService { engine = TaskConfig.ExecutionEngine.from(config); if (engine === ExecutionEngine.Process) { if (this.hasDetectorSupport(config)) { - configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, config).detect(true).then((value): WorkspaceConfigurationResult => { + configPromise = new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, config).detect(true).then((value): WorkspaceFolderConfigurationResult => { let hasErrors = this.printStderr(value.stderr); let detectedConfig = value.config; if (!detectedConfig) { @@ -1529,17 +1539,145 @@ class TaskService extends EventEmitter implements ITaskService { }); }); } + */ + + private computeWorkspaceTasks(): TPromise> { + if (this._workspaceFolders.length === 0) { + return TPromise.as(new Map()); + } else { + let promises: TPromise[] = []; + for (let folder of this._workspaceFolders) { + promises.push(this.computeWorkspaceFolderTasks(folder).then((value) => value, () => undefined)); + } + return TPromise.join(promises).then((values) => { + let result = new Map(); + for (let value of values) { + if (value) { + result.set(value.workspaceFolder.uri.toString(), value); + } + } + return result; + }); + } + } + + private computeWorkspaceFolderTasks(workspaceFolder: WorkspaceFolder): TPromise { + return (this._executionEngine === ExecutionEngine.Process + ? this.computeLegacyConfiguration(workspaceFolder) + : this.computeConfiguration(workspaceFolder)). + then((workspaceFolderConfiguration) => { + if (!workspaceFolderConfiguration || !workspaceFolderConfiguration.config || workspaceFolderConfiguration.hasErrors) { + return TPromise.as({ workspaceFolder, set: undefined, configurations: undefined, hasErrors: workspaceFolderConfiguration ? workspaceFolderConfiguration.hasErrors : false }); + } + return ProblemMatcherRegistry.onReady().then((): WorkspaceFolderTaskResult => { + let problemReporter = new ProblemReporter(this._outputChannel); + let parseResult = TaskConfig.parse(workspaceFolder, workspaceFolderConfiguration.config, problemReporter); + let hasErrors = false; + if (!parseResult.validationStatus.isOK()) { + hasErrors = true; + this.showOutput(); + } + if (problemReporter.status.isFatal()) { + problemReporter.fatal(nls.localize('TaskSystem.configurationErrors', 'Error: the provided task configuration has validation errors and can\'t not be used. Please correct the errors first.')); + return { workspaceFolder, set: undefined, configurations: undefined, hasErrors }; + } + let customizedTasks: { byIdentifier: IStringDictionary; }; + if (parseResult.configured && parseResult.configured.length > 0) { + customizedTasks = { + byIdentifier: Object.create(null) + }; + for (let task of parseResult.configured) { + customizedTasks.byIdentifier[task.configures._key] = task; + } + } + return { workspaceFolder, set: { tasks: parseResult.custom }, configurations: customizedTasks, hasErrors }; + }); + }); + } + + private computeConfiguration(workspaceFolder: WorkspaceFolder): TPromise { + let { config, hasParseErrors } = this.getConfiguration(workspaceFolder); + return TPromise.as({ workspaceFolder, config, hasErrors: hasParseErrors }); + } + + private computeLegacyConfiguration(workspaceFolder: WorkspaceFolder): TPromise { + let { config, hasParseErrors } = this.getConfiguration(workspaceFolder); + if (hasParseErrors) { + return TPromise.as({ workspaceFolder: workspaceFolder, hasErrors: true, config: undefined }); + } + if (config) { + if (this.hasDetectorSupport(config)) { + return new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService, config).detect(true).then((value): WorkspaceFolderConfigurationResult => { + let hasErrors = this.printStderr(value.stderr); + let detectedConfig = value.config; + if (!detectedConfig) { + return { workspaceFolder, config, hasErrors }; + } + let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.clone(config); + let configuredTasks: IStringDictionary = Object.create(null); + if (!result.tasks) { + if (detectedConfig.tasks) { + result.tasks = detectedConfig.tasks; + } + } else { + result.tasks.forEach(task => configuredTasks[task.taskName] = task); + detectedConfig.tasks.forEach((task) => { + if (!configuredTasks[task.taskName]) { + result.tasks.push(task); + } + }); + } + return { workspaceFolder, config: result, hasErrors }; + }); + } else { + return TPromise.as({ workspaceFolder, config, hasErrors: false }); + } + } else { + return new ProcessRunnerDetector(this.fileService, this.contextService, this.configurationResolverService).detect(true).then((value) => { + let hasErrors = this.printStderr(value.stderr); + return { workspaceFolder, config: value.config, hasErrors }; + }); + } + } + + private updateWorkspaceFolders(): void { + if (this.contextService.hasFolderWorkspace()) { + let workspaceFolder = { uri: this.contextService.getWorkspace().roots[0] }; + this._workspaceFolders = [workspaceFolder]; + this._executionEngine = this.computeExecutionEngine(workspaceFolder); + this._schemaVersion = this.computeJsonSchemaVersion(workspaceFolder); + } else if (this.contextService.hasMultiFolderWorkspace()) { + this._executionEngine = ExecutionEngine.Terminal; + this._schemaVersion = JsonSchemaVersion.V2_0_0; + this._workspaceFolders = []; + for (let folder of this.contextService.getWorkspace().roots) { + let workspaceFolder = { uri: folder }; + if (this._schemaVersion === this.computeJsonSchemaVersion(workspaceFolder)) { + this._workspaceFolders.push(workspaceFolder); + } else { + this._outputChannel.append(nls.localize( + 'taskService.ignoreingFolder', + 'Ignoring task configurations for workspace folder {0}. Multi root folder support requires that all folders use task version 2.0.', + folder.fsPath)); + } + } + } else { + this._workspaceFolders = []; + this._executionEngine = ExecutionEngine.Terminal; + this._schemaVersion = JsonSchemaVersion.V2_0_0; + } + } - private getExecutionEngine(): ExecutionEngine { - let { config } = this.getConfiguration(); + private computeExecutionEngine(workspaceFolder: WorkspaceFolder): ExecutionEngine { + let { config } = this.getConfiguration(workspaceFolder); if (!config) { return ExecutionEngine._default; } return TaskConfig.ExecutionEngine.from(config); } - private getJsonSchemaVersion(): JsonSchemaVersion { - let { config } = this.getConfiguration(); + private computeJsonSchemaVersion(workspaceFolder: WorkspaceFolder): JsonSchemaVersion { + let { config } = this.getConfiguration(workspaceFolder); if (!config) { return JsonSchemaVersion.V2_0_0; } @@ -1548,6 +1686,10 @@ class TaskService extends EventEmitter implements ITaskService { private getConfiguration(): { config: TaskConfig.ExternalTaskRunnerConfiguration; hasParseErrors: boolean } { let result = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.configurationService.getConfiguration('tasks', { resource: this.contextService.getWorkspace().folders[0] }) : undefined; + private getConfiguration(workspaceFolder: WorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration; hasParseErrors: boolean } { + let result = this.contextService.hasWorkspace() + ? this.configurationService.getConfiguration('tasks', { resource: workspaceFolder.uri }) + : undefined; if (!result) { return { config: undefined, hasParseErrors: false }; } diff --git a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts index 513e890116a..c16b90342fe 100644 --- a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts @@ -561,6 +561,7 @@ function _freeze(this: void, target: T, properties: MetaData[]): Read } interface ParseContext { + workspaceFolder: Tasks.WorkspaceFolder; problemReporter: IProblemReporter; namedProblemMatchers: IStringDictionary; uuidMap: UUIDMap; @@ -1160,6 +1161,7 @@ namespace ConfiguringTask { } let taskIdentifier = TaskIdentifier.from(identifier); let configElement: Tasks.TaskSourceConfigElement = { + workspaceFolder: context.workspaceFolder, file: '.vscode\\tasks.json', index, element: external @@ -1678,10 +1680,12 @@ class UUIDMap { class ConfigurationParser { + private workspaceFolder: Tasks.WorkspaceFolder; private problemReporter: IProblemReporter; private uuidMap: UUIDMap; - constructor(problemReporter: IProblemReporter, uuidMap: UUIDMap) { + constructor(workspaceFolder: Tasks.WorkspaceFolder, problemReporter: IProblemReporter, uuidMap: UUIDMap) { + this.workspaceFolder = workspaceFolder; this.problemReporter = problemReporter; this.uuidMap = uuidMap; } @@ -1693,6 +1697,7 @@ class ConfigurationParser { this.problemReporter.clearOutput(); } let context: ParseContext = { + workspaceFolder: this.workspaceFolder, problemReporter: this.problemReporter, uuidMap: this.uuidMap, namedProblemMatchers: undefined, @@ -1783,11 +1788,16 @@ class ConfigurationParser { } } -let uuidMap: UUIDMap = new UUIDMap(); -export function parse(configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter): ParseResult { +let uuidMaps: Map = new Map(); +export function parse(workspaceFolder: Tasks.WorkspaceFolder, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter): ParseResult { + let uuidMap = uuidMaps.get(workspaceFolder.uri.toString()); + if (!uuidMap) { + uuidMap = new UUIDMap(); + uuidMaps.set(workspaceFolder.uri.toString(), uuidMap); + } try { uuidMap.start(); - return (new ConfigurationParser(logger, uuidMap)).run(configuration); + return (new ConfigurationParser(workspaceFolder, logger, uuidMap)).run(configuration); } finally { uuidMap.finish(); } @@ -1801,35 +1811,6 @@ export function getTaskIdentifier(value: TaskIdentifier): Tasks.TaskIdentifier { return TaskIdentifier.from(value); } -export function findTaskIndex(fileConfig: ExternalTaskRunnerConfiguration, task: Tasks.Task): number { - if (!fileConfig || !fileConfig.tasks) { - return undefined; - } - if (fileConfig.tasks.length === 0) { - return -1; - } - let localMap = new UUIDMap(uuidMap); - let context: ParseContext = { - problemReporter: this.problemReporter, - uuidMap: localMap, - namedProblemMatchers: undefined, - engine: ExecutionEngine.from(fileConfig), - schemaVersion: JsonSchemaVersion.from(fileConfig) - }; - try { - localMap.start(); - let tasks = TaskParser.quickParse(fileConfig.tasks, context); - for (let i = 0; i < tasks.length; i++) { - if (task._id === tasks[i]._id) { - return i; - } - } - return -1; - } finally { - localMap.finish(); - } -} - /* class VersionConverter { constructor(private problemReporter: IProblemReporter) { diff --git a/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts b/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts index c89319b81dd..d045dc5c153 100644 --- a/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts @@ -177,7 +177,7 @@ class CustomTaskBuilder { this.commandBuilder = new CommandConfigurationBuilder(this, command); this.result = { _id: name, - _source: { kind: Tasks.TaskSourceKind.Workspace, label: 'workspace', config: { element: undefined, index: -1, file: '.vscode/tasks.json' } }, + _source: { kind: Tasks.TaskSourceKind.Workspace, label: 'workspace', config: { workspaceFolder: { uri: undefined }, element: undefined, index: -1, file: '.vscode/tasks.json' } }, _label: name, type: 'custom', identifier: name, @@ -347,7 +347,7 @@ class PatternBuilder { function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, resolved: number) { let reporter = new ProblemReporter(); - let result = parse(external, reporter); + let result = parse(external, { uri: undefined }, reporter); assert.ok(!reporter.receivedMessage); assert.strictEqual(result.custom.length, 1); let task = result.custom[0]; @@ -358,7 +358,7 @@ function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, re function testConfiguration(external: ExternalTaskRunnerConfiguration, builder: ConfiguationBuilder): void { builder.done(); let reporter = new ProblemReporter(); - let result = parse(external, reporter); + let result = parse(external, { uri: undefined }, reporter); if (reporter.receivedMessage) { assert.ok(false, reporter.lastMessage); } -- GitLab