From a43a9103bc9a8c1bf735d51c952bc3b9352a00c3 Mon Sep 17 00:00:00 2001 From: vsrs Date: Thu, 18 Jun 2020 22:20:13 +0300 Subject: [PATCH 1/3] Add custom cargo runners --- editors/code/package.json | 8 ++++++ editors/code/src/commands.ts | 4 +-- editors/code/src/config.ts | 4 +++ editors/code/src/main.ts | 2 +- editors/code/src/run.ts | 55 +++++++++++------------------------- editors/code/src/tasks.ts | 54 ++++++++++++++++++++++++++--------- 6 files changed, 72 insertions(+), 55 deletions(-) diff --git a/editors/code/package.json b/editors/code/package.json index 68484a370b0..f542a490a95 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -336,6 +336,14 @@ "default": null, "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." }, + "rust-analyzer.cargoRunner": { + "type": [ + "null", + "string" + ], + "default": null, + "description": "Custom cargo runner extension ID." + }, "rust-analyzer.inlayHints.enable": { "type": "boolean", "default": true, diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 48a25495fb9..8c9d7802fff 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -394,7 +394,7 @@ export function run(ctx: Ctx): Cmd { item.detail = 'rerun'; prevRunnable = item; - const task = createTask(item.runnable); + const task = await createTask(item.runnable, ctx.config); return await vscode.tasks.executeTask(task); }; } @@ -404,7 +404,7 @@ export function runSingle(ctx: Ctx): Cmd { const editor = ctx.activeRustEditor; if (!editor) return; - const task = createTask(runnable); + const task = await createTask(runnable, ctx.config); task.group = vscode.TaskGroup.Build; task.presentationOptions = { reveal: vscode.TaskRevealKind.Always, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 9591d4fe32c..fc95a7de6b8 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -110,6 +110,10 @@ export class Config { }; } + get cargoRunner() { + return this.get("cargoRunner"); + } + get debug() { // "/rustc/" used by suggestions only. const { ["/rustc/"]: _, ...sourceFileMap } = this.get>("debug.sourceFileMap"); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 12b4d051088..5b4f453c860 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -115,7 +115,7 @@ export async function activate(context: vscode.ExtensionContext) { ctx.registerCommand('applyActionGroup', commands.applyActionGroup); ctx.registerCommand('gotoLocation', commands.gotoLocation); - ctx.pushCleanup(activateTaskProvider(workspaceFolder)); + ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config)); activateStatusDisplay(ctx); diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index bb060cfe155..7ecdeeeaf7f 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -1,10 +1,11 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as ra from './lsp_ext'; -import * as toolchain from "./toolchain"; +import * as tasks from './tasks'; import { Ctx } from './ctx'; import { makeDebugConfig } from './debug'; +import { Config } from './config'; const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; @@ -95,52 +96,28 @@ export class RunnableQuickPick implements vscode.QuickPickItem { } } -interface CargoTaskDefinition extends vscode.TaskDefinition { - type: 'cargo'; - label: string; - command: string; - args: string[]; - env?: { [key: string]: string }; -} +export async function createTask(runnable: ra.Runnable, config: Config): Promise { + if (runnable.kind !== "cargo") { + // rust-analyzer supports only one kind, "cargo" + // do not use tasks.TASK_TYPE here, these are completely different meanings. -export function createTask(runnable: ra.Runnable): vscode.Task { - const TASK_SOURCE = 'Rust'; - - let command; - switch (runnable.kind) { - case "cargo": command = toolchain.getPathForExecutable("cargo"); + throw `Unexpected runnable kind: ${runnable.kind}`; } + const args = [...runnable.args.cargoArgs]; // should be a copy! if (runnable.args.executableArgs.length > 0) { args.push('--', ...runnable.args.executableArgs); } - const definition: CargoTaskDefinition = { - type: 'cargo', - label: runnable.label, - command, - args, + const definition: tasks.CargoTaskDefinition = { + type: tasks.TASK_TYPE, + command: args[0], // run, test, etc... + args: args.slice(1), + cwd: runnable.args.workspaceRoot, env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), }; - const execOption: vscode.ShellExecutionOptions = { - cwd: runnable.args.workspaceRoot || '.', - env: definition.env, - }; - const exec = new vscode.ShellExecution( - definition.command, - definition.args, - execOption, - ); + const cargoTask = await tasks.buildCargoTask(definition, runnable.label, args, config.cargoRunner); + cargoTask.presentationOptions.clear = true; - const f = vscode.workspace.workspaceFolders![0]; - const t = new vscode.Task( - definition, - f, - definition.label, - TASK_SOURCE, - exec, - ['$rustc'], - ); - t.presentationOptions.clear = true; - return t; + return cargoTask; } diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index 9748824df38..e2c43fdd418 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -1,11 +1,14 @@ import * as vscode from 'vscode'; import * as toolchain from "./toolchain"; +import { Config } from './config'; +import { log } from './util'; // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // our configuration should be compatible with it so use the same key. -const TASK_TYPE = 'cargo'; +export const TASK_TYPE = 'cargo'; +export const TASK_SOURCE = 'rust'; -interface CargoTaskDefinition extends vscode.TaskDefinition { +export interface CargoTaskDefinition extends vscode.TaskDefinition { command?: string; args?: string[]; cwd?: string; @@ -14,9 +17,11 @@ interface CargoTaskDefinition extends vscode.TaskDefinition { class CargoTaskProvider implements vscode.TaskProvider { private readonly target: vscode.WorkspaceFolder; + private readonly config: Config; - constructor(target: vscode.WorkspaceFolder) { + constructor(target: vscode.WorkspaceFolder, config: Config) { this.target = target; + this.config = config; } provideTasks(): vscode.Task[] { @@ -58,29 +63,52 @@ class CargoTaskProvider implements vscode.TaskProvider { }); } - resolveTask(task: vscode.Task): vscode.Task | undefined { + async resolveTask(task: vscode.Task): Promise { // VSCode calls this for every cargo task in the user's tasks.json, // we need to inform VSCode how to execute that command by creating // a ShellExecution for it. const definition = task.definition as CargoTaskDefinition; - if (definition.type === 'cargo' && definition.command) { + if (definition.type === TASK_TYPE && definition.command) { const args = [definition.command].concat(definition.args ?? []); - return new vscode.Task( - definition, - task.name, - 'rust', - new vscode.ShellExecution('cargo', args, definition), - ); + return await buildCargoTask(definition, task.name, args, this.config.cargoRunner); } return undefined; } } -export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable { - const provider = new CargoTaskProvider(target); +export async function buildCargoTask(definition: CargoTaskDefinition, name: string, args: string[], customRunner?: string): Promise { + if (customRunner) { + const runnerCommand = `${customRunner}.createCargoTask`; + try { + const runnerArgs = { name, args, cwd: definition.cwd, env: definition.env, source: TASK_SOURCE }; + const task = await vscode.commands.executeCommand(runnerCommand, runnerArgs); + + if (task instanceof vscode.Task) { + return task; + } else if (task) { + log.debug("Invalid cargo task", task); + throw `Invalid task!`; + } + // fallback to default processing + + } catch (e) { + throw `Cargo runner '${customRunner}' failed! ${e}`; + } + } + + return new vscode.Task( + definition, + name, + TASK_SOURCE, + new vscode.ShellExecution(toolchain.cargoPath(), args, definition), + ); +} + +export function activateTaskProvider(target: vscode.WorkspaceFolder, config: Config): vscode.Disposable { + const provider = new CargoTaskProvider(target, config); return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); } From 647b126da52b3dec1268df13b08c8a14744feb06 Mon Sep 17 00:00:00 2001 From: vsrs Date: Fri, 19 Jun 2020 12:42:26 +0300 Subject: [PATCH 2/3] Switch to ShellExecution instead of full Task --- editors/code/src/run.ts | 3 +- editors/code/src/tasks.ts | 85 ++++++++++++++++++++------------------- 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 7ecdeeeaf7f..766b0511269 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -116,7 +116,8 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), }; - const cargoTask = await tasks.buildCargoTask(definition, runnable.label, args, config.cargoRunner); + const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() + const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true); cargoTask.presentationOptions.clear = true; return cargoTask; diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index e2c43fdd418..20ddb3a5d97 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -24,43 +24,28 @@ class CargoTaskProvider implements vscode.TaskProvider { this.config = config; } - provideTasks(): vscode.Task[] { + async provideTasks(): Promise { // Detect Rust tasks. Currently we do not do any actual detection // of tasks (e.g. aliases in .cargo/config) and just return a fixed // set of tasks that always exist. These tasks cannot be removed in // tasks.json - only tweaked. - const cargoPath = toolchain.cargoPath(); - - return [ + const defs = [ { command: 'build', group: vscode.TaskGroup.Build }, { command: 'check', group: vscode.TaskGroup.Build }, { command: 'test', group: vscode.TaskGroup.Test }, { command: 'clean', group: vscode.TaskGroup.Clean }, { command: 'run', group: undefined }, - ] - .map(({ command, group }) => { - const vscodeTask = new vscode.Task( - // The contents of this object end up in the tasks.json entries. - { - type: TASK_TYPE, - command, - }, - // The scope of the task - workspace or specific folder (global - // is not supported). - this.target, - // The task name, and task source. These are shown in the UI as - // `${source}: ${name}`, e.g. `rust: cargo build`. - `cargo ${command}`, - 'rust', - // What to do when this command is executed. - new vscode.ShellExecution(cargoPath, [command]), - // Problem matchers. - ['$rustc'], - ); - vscodeTask.group = group; - return vscodeTask; - }); + ]; + + const tasks: vscode.Task[] = []; + for (const def of defs) { + const vscodeTask = await buildCargoTask(this.target, { type: TASK_TYPE, command: def.command }, `cargo ${def.command}`, [def.command], this.config.cargoRunner); + vscodeTask.group = def.group; + tasks.push(vscodeTask); + } + + return tasks; } async resolveTask(task: vscode.Task): Promise { @@ -73,38 +58,56 @@ class CargoTaskProvider implements vscode.TaskProvider { if (definition.type === TASK_TYPE && definition.command) { const args = [definition.command].concat(definition.args ?? []); - return await buildCargoTask(definition, task.name, args, this.config.cargoRunner); + return await buildCargoTask(this.target, definition, task.name, args, this.config.cargoRunner); } return undefined; } } -export async function buildCargoTask(definition: CargoTaskDefinition, name: string, args: string[], customRunner?: string): Promise { - if (customRunner) { - const runnerCommand = `${customRunner}.createCargoTask`; - try { - const runnerArgs = { name, args, cwd: definition.cwd, env: definition.env, source: TASK_SOURCE }; - const task = await vscode.commands.executeCommand(runnerCommand, runnerArgs); +export async function buildCargoTask( + target: vscode.WorkspaceFolder, + definition: CargoTaskDefinition, + name: string, + args: string[], + customRunner?: string, + throwOnError: boolean = false +): Promise { - if (task instanceof vscode.Task) { - return task; - } else if (task) { - log.debug("Invalid cargo task", task); - throw `Invalid task!`; + let exec: vscode.ShellExecution | undefined = undefined; + + if (customRunner) { + const runnerCommand = `${customRunner}.buildShellExecution`; + try { + const runnerArgs = { kind: TASK_TYPE, args, cwd: definition.cwd, env: definition.env }; + const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs); + if (customExec) { + if (customExec instanceof vscode.ShellExecution) { + exec = customExec as vscode.ShellExecution; + } else { + log.debug("Invalid cargo ShellExecution", customExec); + throw "Invalid cargo ShellExecution."; + } } // fallback to default processing } catch (e) { - throw `Cargo runner '${customRunner}' failed! ${e}`; + if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`; + // fallback to default processing } } + if (!exec) { + exec = new vscode.ShellExecution(toolchain.cargoPath(), args, definition) + } + return new vscode.Task( definition, + target, name, TASK_SOURCE, - new vscode.ShellExecution(toolchain.cargoPath(), args, definition), + exec, + ['$rustc'] ); } From 2791f37a045962535c7b8691a942a7e739d75cad Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 24 Jun 2020 11:50:14 +0300 Subject: [PATCH 3/3] Fix ts lints --- editors/code/src/tasks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index 20ddb3a5d97..14abbd5b794 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -83,7 +83,7 @@ export async function buildCargoTask( const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs); if (customExec) { if (customExec instanceof vscode.ShellExecution) { - exec = customExec as vscode.ShellExecution; + exec = customExec; } else { log.debug("Invalid cargo ShellExecution", customExec); throw "Invalid cargo ShellExecution."; @@ -98,7 +98,7 @@ export async function buildCargoTask( } if (!exec) { - exec = new vscode.ShellExecution(toolchain.cargoPath(), args, definition) + exec = new vscode.ShellExecution(toolchain.cargoPath(), args, definition); } return new vscode.Task(