aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/tasks.ts
blob: 694ee1e41304e04d57e72499501e08e3054c5cfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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.
export const TASK_TYPE = 'cargo';
export const TASK_SOURCE = 'rust';

export interface CargoTaskDefinition extends vscode.TaskDefinition {
    command?: string;
    args?: string[];
    cwd?: string;
    env?: { [key: string]: string };
    overrideCargo?: string;
}

class CargoTaskProvider implements vscode.TaskProvider {
    private readonly config: Config;

    constructor(config: Config) {
        this.config = config;
    }

    async provideTasks(): Promise<vscode.Task[]> {
        // 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 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 },
        ];

        const tasks: vscode.Task[] = [];
        for (const workspaceTarget of vscode.workspace.workspaceFolders || []) {
            for (const def of defs) {
                const vscodeTask = await buildCargoTask(workspaceTarget, { 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<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 === TASK_TYPE && definition.command) {
            const args = [definition.command].concat(definition.args ?? []);
            if (isWorkspaceFolder(task.scope)) {
                return await buildCargoTask(task.scope, definition, task.name, args, this.config.cargoRunner);
            }
        }

        return undefined;
    }
}

function isWorkspaceFolder(scope?: any): scope is vscode.WorkspaceFolder {
    return (scope as vscode.WorkspaceFolder).name !== undefined;
}

export async function buildCargoTask(
    target: vscode.WorkspaceFolder,
    definition: CargoTaskDefinition,
    name: string,
    args: string[],
    customRunner?: string,
    throwOnError: boolean = false
): Promise<vscode.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;
                } else {
                    log.debug("Invalid cargo ShellExecution", customExec);
                    throw "Invalid cargo ShellExecution.";
                }
            }
            // fallback to default processing

        } catch (e) {
            if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`;
            // fallback to default processing
        }
    }

    if (!exec) {
        // Check whether we must use a user-defined substitute for cargo.
        const cargoCommand = definition.overrideCargo ? definition.overrideCargo : toolchain.cargoPath();

        // Prepare the whole command as one line. It is required if user has provided override command which contains spaces,
        // for example "wrapper cargo". Without manual preparation the overridden command will be quoted and fail to execute.
        const fullCommand = [cargoCommand, ...args].join(" ");

        exec = new vscode.ShellExecution(fullCommand, definition);
    }

    return new vscode.Task(
        definition,
        target,
        name,
        TASK_SOURCE,
        exec,
        ['$rustc']
    );
}

export function activateTaskProvider(config: Config): vscode.Disposable {
    const provider = new CargoTaskProvider(config);
    return vscode.tasks.registerTaskProvider(TASK_TYPE, provider);
}