diff options
| author | bors <bors@rust-lang.org> | 2024-06-11 16:13:10 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2024-06-11 16:13:10 +0000 |
| commit | a97aef8466688d0d14fea1c311fa8db24173b5e1 (patch) | |
| tree | c77f57d9e5b444fd900fd97a6bcbadf10b945560 /src/tools/rust-analyzer/editors/code | |
| parent | 5e1ab70dffc12895e984a3a1f87e6794d14c31f0 (diff) | |
| parent | 1e9e86c655884e87e136c867818269b7d7a0dffa (diff) | |
Auto merge of #16840 - Wilfred:shell_runnable, r=Veykril
Allow rust-project.json to include arbitrary shell commands for runnables
This is a follow-up on #16135, resolving the feedback raised :)
Allow rust-project.json to include shell runnables, of the form:
```
{
"build_info": {
"label": "//project/foo:my-crate",
"target_kind": "bin",
"shell_runnables": [
{
"kind": "run",
"program": "buck2",
"args": ["run", "//project/foo:my-crate"]
},
{
"kind": "test_one",
"program": "test_runner",
"args": ["--name=$$TEST_NAME$$"]
}
]
}
}
```
If these runnable configs are present for the current crate in rust-project.json, offer them as runnables in VS Code.
This PR required some boring changes to APIs that previously only handled cargo situations. I've split out these changes as commits labelled 'refactor', so it's easy to see the interesting changes.
Diffstat (limited to 'src/tools/rust-analyzer/editors/code')
7 files changed, 138 insertions, 73 deletions
diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts index 9f4930c94a2..5cec2c61a5b 100644 --- a/src/tools/rust-analyzer/editors/code/src/commands.ts +++ b/src/tools/rust-analyzer/editors/code/src/commands.ts @@ -9,10 +9,11 @@ import { applySnippetTextEdits, type SnippetTextDocumentEdit, } from "./snippets"; -import { type RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run"; +import { type RunnableQuickPick, selectRunnable, createTask, createCargoArgs } from "./run"; import { AstInspector } from "./ast_inspector"; import { isRustDocument, + isCargoRunnableArgs, isCargoTomlDocument, sleep, isRustEditor, @@ -1154,8 +1155,8 @@ export function copyRunCommandLine(ctx: CtxInit) { let prevRunnable: RunnableQuickPick | undefined; return async () => { const item = await selectRunnable(ctx, prevRunnable); - if (!item) return; - const args = createArgs(item.runnable); + if (!item || !isCargoRunnableArgs(item.runnable.args)) return; + const args = createCargoArgs(item.runnable.args); const commandLine = ["cargo", ...args].join(" "); await vscode.env.clipboard.writeText(commandLine); await vscode.window.showInformationMessage("Cargo invocation copied to the clipboard."); diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 4b96e4d5c8e..eef2f6f4ee2 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -7,10 +7,12 @@ import { Cargo, getRustcId, getSysroot } from "./toolchain"; import type { Ctx } from "./ctx"; import { prepareEnv } from "./run"; import { unwrapUndefinable } from "./undefinable"; +import { isCargoRunnableArgs } from "./util"; const debugOutput = vscode.window.createOutputChannel("Debug"); type DebugConfigProvider = ( - config: ra.Runnable, + runnable: ra.Runnable, + runnableArgs: ra.CargoRunnableArgs, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>, @@ -76,6 +78,11 @@ async function getDebugConfiguration( ctx: Ctx, runnable: ra.Runnable, ): Promise<vscode.DebugConfiguration | undefined> { + if (!isCargoRunnableArgs(runnable.args)) { + return; + } + const runnableArgs: ra.CargoRunnableArgs = runnable.args; + const editor = ctx.activeRustEditor; if (!editor) return; @@ -119,9 +126,9 @@ async function getDebugConfiguration( const isMultiFolderWorkspace = workspaceFolders.length > 1; const firstWorkspace = workspaceFolders[0]; const maybeWorkspace = - !isMultiFolderWorkspace || !runnable.args.workspaceRoot + !isMultiFolderWorkspace || !runnableArgs.workspaceRoot ? firstWorkspace - : workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || + : workspaceFolders.find((w) => runnableArgs.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace; const workspace = unwrapUndefinable(maybeWorkspace); @@ -132,8 +139,8 @@ async function getDebugConfiguration( return path.normalize(p).replace(wsFolder, "${workspaceFolder" + workspaceQualifier + "}"); } - const env = prepareEnv(runnable, ctx.config.runnablesExtraEnv); - const executable = await getDebugExecutable(runnable, env); + const env = prepareEnv(runnable.label, runnableArgs, ctx.config.runnablesExtraEnv); + const executable = await getDebugExecutable(runnableArgs, env); let sourceFileMap = debugOptions.sourceFileMap; if (sourceFileMap === "auto") { // let's try to use the default toolchain @@ -147,7 +154,7 @@ async function getDebugConfiguration( } const provider = unwrapUndefinable(knownEngines[debugEngine.id]); - const debugConfig = provider(runnable, simplifyPath(executable), env, sourceFileMap); + const debugConfig = provider(runnable, runnableArgs, simplifyPath(executable), env); if (debugConfig.type in debugOptions.engineSettings) { const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; for (var key in settingsMap) { @@ -170,11 +177,11 @@ async function getDebugConfiguration( } async function getDebugExecutable( - runnable: ra.Runnable, + runnableArgs: ra.CargoRunnableArgs, env: Record<string, string>, ): Promise<string> { - const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env); - const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); + const cargo = new Cargo(runnableArgs.workspaceRoot || ".", debugOutput, env); + const executable = await cargo.executableFromArgs(runnableArgs.cargoArgs); // if we are here, there were no compilation errors. return executable; @@ -182,6 +189,7 @@ async function getDebugExecutable( function getCCppDebugConfig( runnable: ra.Runnable, + runnableArgs: ra.CargoRunnableArgs, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>, @@ -191,8 +199,8 @@ function getCCppDebugConfig( request: "launch", name: runnable.label, program: executable, - args: runnable.args.executableArgs, - cwd: runnable.args.cwd || runnable.args.workspaceRoot || ".", + args: runnableArgs.executableArgs, + cwd: runnable.args.cwd || runnableArgs.workspaceRoot || ".", sourceFileMap, environment: Object.entries(env).map((entry) => ({ name: entry[0], @@ -207,6 +215,7 @@ function getCCppDebugConfig( function getCodeLldbDebugConfig( runnable: ra.Runnable, + runnableArgs: ra.CargoRunnableArgs, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>, @@ -216,8 +225,8 @@ function getCodeLldbDebugConfig( request: "launch", name: runnable.label, program: executable, - args: runnable.args.executableArgs, - cwd: runnable.args.cwd || runnable.args.workspaceRoot || ".", + args: runnableArgs.executableArgs, + cwd: runnable.args.cwd || runnableArgs.workspaceRoot || ".", sourceMap: sourceFileMap, sourceLanguages: ["rust"], env, @@ -226,6 +235,7 @@ function getCodeLldbDebugConfig( function getNativeDebugConfig( runnable: ra.Runnable, + runnableArgs: ra.CargoRunnableArgs, executable: string, env: Record<string, string>, _sourceFileMap?: Record<string, string>, @@ -236,8 +246,8 @@ function getNativeDebugConfig( name: runnable.label, target: executable, // See https://github.com/WebFreak001/code-debug/issues/359 - arguments: quote(runnable.args.executableArgs), - cwd: runnable.args.cwd || runnable.args.workspaceRoot || ".", + arguments: quote(runnableArgs.executableArgs), + cwd: runnable.args.cwd || runnableArgs.workspaceRoot || ".", env, valuesFormatting: "prettyPrinters", }; diff --git a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts index 8e48aeef158..2462f06f18b 100644 --- a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts +++ b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts @@ -223,17 +223,27 @@ export type OpenCargoTomlParams = { export type Runnable = { label: string; location?: lc.LocationLink; - kind: "cargo"; - args: { - workspaceRoot?: string; - cwd?: string; - cargoArgs: string[]; - cargoExtraArgs: string[]; - executableArgs: string[]; - expectTest?: boolean; - overrideCargo?: string; - }; + kind: "cargo" | "shell"; + args: CargoRunnableArgs | ShellRunnableArgs; }; + +export type ShellRunnableArgs = { + kind: string; + program: string; + args: string[]; + cwd: string; +}; + +export type CargoRunnableArgs = { + workspaceRoot?: string; + cargoArgs: string[]; + cwd: string; + cargoExtraArgs: string[]; + executableArgs: string[]; + expectTest?: boolean; + overrideCargo?: string; +}; + export type RunnablesParams = { textDocument: lc.TextDocumentIdentifier; position: lc.Position | null; diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts index 4470689cd8c..52117a442a1 100644 --- a/src/tools/rust-analyzer/editors/code/src/run.ts +++ b/src/tools/rust-analyzer/editors/code/src/run.ts @@ -9,6 +9,7 @@ import type { Config, RunnableEnvCfg, RunnableEnvCfgItem } from "./config"; import { unwrapUndefinable } from "./undefinable"; import type { LanguageClient } from "vscode-languageclient/node"; import type { RustEditor } from "./util"; +import * as toolchain from "./toolchain"; const quickPickButtons = [ { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, @@ -66,17 +67,23 @@ export class RunnableQuickPick implements vscode.QuickPickItem { } } +export function prepareBaseEnv(): Record<string, string> { + const env: Record<string, string> = { RUST_BACKTRACE: "short" }; + Object.assign(env, process.env as { [key: string]: string }); + return env; +} + export function prepareEnv( - runnable: ra.Runnable, + label: string, + runnableArgs: ra.CargoRunnableArgs, runnableEnvCfg: RunnableEnvCfg, ): Record<string, string> { - const env: Record<string, string> = { RUST_BACKTRACE: "short" }; + const env = prepareBaseEnv(); - if (runnable.args.expectTest) { + if (runnableArgs.expectTest) { env["UPDATE_EXPECT"] = "1"; } - Object.assign(env, process.env as { [key: string]: string }); const platform = process.platform; const checkPlatform = (it: RunnableEnvCfgItem) => { @@ -90,7 +97,7 @@ export function prepareEnv( if (runnableEnvCfg) { if (Array.isArray(runnableEnvCfg)) { for (const it of runnableEnvCfg) { - const masked = !it.mask || new RegExp(it.mask).test(runnable.label); + const masked = !it.mask || new RegExp(it.mask).test(label); if (masked && checkPlatform(it)) { Object.assign(env, it.env); } @@ -104,24 +111,41 @@ export function prepareEnv( } 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 definition: tasks.RustTargetDefinition; + if (runnable.kind === "cargo") { + const runnableArgs = runnable.args as ra.CargoRunnableArgs; + let args = createCargoArgs(runnableArgs); + + let program: string; + if (runnableArgs.overrideCargo) { + // Split on spaces to allow overrides like "wrapper cargo". + const cargoParts = runnableArgs.overrideCargo.split(" "); + + program = unwrapUndefinable(cargoParts[0]); + args = [...cargoParts.slice(1), ...args]; + } else { + program = await toolchain.cargoPath(); + } - throw `Unexpected runnable kind: ${runnable.kind}`; + definition = { + type: tasks.TASK_TYPE, + command: program, + args, + cwd: runnableArgs.workspaceRoot || ".", + env: prepareEnv(runnable.label, runnableArgs, config.runnablesExtraEnv), + }; + } else { + const runnableArgs = runnable.args as ra.ShellRunnableArgs; + + definition = { + type: "shell", + command: runnableArgs.program, + args: runnableArgs.args, + cwd: runnableArgs.cwd, + env: prepareBaseEnv(), + }; } - const args = createArgs(runnable); - - const definition: tasks.CargoTaskDefinition = { - type: tasks.TASK_TYPE, - command: unwrapUndefinable(args[0]), // run, test, etc... - args: args.slice(1), - 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 task = await tasks.buildRustTask( @@ -141,13 +165,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise return task; } -export function createArgs(runnable: ra.Runnable): string[] { - const args = [...runnable.args.cargoArgs]; // should be a copy! - if (runnable.args.cargoExtraArgs) { - args.push(...runnable.args.cargoExtraArgs); // Append user-specified cargo options. +export function createCargoArgs(runnableArgs: ra.CargoRunnableArgs): string[] { + const args = [...runnableArgs.cargoArgs]; // should be a copy! + if (runnableArgs.cargoExtraArgs) { + args.push(...runnableArgs.cargoExtraArgs); // Append user-specified cargo options. } - if (runnable.args.executableArgs.length > 0) { - args.push("--", ...runnable.args.executableArgs); + if (runnableArgs.executableArgs.length > 0) { + args.push("--", ...runnableArgs.executableArgs); } return args; } diff --git a/src/tools/rust-analyzer/editors/code/src/tasks.ts b/src/tools/rust-analyzer/editors/code/src/tasks.ts index 2b3abc5d65f..c28a919231b 100644 --- a/src/tools/rust-analyzer/editors/code/src/tasks.ts +++ b/src/tools/rust-analyzer/editors/code/src/tasks.ts @@ -10,7 +10,7 @@ export const TASK_TYPE = "cargo"; export const TASK_SOURCE = "rust"; -export interface CargoTaskDefinition extends vscode.TaskDefinition { +export interface RustTargetDefinition extends vscode.TaskDefinition { // The cargo command, such as "run" or "check". command: string; // Additional arguments passed to the cargo command. @@ -69,7 +69,7 @@ class RustTaskProvider 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) { return await buildRustTask( @@ -87,7 +87,7 @@ class RustTaskProvider implements vscode.TaskProvider { export async function buildRustTask( scope: vscode.WorkspaceFolder | vscode.TaskScope | undefined, - definition: CargoTaskDefinition, + definition: RustTargetDefinition, name: string, problemMatcher: string[], customRunner?: string, @@ -108,7 +108,7 @@ export async function buildRustTask( } async function cargoToExecution( - definition: CargoTaskDefinition, + definition: RustTargetDefinition, customRunner: string | undefined, throwOnError: boolean, ): Promise<vscode.ProcessExecution | vscode.ShellExecution> { @@ -138,20 +138,31 @@ async function cargoToExecution( } } - // Check whether we must use a user-defined substitute for cargo. - // Split on spaces to allow overrides like "wrapper cargo". - const cargoPath = await toolchain.cargoPath(); - const cargoCommand = definition.overrideCargo?.split(" ") ?? [cargoPath]; - - const args = [definition.command].concat(definition.args ?? []); - const fullCommand = [...cargoCommand, ...args]; - - const processName = unwrapUndefinable(fullCommand[0]); - - return new vscode.ProcessExecution(processName, fullCommand.slice(1), { - cwd: definition.cwd, - env: definition.env, - }); + // 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 cargoPath = await toolchain.cargoPath(); + const cargoCommand = definition.overrideCargo?.split(" ") ?? [cargoPath]; + + const args = [definition.command].concat(definition.args ?? []); + const fullCommand = [...cargoCommand, ...args]; + const processName = unwrapUndefinable(fullCommand[0]); + + return new vscode.ProcessExecution(processName, fullCommand.slice(1), { + 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, + }); + } } export function activateTaskProvider(config: Config): vscode.Disposable { diff --git a/src/tools/rust-analyzer/editors/code/src/util.ts b/src/tools/rust-analyzer/editors/code/src/util.ts index 51f921a2962..868cb2b7807 100644 --- a/src/tools/rust-analyzer/editors/code/src/util.ts +++ b/src/tools/rust-analyzer/editors/code/src/util.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { strict as nativeAssert } from "assert"; import { exec, type ExecOptions, spawnSync } from "child_process"; import { inspect } from "util"; +import type { CargoRunnableArgs, ShellRunnableArgs } from "./lsp_ext"; import type { Env } from "./client"; export function assert(condition: boolean, explanation: string): asserts condition { @@ -77,6 +78,12 @@ export function isCargoTomlDocument(document: vscode.TextDocument): document is return document.uri.scheme === "file" && document.fileName.endsWith("Cargo.toml"); } +export function isCargoRunnableArgs( + args: CargoRunnableArgs | ShellRunnableArgs, +): args is CargoRunnableArgs { + return (args as CargoRunnableArgs).executableArgs !== undefined; +} + export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { return isRustDocument(editor.document); } diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts index b1407ce0193..21bdaf5384d 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/runnable_env.test.ts @@ -10,6 +10,7 @@ function makeRunnable(label: string): ra.Runnable { kind: "cargo", args: { cargoArgs: [], + cwd: ".", executableArgs: [], cargoExtraArgs: [], }, @@ -18,7 +19,8 @@ function makeRunnable(label: string): ra.Runnable { function fakePrepareEnv(runnableName: string, config: RunnableEnvCfg): Record<string, string> { const runnable = makeRunnable(runnableName); - return prepareEnv(runnable, config); + const runnableArgs = runnable.args as ra.CargoRunnableArgs; + return prepareEnv(runnable.label, runnableArgs, config); } export async function getTests(ctx: Context) { |
