aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-06-26 17:52:53 +0100
committerGitHub <[email protected]>2020-06-26 17:52:53 +0100
commite07826b1991bbb34da71c99d2f08d11ed8c84885 (patch)
tree33707ec570f04f070d39a19eb80c700788adfee5
parent7e4d26be22d5fa69261fb35e98c4e19692f3287d (diff)
parent2791f37a045962535c7b8691a942a7e739d75cad (diff)
Merge #5017
5017: Add custom cargo runners support. r=matklad a=vsrs This PR adds an option to delegate actual cargo commands building to another extension. For example, to use a different manager like [cross](https://github.com/rust-embedded/cross). https://github.com/vsrs/cross-rust-analyzer is an example of such extension. I'll publish it after the rust-analyzer release with this functionality. Fixes https://github.com/rust-analyzer/rust-analyzer/issues/4902 Co-authored-by: vsrs <[email protected]>
-rw-r--r--editors/code/package.json8
-rw-r--r--editors/code/src/commands.ts4
-rw-r--r--editors/code/src/config.ts4
-rw-r--r--editors/code/src/main.ts2
-rw-r--r--editors/code/src/run.ts58
-rw-r--r--editors/code/src/tasks.ts111
6 files changed, 104 insertions, 83 deletions
diff --git a/editors/code/package.json b/editors/code/package.json
index 68484a370..f542a490a 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -336,6 +336,14 @@
336 "default": null, 336 "default": null,
337 "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." 337 "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`."
338 }, 338 },
339 "rust-analyzer.cargoRunner": {
340 "type": [
341 "null",
342 "string"
343 ],
344 "default": null,
345 "description": "Custom cargo runner extension ID."
346 },
339 "rust-analyzer.inlayHints.enable": { 347 "rust-analyzer.inlayHints.enable": {
340 "type": "boolean", 348 "type": "boolean",
341 "default": true, 349 "default": true,
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 48a25495f..8c9d7802f 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -394,7 +394,7 @@ export function run(ctx: Ctx): Cmd {
394 394
395 item.detail = 'rerun'; 395 item.detail = 'rerun';
396 prevRunnable = item; 396 prevRunnable = item;
397 const task = createTask(item.runnable); 397 const task = await createTask(item.runnable, ctx.config);
398 return await vscode.tasks.executeTask(task); 398 return await vscode.tasks.executeTask(task);
399 }; 399 };
400} 400}
@@ -404,7 +404,7 @@ export function runSingle(ctx: Ctx): Cmd {
404 const editor = ctx.activeRustEditor; 404 const editor = ctx.activeRustEditor;
405 if (!editor) return; 405 if (!editor) return;
406 406
407 const task = createTask(runnable); 407 const task = await createTask(runnable, ctx.config);
408 task.group = vscode.TaskGroup.Build; 408 task.group = vscode.TaskGroup.Build;
409 task.presentationOptions = { 409 task.presentationOptions = {
410 reveal: vscode.TaskRevealKind.Always, 410 reveal: vscode.TaskRevealKind.Always,
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 9591d4fe3..fc95a7de6 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -110,6 +110,10 @@ export class Config {
110 }; 110 };
111 } 111 }
112 112
113 get cargoRunner() {
114 return this.get<string | undefined>("cargoRunner");
115 }
116
113 get debug() { 117 get debug() {
114 // "/rustc/<id>" used by suggestions only. 118 // "/rustc/<id>" used by suggestions only.
115 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); 119 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index cdb63b46f..5ceab8b44 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -114,7 +114,7 @@ export async function activate(context: vscode.ExtensionContext) {
114 ctx.registerCommand('applyActionGroup', commands.applyActionGroup); 114 ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
115 ctx.registerCommand('gotoLocation', commands.gotoLocation); 115 ctx.registerCommand('gotoLocation', commands.gotoLocation);
116 116
117 ctx.pushCleanup(activateTaskProvider(workspaceFolder)); 117 ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config));
118 118
119 activateInlayHints(ctx); 119 activateInlayHints(ctx);
120 120
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index bb060cfe1..766b05112 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -1,10 +1,11 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as ra from './lsp_ext'; 3import * as ra from './lsp_ext';
4import * as toolchain from "./toolchain"; 4import * as tasks from './tasks';
5 5
6import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
7import { makeDebugConfig } from './debug'; 7import { makeDebugConfig } from './debug';
8import { Config } from './config';
8 9
9const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; 10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
10 11
@@ -95,52 +96,29 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
95 } 96 }
96} 97}
97 98
98interface CargoTaskDefinition extends vscode.TaskDefinition { 99export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
99 type: 'cargo'; 100 if (runnable.kind !== "cargo") {
100 label: string; 101 // rust-analyzer supports only one kind, "cargo"
101 command: string; 102 // do not use tasks.TASK_TYPE here, these are completely different meanings.
102 args: string[];
103 env?: { [key: string]: string };
104}
105
106export function createTask(runnable: ra.Runnable): vscode.Task {
107 const TASK_SOURCE = 'Rust';
108 103
109 let command; 104 throw `Unexpected runnable kind: ${runnable.kind}`;
110 switch (runnable.kind) {
111 case "cargo": command = toolchain.getPathForExecutable("cargo");
112 } 105 }
106
113 const args = [...runnable.args.cargoArgs]; // should be a copy! 107 const args = [...runnable.args.cargoArgs]; // should be a copy!
114 if (runnable.args.executableArgs.length > 0) { 108 if (runnable.args.executableArgs.length > 0) {
115 args.push('--', ...runnable.args.executableArgs); 109 args.push('--', ...runnable.args.executableArgs);
116 } 110 }
117 const definition: CargoTaskDefinition = { 111 const definition: tasks.CargoTaskDefinition = {
118 type: 'cargo', 112 type: tasks.TASK_TYPE,
119 label: runnable.label, 113 command: args[0], // run, test, etc...
120 command, 114 args: args.slice(1),
121 args, 115 cwd: runnable.args.workspaceRoot,
122 env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), 116 env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }),
123 }; 117 };
124 118
125 const execOption: vscode.ShellExecutionOptions = { 119 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
126 cwd: runnable.args.workspaceRoot || '.', 120 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true);
127 env: definition.env, 121 cargoTask.presentationOptions.clear = true;
128 }; 122
129 const exec = new vscode.ShellExecution( 123 return cargoTask;
130 definition.command,
131 definition.args,
132 execOption,
133 );
134
135 const f = vscode.workspace.workspaceFolders![0];
136 const t = new vscode.Task(
137 definition,
138 f,
139 definition.label,
140 TASK_SOURCE,
141 exec,
142 ['$rustc'],
143 );
144 t.presentationOptions.clear = true;
145 return t;
146} 124}
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index 9748824df..14abbd5b7 100644
--- a/editors/code/src/tasks.ts
+++ b/editors/code/src/tasks.ts
@@ -1,11 +1,14 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as toolchain from "./toolchain"; 2import * as toolchain from "./toolchain";
3import { Config } from './config';
4import { log } from './util';
3 5
4// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and 6// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
5// our configuration should be compatible with it so use the same key. 7// our configuration should be compatible with it so use the same key.
6const TASK_TYPE = 'cargo'; 8export const TASK_TYPE = 'cargo';
9export const TASK_SOURCE = 'rust';
7 10
8interface CargoTaskDefinition extends vscode.TaskDefinition { 11export interface CargoTaskDefinition extends vscode.TaskDefinition {
9 command?: string; 12 command?: string;
10 args?: string[]; 13 args?: string[];
11 cwd?: string; 14 cwd?: string;
@@ -14,73 +17,101 @@ interface CargoTaskDefinition extends vscode.TaskDefinition {
14 17
15class CargoTaskProvider implements vscode.TaskProvider { 18class CargoTaskProvider implements vscode.TaskProvider {
16 private readonly target: vscode.WorkspaceFolder; 19 private readonly target: vscode.WorkspaceFolder;
20 private readonly config: Config;
17 21
18 constructor(target: vscode.WorkspaceFolder) { 22 constructor(target: vscode.WorkspaceFolder, config: Config) {
19 this.target = target; 23 this.target = target;
24 this.config = config;
20 } 25 }
21 26
22 provideTasks(): vscode.Task[] { 27 async provideTasks(): Promise<vscode.Task[]> {
23 // Detect Rust tasks. Currently we do not do any actual detection 28 // Detect Rust tasks. Currently we do not do any actual detection
24 // of tasks (e.g. aliases in .cargo/config) and just return a fixed 29 // of tasks (e.g. aliases in .cargo/config) and just return a fixed
25 // set of tasks that always exist. These tasks cannot be removed in 30 // set of tasks that always exist. These tasks cannot be removed in
26 // tasks.json - only tweaked. 31 // tasks.json - only tweaked.
27 32
28 const cargoPath = toolchain.cargoPath(); 33 const defs = [
29
30 return [
31 { command: 'build', group: vscode.TaskGroup.Build }, 34 { command: 'build', group: vscode.TaskGroup.Build },
32 { command: 'check', group: vscode.TaskGroup.Build }, 35 { command: 'check', group: vscode.TaskGroup.Build },
33 { command: 'test', group: vscode.TaskGroup.Test }, 36 { command: 'test', group: vscode.TaskGroup.Test },
34 { command: 'clean', group: vscode.TaskGroup.Clean }, 37 { command: 'clean', group: vscode.TaskGroup.Clean },
35 { command: 'run', group: undefined }, 38 { command: 'run', group: undefined },
36 ] 39 ];
37 .map(({ command, group }) => { 40
38 const vscodeTask = new vscode.Task( 41 const tasks: vscode.Task[] = [];
39 // The contents of this object end up in the tasks.json entries. 42 for (const def of defs) {
40 { 43 const vscodeTask = await buildCargoTask(this.target, { type: TASK_TYPE, command: def.command }, `cargo ${def.command}`, [def.command], this.config.cargoRunner);
41 type: TASK_TYPE, 44 vscodeTask.group = def.group;
42 command, 45 tasks.push(vscodeTask);
43 }, 46 }
44 // The scope of the task - workspace or specific folder (global 47
45 // is not supported). 48 return tasks;
46 this.target,
47 // The task name, and task source. These are shown in the UI as
48 // `${source}: ${name}`, e.g. `rust: cargo build`.
49 `cargo ${command}`,
50 'rust',
51 // What to do when this command is executed.
52 new vscode.ShellExecution(cargoPath, [command]),
53 // Problem matchers.
54 ['$rustc'],
55 );
56 vscodeTask.group = group;
57 return vscodeTask;
58 });
59 } 49 }
60 50
61 resolveTask(task: vscode.Task): vscode.Task | undefined { 51 async resolveTask(task: vscode.Task): Promise<vscode.Task | undefined> {
62 // VSCode calls this for every cargo task in the user's tasks.json, 52 // VSCode calls this for every cargo task in the user's tasks.json,
63 // we need to inform VSCode how to execute that command by creating 53 // we need to inform VSCode how to execute that command by creating
64 // a ShellExecution for it. 54 // a ShellExecution for it.
65 55
66 const definition = task.definition as CargoTaskDefinition; 56 const definition = task.definition as CargoTaskDefinition;
67 57
68 if (definition.type === 'cargo' && definition.command) { 58 if (definition.type === TASK_TYPE && definition.command) {
69 const args = [definition.command].concat(definition.args ?? []); 59 const args = [definition.command].concat(definition.args ?? []);
70 60
71 return new vscode.Task( 61 return await buildCargoTask(this.target, definition, task.name, args, this.config.cargoRunner);
72 definition,
73 task.name,
74 'rust',
75 new vscode.ShellExecution('cargo', args, definition),
76 );
77 } 62 }
78 63
79 return undefined; 64 return undefined;
80 } 65 }
81} 66}
82 67
83export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable { 68export async function buildCargoTask(
84 const provider = new CargoTaskProvider(target); 69 target: vscode.WorkspaceFolder,
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.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 exec = new vscode.ShellExecution(toolchain.cargoPath(), args, definition);
102 }
103
104 return new vscode.Task(
105 definition,
106 target,
107 name,
108 TASK_SOURCE,
109 exec,
110 ['$rustc']
111 );
112}
113
114export function activateTaskProvider(target: vscode.WorkspaceFolder, config: Config): vscode.Disposable {
115 const provider = new CargoTaskProvider(target, config);
85 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); 116 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider);
86} 117}