Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions editors/code/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import {
applySnippetTextEdits,
type SnippetTextDocumentEdit,
} from "./snippets";
import { type RunnableQuickPick, selectRunnable, createTask, createCargoArgs } from "./run";
import {
type RunnableQuickPick,
selectRunnable,
createTaskFromRunnable,
createCargoArgs,
} from "./run";
import { AstInspector } from "./ast_inspector";
import {
isRustDocument,
Expand Down Expand Up @@ -1096,7 +1101,7 @@ export function run(ctx: CtxInit): Cmd {

item.detail = "rerun";
prevRunnable = item;
const task = await createTask(item.runnable, ctx.config);
const task = await createTaskFromRunnable(item.runnable, ctx.config);
return await vscode.tasks.executeTask(task);
};
}
Expand Down Expand Up @@ -1139,7 +1144,7 @@ export function runSingle(ctx: CtxInit): Cmd {
const editor = ctx.activeRustEditor;
if (!editor) return;

const task = await createTask(runnable, ctx.config);
const task = await createTaskFromRunnable(runnable, ctx.config);
task.group = vscode.TaskGroup.Build;
task.presentationOptions = {
reveal: vscode.TaskRevealKind.Always,
Expand Down
12 changes: 10 additions & 2 deletions editors/code/src/lsp_ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,16 @@ export type OpenCargoTomlParams = {
export type Runnable = {
label: string;
location?: lc.LocationLink;
kind: "cargo" | "shell";
args: CargoRunnableArgs | ShellRunnableArgs;
} & (RunnableCargo | RunnableShell);

type RunnableCargo = {
kind: "cargo";
args: CargoRunnableArgs;
};

type RunnableShell = {
kind: "shell";
args: ShellRunnableArgs;
};

export type ShellRunnableArgs = {
Expand Down
18 changes: 10 additions & 8 deletions editors/code/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,13 @@ export function prepareEnv(
return env;
}

export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
export async function createTaskFromRunnable(
runnable: ra.Runnable,
config: Config,
): Promise<vscode.Task> {
let definition: tasks.RustTargetDefinition;
if (runnable.kind === "cargo") {
const runnableArgs = runnable.args as ra.CargoRunnableArgs;
const runnableArgs = runnable.args;
let args = createCargoArgs(runnableArgs);

let program: string;
Expand All @@ -128,17 +131,16 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
}

definition = {
type: tasks.TASK_TYPE,
type: tasks.CARGO_TASK_TYPE,
command: program,
args,
cwd: runnableArgs.workspaceRoot || ".",
env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv),
};
} else {
const runnableArgs = runnable.args as ra.ShellRunnableArgs;

const runnableArgs = runnable.args;
definition = {
type: "shell",
type: tasks.SHELL_TASK_TYPE,
command: runnableArgs.program,
args: runnableArgs.args,
cwd: runnableArgs.cwd,
Expand All @@ -148,13 +150,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise

// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
const exec = await tasks.targetToExecution(definition, config.cargoRunner, true);
const task = await tasks.buildRustTask(
target,
definition,
runnable.label,
config.problemMatcher,
config.cargoRunner,
true,
exec,
);

task.presentationOptions.clear = true;
Expand Down
115 changes: 56 additions & 59 deletions editors/code/src/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import * as vscode from "vscode";
import type { Config } from "./config";
import { log } from "./util";
import { expectNotUndefined, unwrapUndefinable } from "./undefinable";
import { unwrapUndefinable } from "./undefinable";
import * as toolchain from "./toolchain";

// 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 CARGO_TASK_TYPE = "cargo";
export const SHELL_TASK_TYPE = "shell";

export const TASK_SOURCE = "rust";
export const RUST_TASK_SOURCE = "rust";

export interface RustTargetDefinition extends vscode.TaskDefinition {
// The cargo command, such as "run" or "check".
export type RustTargetDefinition = {
readonly type: typeof CARGO_TASK_TYPE | typeof SHELL_TASK_TYPE;
} & vscode.TaskDefinition &
RustTarget;
export type RustTarget = {
// The command to run, usually `cargo`.
command: string;
// Additional arguments passed to the cargo command.
// Additional arguments passed to the command.
args?: string[];
// The working directory to run the cargo command in.
// The working directory to run the command in.
cwd?: string;
// The shell environment.
env?: { [key: string]: string };
// Override the cargo executable name, such as
// "my_custom_cargo_bin".
overrideCargo?: string;
}
};

class RustTaskProvider implements vscode.TaskProvider {
private readonly config: Config;
Expand All @@ -31,6 +34,10 @@ class RustTaskProvider implements vscode.TaskProvider {
}

async provideTasks(): Promise<vscode.Task[]> {
if (!vscode.workspace.workspaceFolders) {
return [];
}

// 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
Expand All @@ -45,15 +52,23 @@ class RustTaskProvider implements vscode.TaskProvider {
{ command: "run", group: undefined },
];

// FIXME: The server should provide this
const cargo = await toolchain.cargoPath();

const tasks: vscode.Task[] = [];
for (const workspaceTarget of vscode.workspace.workspaceFolders || []) {
for (const workspaceTarget of vscode.workspace.workspaceFolders) {
for (const def of defs) {
const definition = {
command: cargo,
args: [def.command],
};
const exec = await targetToExecution(definition, this.config.cargoRunner);
const vscodeTask = await buildRustTask(
workspaceTarget,
{ type: TASK_TYPE, command: def.command },
{ ...definition, type: CARGO_TASK_TYPE },
`cargo ${def.command}`,
this.config.problemMatcher,
this.config.cargoRunner,
exec,
);
vscodeTask.group = def.group;
tasks.push(vscodeTask);
Expand All @@ -67,16 +82,24 @@ class RustTaskProvider implements vscode.TaskProvider {
// 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 RustTargetDefinition;

if (definition.type === TASK_TYPE) {
if (task.definition.type === CARGO_TASK_TYPE) {
const taskDefinition = task.definition as RustTargetDefinition;
const cargo = await toolchain.cargoPath();
const exec = await targetToExecution(
{
command: cargo,
args: [taskDefinition.command].concat(taskDefinition.args || []),
cwd: taskDefinition.cwd,
env: taskDefinition.env,
},
this.config.cargoRunner,
);
return await buildRustTask(
task.scope,
definition,
taskDefinition,
task.name,
this.config.problemMatcher,
this.config.cargoRunner,
exec,
);
}

Expand All @@ -89,34 +112,31 @@ export async function buildRustTask(
definition: RustTargetDefinition,
name: string,
problemMatcher: string[],
customRunner?: string,
throwOnError: boolean = false,
exec: vscode.ProcessExecution | vscode.ShellExecution,
): Promise<vscode.Task> {
const exec = await cargoToExecution(definition, customRunner, throwOnError);

return new vscode.Task(
definition,
// scope can sometimes be undefined. in these situations we default to the workspace taskscope as
// recommended by the official docs: https://code.visualstudio.com/api/extension-guides/task-provider#task-provider)
scope ?? vscode.TaskScope.Workspace,
name,
TASK_SOURCE,
RUST_TASK_SOURCE,
exec,
problemMatcher,
);
}

async function cargoToExecution(
definition: RustTargetDefinition,
customRunner: string | undefined,
throwOnError: boolean,
export async function targetToExecution(
definition: RustTarget,
customRunner?: string,
throwOnError: boolean = false,
): Promise<vscode.ProcessExecution | vscode.ShellExecution> {
if (customRunner) {
const runnerCommand = `${customRunner}.buildShellExecution`;

try {
const runnerArgs = {
kind: TASK_TYPE,
kind: CARGO_TASK_TYPE,
args: definition.args,
cwd: definition.cwd,
env: definition.env,
Expand All @@ -136,37 +156,14 @@ async function cargoToExecution(
// fallback to default processing
}
}

// this is a cargo task; do Cargo-esque processing
if (definition.type === TASK_TYPE) {
// Check whether we must use a user-defined substitute for cargo.
// Split on spaces to allow overrides like "wrapper cargo".
const cargoCommand = definition.overrideCargo?.split(" ") ?? [definition.command];

const definitionArgs = expectNotUndefined(
definition.args,
"args were not provided via runnables; this is a bug.",
);
const args = [...cargoCommand.slice(1), ...definitionArgs];
const processName = unwrapUndefinable(cargoCommand[0]);

return new vscode.ProcessExecution(processName, args, {
cwd: definition.cwd,
env: definition.env,
});
} else {
// we've been handed a process definition by rust-analyzer, trust all its inputs
// and make a shell execution.
const args = unwrapUndefinable(definition.args);

return new vscode.ProcessExecution(definition.command, args, {
cwd: definition.cwd,
env: definition.env,
});
}
const args = unwrapUndefinable(definition.args);
return new vscode.ProcessExecution(definition.command, args, {
cwd: definition.cwd,
env: definition.env,
});
}

export function activateTaskProvider(config: Config): vscode.Disposable {
const provider = new RustTaskProvider(config);
return vscode.tasks.registerTaskProvider(TASK_TYPE, provider);
return vscode.tasks.registerTaskProvider(CARGO_TASK_TYPE, provider);
}
1 change: 1 addition & 0 deletions editors/code/src/toolchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export async function getRustcId(dir: string): Promise<string> {
}

/** Mirrors `toolchain::cargo()` implementation */
// FIXME: The server should provide this
export function cargoPath(): Promise<string> {
return getPathForExecutable("cargo");
}
Expand Down