任务提供程序

用户通常在 Visual Studio Code 的 tasks.json 文件中定义任务。然而,软件开发过程中有些任务可以通过带有任务提供程序(Task Provider)的 VS Code 扩展自动检测到。当从 VS Code 运行 Tasks: Run Task 命令时,所有激活的任务提供程序都会贡献出用户可以运行的任务。虽然 tasks.json 文件允许用户手动为特定文件夹或工作区定义任务,但任务提供程序可以检测工作区的详细信息,然后自动创建相应的 VS Code 任务。例如,任务提供程序可以检查是否存在特定的构建文件(如 makeRakefile),并创建一个构建任务。本主题介绍了扩展如何向最终用户自动检测并提供任务。

本指南将教您如何构建一个自动检测 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 任务贡献了一个任务定义。该任务定义有两个属性:taskfiletask 是 Rake 任务的名称,file 指向包含该任务的 Rakefiletask 属性是必需的,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 一样,VS Code 会调用 resolveTask 方法从扩展获取任务。调用 resolveTask 可以代替 provideTasks,旨在为实现它的提供程序提供可选的性能提升。例如,如果用户有运行扩展提供任务的快捷键,VS Code 直接为该任务提供程序调用 resolveTask 并快速获取单个任务,要比调用 provideTasks 并等待扩展提供所有任务更好。通常建议设置一个允许用户关闭单个任务提供程序的开关,这很常见。用户可能会注意到来自特定提供程序的任务获取速度较慢,从而关闭该提供程序。在这种情况下,用户可能仍会在其 tasks.json 中引用该提供程序的一些任务。如果未实现 resolveTask,则会发出警告,提示 tasks.json 中的任务未被创建。通过 resolveTask,扩展仍然可以为 tasks.json 中定义的任务提供支持。

getRakeTasks 的实现执行以下操作:

  • 使用 rake -AT -f Rakefile 命令为每个工作区文件夹列出 Rakefile 中定义的所有 rake 任务。
  • 解析标准输入输出(stdio)。
  • 为每个列出的任务创建一个 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.ProcessExecutionProcessExecution 的优势在于扩展可以完全控制传递给进程的参数。使用 ShellExecution 则会利用 shell 命令解释(例如 bash 下的通配符扩展)。如果使用单个命令行创建 ShellExecution,则扩展需要确保命令内部进行适当的引用和转义(例如处理空格)。

CustomExecution

通常情况下,最好使用 ShellExecutionProcessExecution,因为它们很简单。然而,如果您的任务在运行之间需要大量保存的状态,不适合作为单独的脚本或进程工作,或者需要对输出进行大量处理,那么 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

© . This site is unofficial and not affiliated with Microsoft.