1import * as vscode from 'vscode'; 2import * as toolchain from "./toolchain"; 3import { Config } from './config'; 4import { log } from './util'; 5 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. 8export const TASK_TYPE = 'cargo'; 9export const TASK_SOURCE = 'rust'; 10 11export interface CargoTaskDefinition extends vscode.TaskDefinition { 12 command?: string; 13 args?: string[]; 14 cwd?: string; 15 env?: { [key: string]: string }; 16 overrideCargo?: string; 17} 18 19class CargoTaskProvider implements vscode.TaskProvider { 20 private readonly config: Config; 21 22 constructor(config: Config) { 23 this.config = config; 24 } 25 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. 31 32 const defs = [ 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 }, 38 ]; 39 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); 46 } 47 } 48 49 return tasks; 50 } 51 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. 56 57 const definition = task.definition as CargoTaskDefinition; 58 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); 62 } 63 64 return undefined; 65 } 66} 67 68export async function buildCargoTask( 69 scope: vscode.WorkspaceFolder | vscode.TaskScope | undefined, 70 definition: CargoTaskDefinition, 71 name: string, 72 args: string[], 73 customRunner?: string, 74 throwOnError: boolean = false 75): Promise<vscode.Task> { 76 77 let exec: vscode.ProcessExecution | vscode.ShellExecution | undefined = undefined; 78 79 if (customRunner) { 80 const runnerCommand = `${customRunner}.buildShellExecution`; 81 try { 82 const runnerArgs = { kind: TASK_TYPE, args, cwd: definition.cwd, env: definition.env }; 83 const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs); 84 if (customExec) { 85 if (customExec instanceof vscode.ShellExecution) { 86 exec = customExec; 87 } else { 88 log.debug("Invalid cargo ShellExecution", customExec); 89 throw "Invalid cargo ShellExecution."; 90 } 91 } 92 // fallback to default processing 93 94 } catch (e) { 95 if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`; 96 // fallback to default processing 97 } 98 } 99 100 if (!exec) { 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]; 106 107 const fullCommand = [...cargoCommand, ...args]; 108 109 exec = new vscode.ProcessExecution(fullCommand[0], fullCommand.slice(1), definition); 110 } 111 112 return new vscode.Task( 113 definition, 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, 117 name, 118 TASK_SOURCE, 119 exec, 120 ['$rustc'] 121 ); 122} 123 124export function activateTaskProvider(config: Config): vscode.Disposable { 125 const provider = new CargoTaskProvider(config); 126 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); 127} 128