From a43a9103bc9a8c1bf735d51c952bc3b9352a00c3 Mon Sep 17 00:00:00 2001
From: vsrs <vit@conrlab.com>
Date: Thu, 18 Jun 2020 22:20:13 +0300
Subject: 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      | 57 +++++++++++++-------------------------------
 editors/code/src/tasks.ts    | 54 +++++++++++++++++++++++++++++++----------
 6 files changed, 73 insertions(+), 56 deletions(-)

(limited to 'editors/code')

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 @@
                     "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 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 {
 
         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 9591d4fe3..fc95a7de6 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<string | undefined>("cargoRunner");
+    }
+
     get debug() {
         // "/rustc/<id>" used by suggestions only.
         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 12b4d0510..5b4f453c8 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 bb060cfe1..7ecdeeeaf 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 function createTask(runnable: ra.Runnable): vscode.Task {
-    const TASK_SOURCE = 'Rust';
+export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
+    if (runnable.kind !== "cargo") {
+        // rust-analyzer supports only one kind, "cargo"
+        // do not use tasks.TASK_TYPE here, these are completely different meanings.
 
-    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 f = vscode.workspace.workspaceFolders![0];
-    const t = new vscode.Task(
-        definition,
-        f,
-        definition.label,
-        TASK_SOURCE,
-        exec,
-        ['$rustc'],
-    );
-    t.presentationOptions.clear = true;
-    return t;
+    const cargoTask = await tasks.buildCargoTask(definition, runnable.label, args, config.cargoRunner);
+    cargoTask.presentationOptions.clear = true;
+
+    return cargoTask;
 }
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index 9748824df..e2c43fdd4 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.Task | undefined> {
         // 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<vscode.Task> {
+    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);
 }
-- 
cgit v1.2.3