Skip to content

Commit eb4a91f

Browse files
committed
Add when property to tasks definition
Part of microsoft#102477
1 parent ffd12de commit eb4a91f

8 files changed

Lines changed: 138 additions & 28 deletions

File tree

extensions/npm/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@
290290
"type": "string",
291291
"description": "%taskdef.path%"
292292
}
293-
}
293+
},
294+
"when": "shellExecutionSupported"
294295
}
295296
]
296297
}

src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import {
5656
TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, TaskRunSource,
5757
KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition
5858
} from 'vs/workbench/contrib/tasks/common/tasks';
59-
import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService';
59+
import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, USER_TASKS_GROUP_KEY, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
6060
import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates';
6161

6262
import * as TaskConfig from '../common/taskConfiguration';
@@ -248,7 +248,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
248248
@IHostService private readonly _hostService: IHostService,
249249
@IDialogService private readonly dialogService: IDialogService,
250250
@INotificationService private readonly notificationService: INotificationService,
251-
@IContextKeyService contextKeyService: IContextKeyService,
251+
@IContextKeyService protected readonly contextKeyService: IContextKeyService,
252252
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
253253
@ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService,
254254
@IPathService private readonly pathService: IPathService,
@@ -331,6 +331,16 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
331331
}
332332
return task._label;
333333
});
334+
this.setExecutionContexts();
335+
}
336+
337+
protected setExecutionContexts(custom: boolean = true, shell: boolean = false, process: boolean = false): void {
338+
const customContext = CustomExecutionSupportedContext.bindTo(this.contextKeyService);
339+
customContext.set(custom);
340+
const shellContext = ShellExecutionSupportedContext.bindTo(this.contextKeyService);
341+
shellContext.set(shell);
342+
const processContext = ProcessExecutionSupportedContext.bindTo(this.contextKeyService);
343+
processContext.set(process);
334344
}
335345

336346
public get onDidStateChange(): Event<TaskEvent> {
@@ -556,14 +566,27 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
556566
public async tryResolveTask(configuringTask: ConfiguringTask): Promise<Task | undefined> {
557567
await Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), this.extensionService.whenInstalledExtensionsRegistered()]);
558568
let matchingProvider: ITaskProvider | undefined;
569+
let matchingProviderUnavailable: boolean = false;
559570
for (const [handle, provider] of this._providers) {
560-
if (configuringTask.type === this._providerTypes.get(handle)) {
571+
const providerType = this._providerTypes.get(handle);
572+
if (configuringTask.type === providerType) {
573+
if (providerType && !this.isTaskProviderEnabled(providerType)) {
574+
matchingProviderUnavailable = true;
575+
continue;
576+
}
561577
matchingProvider = provider;
562578
break;
563579
}
564580
}
565581

566582
if (!matchingProvider) {
583+
if (matchingProviderUnavailable) {
584+
this._outputChannel.append(nls.localize(
585+
'TaskService.providerUnavailable',
586+
'Warning: {0} tasks are unavailable in the current environment.\n',
587+
configuringTask.configures.type
588+
));
589+
}
567590
return;
568591
}
569592

@@ -624,7 +647,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
624647
if (this.isProvideTasksEnabled()) {
625648
for (const [handle] of this._providers) {
626649
const type = this._providerTypes.get(handle);
627-
if (type) {
650+
if (type && this.isTaskProviderEnabled(type)) {
628651
types.push(type);
629652
}
630653
}
@@ -1559,6 +1582,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
15591582

15601583
protected abstract getTaskSystem(): ITaskSystem;
15611584

1585+
private isTaskProviderEnabled(type: string) {
1586+
const definition = TaskDefinitionRegistry.get(type);
1587+
return !definition.when || this.contextKeyService.contextMatchesRules(definition.when);
1588+
}
1589+
15621590
private getGroupedTasks(type?: string): Promise<TaskMap> {
15631591
const needsRecentTasksMigration = this.needsRecentTasksMigration();
15641592
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
15961624
};
15971625
if (this.isProvideTasksEnabled() && (this.schemaVersion === JsonSchemaVersion.V2_0_0) && (this._providers.size > 0)) {
15981626
for (const [handle, provider] of this._providers) {
1599-
if ((type === undefined) || (type === this._providerTypes.get(handle))) {
1627+
const providerType = this._providerTypes.get(handle);
1628+
if ((type === undefined) || (type === providerType)) {
1629+
if (providerType && !this.isTaskProviderEnabled(providerType)) {
1630+
continue;
1631+
}
16001632
counter++;
16011633
provider.provideTasks(validTypes).then((taskSet: TaskSet) => {
16021634
// Check that the tasks provided are of the correct type
@@ -1699,8 +1731,16 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
16991731
return;
17001732
}
17011733

1734+
let requiredTaskProviderUnavailable: boolean = false;
1735+
17021736
for (const [handle, provider] of this._providers) {
1703-
if (configuringTask.type === this._providerTypes.get(handle)) {
1737+
const providerType = this._providerTypes.get(handle);
1738+
if (configuringTask.type === providerType) {
1739+
if (providerType && !this.isTaskProviderEnabled(providerType)) {
1740+
requiredTaskProviderUnavailable = true;
1741+
continue;
1742+
}
1743+
17041744
try {
17051745
const resolvedTask = await provider.resolveTask(configuringTask);
17061746
if (resolvedTask && (resolvedTask._id === configuringTask._id)) {
@@ -1713,13 +1753,21 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
17131753
}
17141754
}
17151755

1716-
this._outputChannel.append(nls.localize(
1717-
'TaskService.noConfiguration',
1718-
'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n',
1719-
configuringTask.configures.type,
1720-
JSON.stringify(configuringTask._source.config.element, undefined, 4)
1721-
));
1722-
this.showOutput();
1756+
if (requiredTaskProviderUnavailable) {
1757+
this._outputChannel.append(nls.localize(
1758+
'TaskService.providerUnavailable',
1759+
'Warning: {0} tasks are unavailable in the current environment.\n',
1760+
configuringTask.configures.type
1761+
));
1762+
} else {
1763+
this._outputChannel.append(nls.localize(
1764+
'TaskService.noConfiguration',
1765+
'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n',
1766+
configuringTask.configures.type,
1767+
JSON.stringify(configuringTask._source.config.element, undefined, 4)
1768+
));
1769+
this.showOutput();
1770+
}
17231771
});
17241772

17251773
await Promise.all(unUsedConfigurationPromises);
@@ -1831,9 +1879,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
18311879
return ProblemMatcherRegistry.onReady().then(async (): Promise<WorkspaceFolderTaskResult> => {
18321880
let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme);
18331881
let problemReporter = new ProblemReporter(this._outputChannel);
1834-
let parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson);
1882+
let parseResult = TaskConfig.parse(workspaceFolder, undefined, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson, this.contextKeyService);
18351883
let hasErrors = false;
1836-
if (!parseResult.validationStatus.isOK()) {
1884+
if (!parseResult.validationStatus.isOK() && (parseResult.validationStatus.state !== ValidationState.Info)) {
18371885
hasErrors = true;
18381886
this.showOutput(runSource);
18391887
}
@@ -1928,9 +1976,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
19281976
}
19291977
let taskSystemInfo: TaskSystemInfo | undefined = workspaceFolder ? this._taskSystemInfos.get(workspaceFolder.uri.scheme) : undefined;
19301978
let problemReporter = new ProblemReporter(this._outputChannel);
1931-
let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source, isRecentTask);
1979+
let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source, this.contextKeyService, isRecentTask);
19321980
let hasErrors = false;
1933-
if (!parseResult.validationStatus.isOK()) {
1981+
if (!parseResult.validationStatus.isOK() && (parseResult.validationStatus.state !== ValidationState.Info)) {
19341982
this.showOutput(runSource);
19351983
hasErrors = true;
19361984
}

src/vs/workbench/contrib/tasks/common/taskConfiguration.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import * as Tasks from './tasks';
2323
import { TaskDefinitionRegistry } from './taskDefinitionRegistry';
2424
import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
2525
import { URI } from 'vs/base/common/uri';
26-
import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService';
27-
26+
import { USER_TASKS_GROUP_KEY, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
27+
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
2828

2929
export const enum ShellQuoting {
3030
/**
@@ -709,6 +709,7 @@ interface ParseContext {
709709
schemaVersion: Tasks.JsonSchemaVersion;
710710
platform: Platform;
711711
taskLoadIssues: string[];
712+
contextKeyService: IContextKeyService;
712713
}
713714

714715

@@ -1656,6 +1657,11 @@ namespace TaskParser {
16561657
return customize === undefined && (type === undefined || type === null || type === Tasks.CUSTOMIZED_TASK_TYPE || type === 'shell' || type === 'process');
16571658
}
16581659

1660+
const builtinTypeContextMap: IStringDictionary<RawContextKey<boolean>> = {
1661+
shell: ShellExecutionSupportedContext,
1662+
process: ProcessExecutionSupportedContext
1663+
};
1664+
16591665
export function from(this: void, externals: Array<CustomTask | ConfiguringTask> | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource): TaskParseResult {
16601666
let result: TaskParseResult = { custom: [], configured: [] };
16611667
if (!externals) {
@@ -1667,6 +1673,27 @@ namespace TaskParser {
16671673
const baseLoadIssues = Objects.deepClone(context.taskLoadIssues);
16681674
for (let index = 0; index < externals.length; index++) {
16691675
let external = externals[index];
1676+
const definition = external.type ? TaskDefinitionRegistry.get(external.type) : undefined;
1677+
let typeNotSupported: boolean = false;
1678+
if (definition && definition.when && !context.contextKeyService.contextMatchesRules(definition.when)) {
1679+
typeNotSupported = true;
1680+
} else if (!definition && external.type) {
1681+
for (const key of Object.keys(builtinTypeContextMap)) {
1682+
if (external.type === key) {
1683+
typeNotSupported = !ShellExecutionSupportedContext.evaluate(context.contextKeyService.getContext(null));
1684+
break;
1685+
}
1686+
}
1687+
}
1688+
1689+
if (typeNotSupported) {
1690+
context.problemReporter.info(nls.localize(
1691+
'taskConfiguration.providerUnavailable', 'Warning: {0} tasks are unavailable in the current environment.\n',
1692+
external.type
1693+
));
1694+
continue;
1695+
}
1696+
16701697
if (isCustomTask(external)) {
16711698
let customTask = CustomTask.from(external, context, index, source);
16721699
if (customTask) {
@@ -1976,7 +2003,7 @@ class ConfigurationParser {
19762003
this.uuidMap = uuidMap;
19772004
}
19782005

1979-
public run(fileConfig: ExternalTaskRunnerConfiguration, source: TaskConfigSource): ParseResult {
2006+
public run(fileConfig: ExternalTaskRunnerConfiguration, source: TaskConfigSource, contextKeyService: IContextKeyService): ParseResult {
19802007
let engine = ExecutionEngine.from(fileConfig);
19812008
let schemaVersion = JsonSchemaVersion.from(fileConfig);
19822009
let context: ParseContext = {
@@ -1988,7 +2015,8 @@ class ConfigurationParser {
19882015
engine,
19892016
schemaVersion,
19902017
platform: this.platform,
1991-
taskLoadIssues: []
2018+
taskLoadIssues: [],
2019+
contextKeyService
19922020
};
19932021
let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context, source);
19942022
return {
@@ -2081,7 +2109,7 @@ class ConfigurationParser {
20812109

20822110
let uuidMaps: Map<TaskConfigSource, Map<string, UUIDMap>> = new Map();
20832111
let recentUuidMaps: Map<TaskConfigSource, Map<string, UUIDMap>> = new Map();
2084-
export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource, isRecents: boolean = false): ParseResult {
2112+
export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource, contextKeyService: IContextKeyService, isRecents: boolean = false): ParseResult {
20852113
let recentOrOtherMaps = isRecents ? recentUuidMaps : uuidMaps;
20862114
let selectedUuidMaps = recentOrOtherMaps.get(source);
20872115
if (!selectedUuidMaps) {
@@ -2095,7 +2123,7 @@ export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace |
20952123
}
20962124
try {
20972125
uuidMap.start();
2098-
return (new ConfigurationParser(workspaceFolder, workspace, platform, logger, uuidMap)).run(configuration, source);
2126+
return (new ConfigurationParser(workspaceFolder, workspace, platform, logger, uuidMap)).run(configuration, source, contextKeyService);
20992127
} finally {
21002128
uuidMap.finish();
21012129
}

src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/serv
1313

1414
import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks';
1515
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
16+
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
1617

1718

1819
const taskDefinitionSchema: IJSONSchema = {
@@ -35,6 +36,10 @@ const taskDefinitionSchema: IJSONSchema = {
3536
additionalProperties: {
3637
$ref: 'http://json-schema.org/draft-07/schema#'
3738
}
39+
},
40+
when: {
41+
type: 'string',
42+
markdownDescription: nls.localize('TaskDefinition.when', 'Condition when the task definition is valid. Consider using `shellExecutionSupported`, `processExecutionSupported`, and `customExecutionSupported` as appropriate for this task definition.')
3843
}
3944
}
4045
};
@@ -44,6 +49,7 @@ namespace Configuration {
4449
type?: string;
4550
required?: string[];
4651
properties?: IJSONSchemaMap;
52+
when?: string;
4753
}
4854

4955
export function from(value: TaskDefinition, extensionId: ExtensionIdentifier, messageCollector: ExtensionMessageCollector): Tasks.TaskDefinition | undefined {
@@ -63,7 +69,12 @@ namespace Configuration {
6369
}
6470
}
6571
}
66-
return { extensionId: extensionId.value, taskType, required: required, properties: value.properties ? Objects.deepClone(value.properties) : {} };
72+
return {
73+
extensionId: extensionId.value,
74+
taskType, required: required,
75+
properties: value.properties ? Objects.deepClone(value.properties) : {},
76+
when: value.when ? ContextKeyExpr.deserialize(value.when) : undefined
77+
};
6778
}
6879
}
6980

src/vs/workbench/contrib/tasks/common/taskService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works
1313
import { Task, ContributedTask, CustomTask, TaskSet, TaskSorter, TaskEvent, TaskIdentifier, ConfiguringTask, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks';
1414
import { ITaskSummary, TaskTerminateResponse, TaskSystemInfo } from 'vs/workbench/contrib/tasks/common/taskSystem';
1515
import { IStringDictionary } from 'vs/base/common/collections';
16+
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
1617

1718
export { ITaskSummary, Task, TaskTerminateResponse };
1819

20+
export const CustomExecutionSupportedContext = new RawContextKey<boolean>('customExecutionSupported', true);
21+
export const ShellExecutionSupportedContext = new RawContextKey<boolean>('shellExecutionSupported', false);
22+
export const ProcessExecutionSupportedContext = new RawContextKey<boolean>('processExecutionSupported', false);
23+
1924
export const ITaskService = createDecorator<ITaskService>('taskService');
2025

2126
export interface ITaskProvider {

src/vs/workbench/contrib/tasks/common/tasks.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { UriComponents, URI } from 'vs/base/common/uri';
1212

1313
import { ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher';
1414
import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
15-
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
15+
import { RawContextKey, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
1616
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
1717
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
1818
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
@@ -1002,6 +1002,7 @@ export interface TaskDefinition {
10021002
taskType: string;
10031003
required: string[];
10041004
properties: IJSONSchemaMap;
1005+
when?: ContextKeyExpression;
10051006
}
10061007

10071008
export class TaskSorter {

src/vs/workbench/contrib/tasks/electron-browser/taskService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ interface WorkspaceFolderConfigurationResult {
2525
export class TaskService extends AbstractTaskService {
2626
private _configHasErrors: boolean = false;
2727

28+
protected setExecutionContexts(): void {
29+
super.setExecutionContexts(true, true, true);
30+
}
31+
2832
protected getTaskSystem(): ITaskSystem {
2933
if (this._taskSystem) {
3034
return this._taskSystem;

src/vs/workbench/contrib/tasks/test/common/configuration.test.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { WorkspaceFolder, Workspace, IWorkspace } from 'vs/platform/workspace/co
1414

1515
import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks';
1616
import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask, TaskConfigSource } from 'vs/workbench/contrib/tasks/common/taskConfiguration';
17+
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
18+
import { IContext } from 'vs/platform/contextkey/common/contextkey';
1719

1820
const workspaceFolder: WorkspaceFolder = new WorkspaceFolder({
1921
uri: URI.file('/workspace/folderOne'),
@@ -357,9 +359,19 @@ class PatternBuilder {
357359
}
358360
}
359361

362+
class TasksMockContextKeyService extends MockContextKeyService {
363+
public getContext(domNode: HTMLElement): IContext {
364+
return {
365+
getValue: <T>(_key: string) => {
366+
return <T><unknown>true;
367+
}
368+
};
369+
}
370+
}
371+
360372
function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, resolved: number) {
361373
let reporter = new ProblemReporter();
362-
let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson);
374+
let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson, new TasksMockContextKeyService());
363375
assert.ok(!reporter.receivedMessage);
364376
assert.strictEqual(result.custom.length, 1);
365377
let task = result.custom[0];
@@ -370,7 +382,7 @@ function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, re
370382
function testConfiguration(external: ExternalTaskRunnerConfiguration, builder: ConfiguationBuilder): void {
371383
builder.done();
372384
let reporter = new ProblemReporter();
373-
let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson);
385+
let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson, new TasksMockContextKeyService());
374386
if (reporter.receivedMessage) {
375387
assert.ok(false, reporter.lastMessage);
376388
}

0 commit comments

Comments
 (0)