Skip to content

Commit

Permalink
Auto merge of #16839 - Wilfred:extension_refactor_for_shell, r=Veykril
Browse files Browse the repository at this point in the history
Refactor extension to support arbitrary shell command runnables

Currently, the extension assumes that all runnables invoke cargo. Arguments are sometimes full CLI arguments, and sometimes arguments passed to a cargo subcommand.

Refactor the extension so that tasks are just a `program` and a list of strings `args`, and rename `CargoTask` to `RustTask` to make it generic.

(This was factored out of #16135 and tidied.)
  • Loading branch information
bors committed Mar 18, 2024
2 parents a71a032 + 4422a90 commit f6e2895
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 38 deletions.
28 changes: 19 additions & 9 deletions editors/code/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from "vscode";
import type * as lc from "vscode-languageclient";
import * as ra from "./lsp_ext";
import * as tasks from "./tasks";
import * as toolchain from "./toolchain";

import type { CtxInit } from "./ctx";
import { makeDebugConfig } from "./debug";
Expand Down Expand Up @@ -111,35 +112,44 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
throw `Unexpected runnable kind: ${runnable.kind}`;
}

const args = createArgs(runnable);
let program: string;
let args = createArgs(runnable);
if (runnable.args.overrideCargo) {
// Split on spaces to allow overrides like "wrapper cargo".
const cargoParts = runnable.args.overrideCargo.split(" ");

const definition: tasks.CargoTaskDefinition = {
program = unwrapUndefinable(cargoParts[0]);
args = [...cargoParts.slice(1), ...args];
} else {
program = await toolchain.cargoPath();
}

const definition: tasks.RustTargetDefinition = {
type: tasks.TASK_TYPE,
command: args[0], // run, test, etc...
args: args.slice(1),
program,
args,
cwd: runnable.args.workspaceRoot || ".",
env: prepareEnv(runnable, config.runnablesExtraEnv),
overrideCargo: runnable.args.overrideCargo,
};

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

cargoTask.presentationOptions.clear = true;
task.presentationOptions.clear = true;
// Sadly, this doesn't prevent focus stealing if the terminal is currently
// hidden, and will become revealed due to task execution.
cargoTask.presentationOptions.focus = false;
task.presentationOptions.focus = false;

return cargoTask;
return task;
}

export function createArgs(runnable: ra.Runnable): string[] {
Expand Down
50 changes: 21 additions & 29 deletions editors/code/src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ import * as vscode from "vscode";
import * as toolchain from "./toolchain";
import type { Config } from "./config";
import { log } from "./util";
import { unwrapUndefinable } from "./undefinable";

// 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[];
export interface RustTargetDefinition extends vscode.TaskDefinition {
program: string;
args: string[];
cwd?: string;
env?: { [key: string]: string };
overrideCargo?: string;
}

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

constructor(config: Config) {
Expand All @@ -39,14 +37,15 @@ class CargoTaskProvider implements vscode.TaskProvider {
{ command: "run", group: undefined },
];

const cargoPath = await toolchain.cargoPath();

const tasks: vscode.Task[] = [];
for (const workspaceTarget of vscode.workspace.workspaceFolders || []) {
for (const def of defs) {
const vscodeTask = await buildCargoTask(
const vscodeTask = await buildRustTask(
workspaceTarget,
{ type: TASK_TYPE, command: def.command },
{ type: TASK_TYPE, program: cargoPath, args: [def.command] },
`cargo ${def.command}`,
[def.command],
this.config.problemMatcher,
this.config.cargoRunner,
);
Expand All @@ -63,15 +62,13 @@ class CargoTaskProvider implements vscode.TaskProvider {
// we need to inform VSCode how to execute that command by creating
// a ShellExecution for it.

const definition = task.definition as CargoTaskDefinition;
const definition = task.definition as RustTargetDefinition;

if (definition.type === TASK_TYPE && definition.command) {
const args = [definition.command].concat(definition.args ?? []);
return await buildCargoTask(
if (definition.type === TASK_TYPE) {
return await buildRustTask(
task.scope,
definition,
task.name,
args,
this.config.problemMatcher,
this.config.cargoRunner,
);
Expand All @@ -81,11 +78,10 @@ class CargoTaskProvider implements vscode.TaskProvider {
}
}

export async function buildCargoTask(
export async function buildRustTask(
scope: vscode.WorkspaceFolder | vscode.TaskScope | undefined,
definition: CargoTaskDefinition,
definition: RustTargetDefinition,
name: string,
args: string[],
problemMatcher: string[],
customRunner?: string,
throwOnError: boolean = false,
Expand All @@ -95,7 +91,12 @@ export async function buildCargoTask(
if (customRunner) {
const runnerCommand = `${customRunner}.buildShellExecution`;
try {
const runnerArgs = { kind: TASK_TYPE, args, cwd: definition.cwd, env: definition.env };
const runnerArgs = {
kind: TASK_TYPE,
args: definition.args,
cwd: definition.cwd,
env: definition.env,
};
const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs);
if (customExec) {
if (customExec instanceof vscode.ShellExecution) {
Expand All @@ -113,16 +114,7 @@ export async function buildCargoTask(
}

if (!exec) {
// Check whether we must use a user-defined substitute for cargo.
// Split on spaces to allow overrides like "wrapper cargo".
const overrideCargo = definition.overrideCargo ?? definition.overrideCargo;
const cargoPath = await toolchain.cargoPath();
const cargoCommand = overrideCargo?.split(" ") ?? [cargoPath];

const fullCommand = [...cargoCommand, ...args];

const processName = unwrapUndefinable(fullCommand[0]);
exec = new vscode.ProcessExecution(processName, fullCommand.slice(1), definition);
exec = new vscode.ProcessExecution(definition.program, definition.args, definition);
}

return new vscode.Task(
Expand All @@ -138,6 +130,6 @@ export async function buildCargoTask(
}

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

0 comments on commit f6e2895

Please sign in to comment.