任务提供者
用户通常在Visual Studio Code中的tasks.json
文件中定义任务。然而,在软件开发过程中,有一些任务可以通过带有任务提供者的VS Code扩展自动检测到。当从VS Code运行任务:运行任务命令时,所有活动的任务提供者都会贡献用户可以运行的任务。虽然tasks.json
文件允许用户手动为特定文件夹或工作区定义任务,但任务提供者可以检测工作区的详细信息,然后自动创建相应的VS Code任务。例如,任务提供者可以检查是否存在特定的构建文件,如make
或Rakefile
,并创建构建任务。本主题描述了扩展如何自动检测并向最终用户提供任务。
本指南教你如何构建一个自动检测在Rakefiles中定义的任务的任务提供者。完整的源代码位于:https://github.com/microsoft/vscode-extension-samples/tree/main/task-provider-sample。
任务定义
为了在系统中唯一标识一个任务,贡献任务的扩展需要定义标识任务的属性。在Rake示例中,任务定义如下所示:
"taskDefinitions": [
{
"type": "rake",
"required": [
"task"
],
"properties": {
"task": {
"type": "string",
"description": "The Rake task to customize"
},
"file": {
"type": "string",
"description": "The Rake file that provides the task. Can be omitted."
}
}
}
]
这为rake
任务提供了一个任务定义。任务定义有两个属性task
和file
。task
是Rake任务的名称,file
指向包含任务的Rakefile
。task
属性是必需的,file
属性是可选的。如果省略file
属性,则使用工作区文件夹根目录中的Rakefile
。
When 子句
任务定义可以选择性地包含一个when
属性。when
属性指定了此类型任务可用的条件。when
属性的功能与VS Code中的其他位置相同,这些位置也有when
属性。在创建任务定义时,应始终考虑以下上下文:
shellExecutionSupported
: 当VS Code可以运行ShellExecution
任务时为True,例如当VS Code作为桌面应用程序运行或使用远程扩展(如Dev Containers)时。processExecutionSupported
: 当VS Code可以运行ProcessExecution
任务时为True,例如当VS Code作为桌面应用程序运行或使用远程扩展(如Dev Containers)时。目前,它将始终与shellExecutionSupported
具有相同的值。customExecutionSupported
: 当VS Code可以运行CustomExecution
时为True。这始终为true。
任务提供者
类似于让扩展支持代码补全的语言提供者,扩展可以注册一个任务提供者来计算所有可用的任务。这是通过使用vscode.tasks
命名空间来完成的,如下面的代码片段所示:
import * as vscode from 'vscode';
let rakePromise: Thenable<vscode.Task[]> | undefined = undefined;
const taskProvider = vscode.tasks.registerTaskProvider('rake', {
provideTasks: () => {
if (!rakePromise) {
rakePromise = getRakeTasks();
}
return rakePromise;
},
resolveTask(_task: vscode.Task): vscode.Task | undefined {
const task = _task.definition.task;
// A Rake task consists of a task and an optional file as specified in RakeTaskDefinition
// Make sure that this looks like a Rake task by checking that there is a task.
if (task) {
// resolveTask requires that the same definition object be used.
const definition: RakeTaskDefinition = <any>_task.definition;
return new vscode.Task(
definition,
_task.scope ?? vscode.TaskScope.Workspace,
definition.task,
'rake',
new vscode.ShellExecution(`rake ${definition.task}`)
);
}
return undefined;
}
});
与provideTasks
类似,resolveTask
方法由VS Code调用以从扩展中获取任务。resolveTask
可以代替provideTasks
被调用,旨在为实现了它的提供者提供可选的性能提升。例如,如果用户有一个运行扩展提供任务的快捷键绑定,VS Code调用resolveTask
来获取该任务提供者的任务,并快速获取单个任务,而不是调用provideTasks
并等待扩展提供所有任务,这样会更好。有一个设置允许用户关闭单个任务提供者是一个好的做法,因此这很常见。用户可能会注意到来自特定提供者的任务获取速度较慢,并关闭该提供者。在这种情况下,用户可能仍然在他们的tasks.json
中引用该提供者的一些任务。如果未实现resolveTask
,则会有一个警告,指出他们的tasks.json
中的任务未创建。通过resolveTask
,扩展仍然可以为tasks.json
中定义的任务提供任务。
getRakeTasks
实现执行以下操作:
- 列出每个工作区文件夹中使用
rake -AT -f Rakefile
命令在Rakefile
中定义的所有rake任务。 - 解析标准输入输出。
- 对于每个列出的任务,创建一个
vscode.Task
实现。
由于Rake任务实例化需要一个在package.json
文件中定义的任务定义,VS Code也使用TypeScript接口定义结构,如下所示:
interface RakeTaskDefinition extends vscode.TaskDefinition {
/**
* The task name
*/
task: string;
/**
* The rake file containing the task
*/
file?: string;
}
假设输出来自第一个工作区文件夹中名为compile
的任务,那么相应的任务创建如下所示:
let task = new vscode.Task(
{ type: 'rake', task: 'compile' },
vscode.workspace.workspaceFolders[0],
'compile',
'rake',
new vscode.ShellExecution('rake compile')
);
对于输出中列出的每个任务,使用上述模式创建一个相应的VS Code任务,然后从getRakeTasks
调用返回所有任务的数组。
ShellExecution
在特定于操作系统的 shell 中执行 rake compile
命令(例如在 Windows 下,该命令将在 PowerShell 中执行,在 Ubuntu 下将在 bash 中执行)。如果任务应直接执行一个进程(而不生成 shell),则可以使用 vscode.ProcessExecution
。ProcessExecution
的优势在于扩展可以完全控制传递给进程的参数。使用 ShellExecution
则利用了 shell 命令的解释功能(例如在 bash 下的通配符扩展)。如果 ShellExecution
是用单个命令行创建的,则扩展需要确保命令内部的正确引用和转义(例如处理空格)。
自定义执行
一般来说,最好使用ShellExecution
或ProcessExecution
,因为它们很简单。然而,如果你的任务需要在运行之间保存大量状态,不适合作为单独的脚本或进程运行,或者需要大量处理输出,那么CustomExecution
可能是一个不错的选择。CustomExecution
的现有用途通常用于复杂的构建系统。CustomExecution
只有一个回调函数,该回调函数在任务运行时执行。这使得任务可以更灵活地执行操作,但也意味着任务提供者需要负责任何必要的进程管理和输出解析。任务提供者还负责实现Pseudoterminal
并从CustomExecution
回调中返回它。
return new vscode.Task(
definition,
vscode.TaskScope.Workspace,
`${flavor} ${flags.join(' ')}`,
CustomBuildTaskProvider.CustomBuildScriptType,
new vscode.CustomExecution(
async (): Promise<vscode.Pseudoterminal> => {
// When the task is executed, this callback will run. Here, we setup for running the task.
return new CustomBuildTaskTerminal(
this.workspaceRoot,
flavor,
flags,
() => this.sharedState,
(state: string) => (this.sharedState = state)
);
}
)
);
完整的示例,包括Pseudoterminal
的实现,位于https://github.com/microsoft/vscode-extension-samples/tree/main/task-provider-sample/src/customTaskProvider.ts。