1 import * as vscode from 'vscode';
2 import * as toolchain from "./toolchain";
3 import { Config } from './config';
4 import { log } from './util';
6 // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
7 // our configuration should be compatible with it so use the same key.
8 export const TASK_TYPE = 'cargo';
9 export const TASK_SOURCE = 'rust';
11 export interface CargoTaskDefinition extends vscode.TaskDefinition {
15 env?: { [key: string]: string };
16 overrideCargo?: string;
19 class CargoTaskProvider implements vscode.TaskProvider {
20 private readonly config: Config;
22 constructor(config: Config) {
26 async provideTasks(): Promise<vscode.Task[]> {
27 // Detect Rust tasks. Currently we do not do any actual detection
28 // of tasks (e.g. aliases in .cargo/config) and just return a fixed
29 // set of tasks that always exist. These tasks cannot be removed in
30 // tasks.json - only tweaked.
33 { command: 'build', group: vscode.TaskGroup.Build },
34 { command: 'check', group: vscode.TaskGroup.Build },
35 { command: 'test', group: vscode.TaskGroup.Test },
36 { command: 'clean', group: vscode.TaskGroup.Clean },
37 { command: 'run', group: undefined },
40 const tasks: vscode.Task[] = [];
41 for (const workspaceTarget of vscode.workspace.workspaceFolders || []) {
42 for (const def of defs) {
43 const vscodeTask = await buildCargoTask(workspaceTarget, { type: TASK_TYPE, command: def.command }, `cargo ${def.command}`, [def.command], this.config.cargoRunner);
44 vscodeTask.group = def.group;
45 tasks.push(vscodeTask);
52 async resolveTask(task: vscode.Task): Promise<vscode.Task | undefined> {
53 // VSCode calls this for every cargo task in the user's tasks.json,
54 // we need to inform VSCode how to execute that command by creating
55 // a ShellExecution for it.
57 const definition = task.definition as CargoTaskDefinition;
59 if (definition.type === TASK_TYPE && definition.command) {
60 const args = [definition.command].concat(definition.args ?? []);
61 return await buildCargoTask(task.scope, definition, task.name, args, this.config.cargoRunner);
68 export async function buildCargoTask(
69 scope: vscode.WorkspaceFolder | vscode.TaskScope | undefined,
70 definition: CargoTaskDefinition,
73 customRunner?: string,
74 throwOnError: boolean = false
75 ): Promise<vscode.Task> {
77 let exec: vscode.ProcessExecution | vscode.ShellExecution | undefined = undefined;
80 const runnerCommand = `${customRunner}.buildShellExecution`;
82 const runnerArgs = { kind: TASK_TYPE, args, cwd: definition.cwd, env: definition.env };
83 const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs);
85 if (customExec instanceof vscode.ShellExecution) {
88 log.debug("Invalid cargo ShellExecution", customExec);
89 throw "Invalid cargo ShellExecution.";
92 // fallback to default processing
95 if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`;
96 // fallback to default processing
101 // Check whether we must use a user-defined substitute for cargo.
102 // Split on spaces to allow overrides like "wrapper cargo".
103 const overrideCargo = definition.overrideCargo ?? definition.overrideCargo;
104 const cargoPath = await toolchain.cargoPath();
105 const cargoCommand = overrideCargo?.split(" ") ?? [cargoPath];
107 const fullCommand = [...cargoCommand, ...args];
109 exec = new vscode.ProcessExecution(fullCommand[0], fullCommand.slice(1), definition);
112 return new vscode.Task(
114 // scope can sometimes be undefined. in these situations we default to the workspace taskscope as
115 // recommended by the official docs: https://code.visualstudio.com/api/extension-guides/task-provider#task-provider)
116 scope ?? vscode.TaskScope.Workspace,
124 export function activateTaskProvider(config: Config): vscode.Disposable {
125 const provider = new CargoTaskProvider(config);
126 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider);