diff --git a/extensions/npm/package.json b/extensions/npm/package.json index b4f8c69726ffdcde276e179aa1b4c9c56be64f7f..0e8b70bb2564bcb45f21ea9e2560a9346aeb4845 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -290,7 +290,8 @@ "type": "string", "description": "%taskdef.path%" } - } + }, + "when": "shellExecutionSupported" } ] } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index f52a051103cf409e557eb50b93a4d936c46d93d9..315f7f78bdae182054694dd0ea17a19460f8176b 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -56,7 +56,7 @@ import { TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, TaskRunSource, KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, USER_TASKS_GROUP_KEY, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; import * as TaskConfig from '../common/taskConfiguration'; @@ -248,7 +248,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IHostService private readonly _hostService: IHostService, @IDialogService private readonly dialogService: IDialogService, @INotificationService private readonly notificationService: INotificationService, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService, @IPathService private readonly pathService: IPathService, @@ -331,6 +331,16 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } return task._label; }); + this.setExecutionContexts(); + } + + protected setExecutionContexts(custom: boolean = true, shell: boolean = false, process: boolean = false): void { + const customContext = CustomExecutionSupportedContext.bindTo(this.contextKeyService); + customContext.set(custom); + const shellContext = ShellExecutionSupportedContext.bindTo(this.contextKeyService); + shellContext.set(shell); + const processContext = ProcessExecutionSupportedContext.bindTo(this.contextKeyService); + processContext.set(process); } public get onDidStateChange(): Event { @@ -556,14 +566,27 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer public async tryResolveTask(configuringTask: ConfiguringTask): Promise { await Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]); let matchingProvider: ITaskProvider | undefined; + let matchingProviderUnavailable: boolean = false; for (const [handle, provider] of this._providers) { - if (configuringTask.type === this._providerTypes.get(handle)) { + const providerType = this._providerTypes.get(handle); + if (configuringTask.type === providerType) { + if (providerType && !this.isTaskProviderEnabled(providerType)) { + matchingProviderUnavailable = true; + continue; + } matchingProvider = provider; break; } } if (!matchingProvider) { + if (matchingProviderUnavailable) { + this._outputChannel.append(nls.localize( + 'TaskService.providerUnavailable', + 'Warning: {0} tasks are unavailable in the current environment.\n', + configuringTask.configures.type + )); + } return; } @@ -624,7 +647,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (this.isProvideTasksEnabled()) { for (const [handle] of this._providers) { const type = this._providerTypes.get(handle); - if (type) { + if (type && this.isTaskProviderEnabled(type)) { types.push(type); } } @@ -1559,6 +1582,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; + private isTaskProviderEnabled(type: string) { + const definition = TaskDefinitionRegistry.get(type); + return !definition.when || this.contextKeyService.contextMatchesRules(definition.when); + } + private getGroupedTasks(type?: string): Promise { const needsRecentTasksMigration = this.needsRecentTasksMigration(); return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]).then(() => { @@ -1596,7 +1624,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }; if (this.isProvideTasksEnabled() && (this.schemaVersion === JsonSchemaVersion.V2_0_0) && (this._providers.size > 0)) { for (const [handle, provider] of this._providers) { - if ((type === undefined) || (type === this._providerTypes.get(handle))) { + const providerType = this._providerTypes.get(handle); + if ((type === undefined) || (type === providerType)) { + if (providerType && !this.isTaskProviderEnabled(providerType)) { + continue; + } counter++; provider.provideTasks(validTypes).then((taskSet: TaskSet) => { // Check that the tasks provided are of the correct type @@ -1699,8 +1731,16 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return; } + let requiredTaskProviderUnavailable: boolean = false; + for (const [handle, provider] of this._providers) { - if (configuringTask.type === this._providerTypes.get(handle)) { + const providerType = this._providerTypes.get(handle); + if (configuringTask.type === providerType) { + if (providerType && !this.isTaskProviderEnabled(providerType)) { + requiredTaskProviderUnavailable = true; + continue; + } + try { const resolvedTask = await provider.resolveTask(configuringTask); if (resolvedTask && (resolvedTask._id === configuringTask._id)) { @@ -1713,13 +1753,21 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - this._outputChannel.append(nls.localize( - 'TaskService.noConfiguration', - 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n', - configuringTask.configures.type, - JSON.stringify(configuringTask._source.config.element, undefined, 4) - )); - this.showOutput(); + if (requiredTaskProviderUnavailable) { + this._outputChannel.append(nls.localize( + 'TaskService.providerUnavailable', + 'Warning: {0} tasks are unavailable in the current environment.\n', + configuringTask.configures.type + )); + } else { + this._outputChannel.append(nls.localize( + 'TaskService.noConfiguration', + 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n', + configuringTask.configures.type, + JSON.stringify(configuringTask._source.config.element, undefined, 4) + )); + this.showOutput(); + } }); await Promise.all(unUsedConfigurationPromises); @@ -1831,9 +1879,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return ProblemMatcherRegistry.onReady().then(async (): Promise => { let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme); let problemReporter = new ProblemReporter(this._outputChannel); - let parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson); + let parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson, this.contextKeyService); let hasErrors = false; - if (!parseResult.validationStatus.isOK()) { + if (!parseResult.validationStatus.isOK() && (parseResult.validationStatus.state !== ValidationState.Info)) { hasErrors = true; this.showOutput(runSource); } @@ -1928,9 +1976,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } let taskSystemInfo: TaskSystemInfo | undefined = workspaceFolder ? this._taskSystemInfos.get(workspaceFolder.uri.scheme) : undefined; let problemReporter = new ProblemReporter(this._outputChannel); - let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source, isRecentTask); + let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source, this.contextKeyService, isRecentTask); let hasErrors = false; - if (!parseResult.validationStatus.isOK()) { + if (!parseResult.validationStatus.isOK() && (parseResult.validationStatus.state !== ValidationState.Info)) { this.showOutput(runSource); hasErrors = true; } diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 9d0e8bad9f50e4e7876554cb9702b18879572f60..28d45ddf3069255c15950faa9a54eb6e7e2c2dcc 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -23,8 +23,8 @@ import * as Tasks from './tasks'; import { TaskDefinitionRegistry } from './taskDefinitionRegistry'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { URI } from 'vs/base/common/uri'; -import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; - +import { USER_TASKS_GROUP_KEY, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const enum ShellQuoting { /** @@ -709,6 +709,7 @@ interface ParseContext { schemaVersion: Tasks.JsonSchemaVersion; platform: Platform; taskLoadIssues: string[]; + contextKeyService: IContextKeyService; } @@ -1656,6 +1657,11 @@ namespace TaskParser { return customize === undefined && (type === undefined || type === null || type === Tasks.CUSTOMIZED_TASK_TYPE || type === 'shell' || type === 'process'); } + const builtinTypeContextMap: IStringDictionary> = { + shell: ShellExecutionSupportedContext, + process: ProcessExecutionSupportedContext + }; + export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource): TaskParseResult { let result: TaskParseResult = { custom: [], configured: [] }; if (!externals) { @@ -1667,6 +1673,27 @@ namespace TaskParser { const baseLoadIssues = Objects.deepClone(context.taskLoadIssues); for (let index = 0; index < externals.length; index++) { let external = externals[index]; + const definition = external.type ? TaskDefinitionRegistry.get(external.type) : undefined; + let typeNotSupported: boolean = false; + if (definition && definition.when && !context.contextKeyService.contextMatchesRules(definition.when)) { + typeNotSupported = true; + } else if (!definition && external.type) { + for (const key of Object.keys(builtinTypeContextMap)) { + if (external.type === key) { + typeNotSupported = !ShellExecutionSupportedContext.evaluate(context.contextKeyService.getContext(null)); + break; + } + } + } + + if (typeNotSupported) { + context.problemReporter.info(nls.localize( + 'taskConfiguration.providerUnavailable', 'Warning: {0} tasks are unavailable in the current environment.\n', + external.type + )); + continue; + } + if (isCustomTask(external)) { let customTask = CustomTask.from(external, context, index, source); if (customTask) { @@ -1976,7 +2003,7 @@ class ConfigurationParser { this.uuidMap = uuidMap; } - public run(fileConfig: ExternalTaskRunnerConfiguration, source: TaskConfigSource): ParseResult { + public run(fileConfig: ExternalTaskRunnerConfiguration, source: TaskConfigSource, contextKeyService: IContextKeyService): ParseResult { let engine = ExecutionEngine.from(fileConfig); let schemaVersion = JsonSchemaVersion.from(fileConfig); let context: ParseContext = { @@ -1988,7 +2015,8 @@ class ConfigurationParser { engine, schemaVersion, platform: this.platform, - taskLoadIssues: [] + taskLoadIssues: [], + contextKeyService }; let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context, source); return { @@ -2081,7 +2109,7 @@ class ConfigurationParser { let uuidMaps: Map> = new Map(); let recentUuidMaps: Map> = new Map(); -export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource, isRecents: boolean = false): ParseResult { +export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource, contextKeyService: IContextKeyService, isRecents: boolean = false): ParseResult { let recentOrOtherMaps = isRecents ? recentUuidMaps : uuidMaps; let selectedUuidMaps = recentOrOtherMaps.get(source); if (!selectedUuidMaps) { @@ -2095,7 +2123,7 @@ export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | } try { uuidMap.start(); - return (new ConfigurationParser(workspaceFolder, workspace, platform, logger, uuidMap)).run(configuration, source); + return (new ConfigurationParser(workspaceFolder, workspace, platform, logger, uuidMap)).run(configuration, source, contextKeyService); } finally { uuidMap.finish(); } diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index 1526a62d12bc44640b336caa1fa5ce3c7cfbdc6e..4ec3b1db74491272e6c937262008804b2d53072f 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -13,6 +13,7 @@ import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/serv import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; const taskDefinitionSchema: IJSONSchema = { @@ -35,6 +36,10 @@ const taskDefinitionSchema: IJSONSchema = { additionalProperties: { $ref: 'http://json-schema.org/draft-07/schema#' } + }, + when: { + type: 'string', + markdownDescription: nls.localize('TaskDefinition.when', 'Condition when the task definition is valid. Consider using `shellExecutionSupported`, `processExecutionSupported`, and `customExecutionSupported` as appropriate for this task definition.') } } }; @@ -44,6 +49,7 @@ namespace Configuration { type?: string; required?: string[]; properties?: IJSONSchemaMap; + when?: string; } export function from(value: TaskDefinition, extensionId: ExtensionIdentifier, messageCollector: ExtensionMessageCollector): Tasks.TaskDefinition | undefined { @@ -63,7 +69,12 @@ namespace Configuration { } } } - return { extensionId: extensionId.value, taskType, required: required, properties: value.properties ? Objects.deepClone(value.properties) : {} }; + return { + extensionId: extensionId.value, + taskType, required: required, + properties: value.properties ? Objects.deepClone(value.properties) : {}, + when: value.when ? ContextKeyExpr.deserialize(value.when) : undefined + }; } } diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 18758775d5007da2c1b323949b26f44701cb8c56..e456f8e7b53dd65813a14ecd2296518b047a0cc3 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -13,9 +13,14 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works import { Task, ContributedTask, CustomTask, TaskSet, TaskSorter, TaskEvent, TaskIdentifier, ConfiguringTask, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSummary, TaskTerminateResponse, TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { IStringDictionary } from 'vs/base/common/collections'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export { ITaskSummary, Task, TaskTerminateResponse }; +export const CustomExecutionSupportedContext = new RawContextKey('customExecutionSupported', true); +export const ShellExecutionSupportedContext = new RawContextKey('shellExecutionSupported', false); +export const ProcessExecutionSupportedContext = new RawContextKey('processExecutionSupported', false); + export const ITaskService = createDecorator('taskService'); export interface ITaskProvider { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 079fdde1d68951babf9d34028ea408b3ba2b9be6..ec40a24a5482b5fbd54582a65fb3e5e0d9ff0170 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -12,7 +12,7 @@ import { UriComponents, URI } from 'vs/base/common/uri'; import { ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -1002,6 +1002,7 @@ export interface TaskDefinition { taskType: string; required: string[]; properties: IJSONSchemaMap; + when?: ContextKeyExpression; } export class TaskSorter { diff --git a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts index 4dbac3fbf9114192926ce85c383123b0598e1965..16470daef7b1f6c2df485a62ad1aa7ba081291fa 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts @@ -25,6 +25,10 @@ interface WorkspaceFolderConfigurationResult { export class TaskService extends AbstractTaskService { private _configHasErrors: boolean = false; + protected setExecutionContexts(): void { + super.setExecutionContexts(true, true, true); + } + protected getTaskSystem(): ITaskSystem { if (this._taskSystem) { return this._taskSystem; diff --git a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts index 766b2115a83f29cb15ffe4cd8856961c1bb2410f..7784109487f65a584ec6311a82d0025bf4fa2929 100644 --- a/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/configuration.test.ts @@ -14,6 +14,8 @@ import { WorkspaceFolder, Workspace, IWorkspace } from 'vs/platform/workspace/co import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask, TaskConfigSource } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { IContext } from 'vs/platform/contextkey/common/contextkey'; const workspaceFolder: WorkspaceFolder = new WorkspaceFolder({ uri: URI.file('/workspace/folderOne'), @@ -357,9 +359,19 @@ class PatternBuilder { } } +class TasksMockContextKeyService extends MockContextKeyService { + public getContext(domNode: HTMLElement): IContext { + return { + getValue: (_key: string) => { + return true; + } + }; + } +} + function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, resolved: number) { let reporter = new ProblemReporter(); - let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson); + let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson, new TasksMockContextKeyService()); assert.ok(!reporter.receivedMessage); assert.strictEqual(result.custom.length, 1); let task = result.custom[0]; @@ -370,7 +382,7 @@ function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, re function testConfiguration(external: ExternalTaskRunnerConfiguration, builder: ConfiguationBuilder): void { builder.done(); let reporter = new ProblemReporter(); - let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson); + let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson, new TasksMockContextKeyService()); if (reporter.receivedMessage) { assert.ok(false, reporter.lastMessage); }