about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <me@lukaswirth.dev>2025-07-27 14:07:10 +0200
committerLukas Wirth <me@lukaswirth.dev>2025-07-27 14:07:10 +0200
commit71ea00f7a3911990e9e5328446312bc0d792cf5f (patch)
tree966ab5705ed00251d72080db37e1ca2a037980b0
parentd941bc7d306e2a6b232342ff453e8196bc27195e (diff)
downloadrust-71ea00f7a3911990e9e5328446312bc0d792cf5f.tar.gz
rust-71ea00f7a3911990e9e5328446312bc0d792cf5f.zip
fix: Fix runnables extra env not substituting env vars
-rw-r--r--src/tools/rust-analyzer/editors/code/src/config.ts45
-rw-r--r--src/tools/rust-analyzer/editors/code/src/debug.ts24
-rw-r--r--src/tools/rust-analyzer/editors/code/src/run.ts14
-rw-r--r--src/tools/rust-analyzer/editors/code/src/tasks.ts12
-rw-r--r--src/tools/rust-analyzer/editors/code/src/toolchain.ts8
5 files changed, 53 insertions, 50 deletions
diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts
index d2dc740c09b..cadab37f517 100644
--- a/src/tools/rust-analyzer/editors/code/src/config.ts
+++ b/src/tools/rust-analyzer/editors/code/src/config.ts
@@ -8,10 +8,9 @@ import type { Disposable } from "vscode";
 
 export type RunnableEnvCfgItem = {
     mask?: string;
-    env: Record<string, string>;
+    env: { [key: string]: { toString(): string } | null };
     platform?: string | string[];
 };
-export type RunnableEnvCfg = Record<string, string> | RunnableEnvCfgItem[];
 
 type ShowStatusBar = "always" | "never" | { documentSelector: vscode.DocumentSelector };
 
@@ -261,18 +260,9 @@ export class Config {
         return this.get<boolean | undefined>("testExplorer");
     }
 
-    runnablesExtraEnv(label: string): Record<string, string> | undefined {
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        const item = this.get<any>("runnables.extraEnv") ?? this.get<any>("runnableEnv");
-        if (!item) return undefined;
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        const fixRecord = (r: Record<string, any>) => {
-            for (const key in r) {
-                if (typeof r[key] !== "string") {
-                    r[key] = String(r[key]);
-                }
-            }
-        };
+    runnablesExtraEnv(label: string): Env {
+        let extraEnv = this.get<RunnableEnvCfgItem[] | { [key: string]: { toString(): string } | null } | null>("runnables.extraEnv") ?? {};
+        if (!extraEnv) return {};
 
         const platform = process.platform;
         const checkPlatform = (it: RunnableEnvCfgItem) => {
@@ -283,19 +273,24 @@ export class Config {
             return true;
         };
 
-        if (item instanceof Array) {
+        if (extraEnv instanceof Array) {
             const env = {};
-            for (const it of item) {
+            for (const it of extraEnv) {
                 const masked = !it.mask || new RegExp(it.mask).test(label);
                 if (masked && checkPlatform(it)) {
                     Object.assign(env, it.env);
                 }
             }
-            fixRecord(env);
-            return env;
+            extraEnv = env;
         }
-        fixRecord(item);
-        return item;
+        return substituteVariablesInEnv(
+            Object.fromEntries(
+                Object.entries(extraEnv).map(([k, v]) => [
+                    k,
+                    typeof v === "string" ? v : v?.toString(),
+                ]),
+            ),
+        );
     }
 
     get restartServerOnConfigChange() {
@@ -490,11 +485,11 @@ function computeVscodeVar(varName: string): string | null {
             folder === undefined
                 ? "" // no workspace opened
                 : // could use currently opened document to detect the correct
-                  // workspace. However, that would be determined by the document
-                  // user has opened on Editor startup. Could lead to
-                  // unpredictable workspace selection in practice.
-                  // It's better to pick the first one
-                  normalizeDriveLetter(folder.uri.fsPath);
+                // workspace. However, that would be determined by the document
+                // user has opened on Editor startup. Could lead to
+                // unpredictable workspace selection in practice.
+                // It's better to pick the first one
+                normalizeDriveLetter(folder.uri.fsPath);
         return fsPath;
     };
     // https://code.visualstudio.com/docs/editor/variables-reference
diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts
index adb75c23c70..9559fe16f51 100644
--- a/src/tools/rust-analyzer/editors/code/src/debug.ts
+++ b/src/tools/rust-analyzer/editors/code/src/debug.ts
@@ -6,7 +6,7 @@ import type * as ra from "./lsp_ext";
 import { Cargo } from "./toolchain";
 import type { Ctx } from "./ctx";
 import { createTaskFromRunnable, prepareEnv } from "./run";
-import { execute, isCargoRunnableArgs, unwrapUndefinable, log, normalizeDriveLetter } from "./util";
+import { execute, isCargoRunnableArgs, unwrapUndefinable, log, normalizeDriveLetter, Env } from "./util";
 import type { Config } from "./config";
 
 // Here we want to keep track on everything that's currently running
@@ -108,9 +108,9 @@ async function getDebugConfiguration(
 
         await vscode.window.showErrorMessage(
             `Install [CodeLLDB](command:${commandCodeLLDB} "Open CodeLLDB")` +
-                `, [lldb-dap](command:${commandLLDBDap} "Open lldb-dap")` +
-                `, [C/C++](command:${commandCCpp} "Open C/C++") ` +
-                `or [Native Debug](command:${commandNativeDebug} "Open Native Debug") for debugging.`,
+            `, [lldb-dap](command:${commandLLDBDap} "Open lldb-dap")` +
+            `, [C/C++](command:${commandCCpp} "Open C/C++") ` +
+            `or [Native Debug](command:${commandNativeDebug} "Open Native Debug") for debugging.`,
         );
         return;
     }
@@ -124,7 +124,7 @@ async function getDebugConfiguration(
         !isMultiFolderWorkspace || !runnableArgs.workspaceRoot
             ? firstWorkspace
             : workspaceFolders.find((w) => runnableArgs.workspaceRoot?.includes(w.uri.fsPath)) ||
-              firstWorkspace;
+            firstWorkspace;
 
     const workspace = unwrapUndefinable(maybeWorkspace);
     const wsFolder = normalizeDriveLetter(path.normalize(workspace.uri.fsPath));
@@ -207,7 +207,7 @@ type SourceFileMap = {
 };
 
 async function discoverSourceFileMap(
-    env: Record<string, string>,
+    env: Env,
     cwd: string,
 ): Promise<SourceFileMap | undefined> {
     const sysroot = env["RUSTC_TOOLCHAIN"];
@@ -232,7 +232,7 @@ type PropertyFetcher<Config, Input, Key extends keyof Config> = (
 
 type DebugConfigProvider<Type extends string, DebugConfig extends BaseDebugConfig<Type>> = {
     executableProperty: keyof DebugConfig;
-    environmentProperty: PropertyFetcher<DebugConfig, Record<string, string>, keyof DebugConfig>;
+    environmentProperty: PropertyFetcher<DebugConfig, Env, keyof DebugConfig>;
     runnableArgsProperty: PropertyFetcher<DebugConfig, ra.CargoRunnableArgs, keyof DebugConfig>;
     sourceFileMapProperty?: keyof DebugConfig;
     type: Type;
@@ -276,7 +276,7 @@ const knownEngines: {
             "environment",
             Object.entries(env).map((entry) => ({
                 name: entry[0],
-                value: entry[1],
+                value: entry[1] ?? "",
             })),
         ],
         runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [
@@ -306,7 +306,7 @@ const knownEngines: {
 
 async function getDebugExecutable(
     runnableArgs: ra.CargoRunnableArgs,
-    env: Record<string, string>,
+    env: Env,
 ): Promise<string> {
     const cargo = new Cargo(runnableArgs.workspaceRoot || ".", env);
     const executable = await cargo.executableFromArgs(runnableArgs);
@@ -328,7 +328,7 @@ function getDebugConfig(
     runnable: ra.Runnable,
     runnableArgs: ra.CargoRunnableArgs,
     executable: string,
-    env: Record<string, string>,
+    env: Env,
     sourceFileMap?: Record<string, string>,
 ): vscode.DebugConfiguration {
     const {
@@ -380,14 +380,14 @@ type CodeLldbDebugConfig = {
     args: string[];
     sourceMap: Record<string, string> | undefined;
     sourceLanguages: ["rust"];
-    env: Record<string, string>;
+    env: Env;
 } & BaseDebugConfig<"lldb">;
 
 type NativeDebugConfig = {
     target: string;
     // See https://github.com/WebFreak001/code-debug/issues/359
     arguments: string;
-    env: Record<string, string>;
+    env: Env;
     valuesFormatting: "prettyPrinters";
 } & BaseDebugConfig<"gdb">;
 
diff --git a/src/tools/rust-analyzer/editors/code/src/run.ts b/src/tools/rust-analyzer/editors/code/src/run.ts
index 95166c427b2..bed874705d8 100644
--- a/src/tools/rust-analyzer/editors/code/src/run.ts
+++ b/src/tools/rust-analyzer/editors/code/src/run.ts
@@ -7,7 +7,7 @@ import type { CtxInit } from "./ctx";
 import { makeDebugConfig } from "./debug";
 import type { Config } from "./config";
 import type { LanguageClient } from "vscode-languageclient/node";
-import { log, unwrapUndefinable, type RustEditor } from "./util";
+import { Env, log, unwrapUndefinable, type RustEditor } from "./util";
 
 const quickPickButtons = [
     { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@@ -124,9 +124,9 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
 
 export function prepareBaseEnv(
     inheritEnv: boolean,
-    base?: Record<string, string>,
-): Record<string, string> {
-    const env: Record<string, string> = { RUST_BACKTRACE: "short" };
+    base?: Env,
+): Env {
+    const env: Env = { RUST_BACKTRACE: "short" };
     if (inheritEnv) {
         Object.assign(env, process.env);
     }
@@ -138,9 +138,9 @@ export function prepareBaseEnv(
 
 export function prepareEnv(
     inheritEnv: boolean,
-    runnableEnv?: Record<string, string>,
-    runnableEnvCfg?: Record<string, string>,
-): Record<string, string> {
+    runnableEnv?: Env,
+    runnableEnvCfg?: Env,
+): Env {
     const env = prepareBaseEnv(inheritEnv, runnableEnv);
 
     if (runnableEnvCfg) {
diff --git a/src/tools/rust-analyzer/editors/code/src/tasks.ts b/src/tools/rust-analyzer/editors/code/src/tasks.ts
index 730ec6d1e90..417c28f0ee8 100644
--- a/src/tools/rust-analyzer/editors/code/src/tasks.ts
+++ b/src/tools/rust-analyzer/editors/code/src/tasks.ts
@@ -1,6 +1,7 @@
 import * as vscode from "vscode";
 import type { Config } from "./config";
 import * as toolchain from "./toolchain";
+import { Env } 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.
@@ -117,8 +118,8 @@ export async function buildRustTask(
 export async function targetToExecution(
     definition: TaskDefinition,
     options?: {
-        env?: { [key: string]: string };
         cwd?: string;
+        env?: Env;
     },
     cargo?: string,
 ): Promise<vscode.ProcessExecution | vscode.ShellExecution> {
@@ -131,7 +132,14 @@ export async function targetToExecution(
         command = definition.command;
         args = definition.args || [];
     }
-    return new vscode.ProcessExecution(command, args, options);
+    return new vscode.ProcessExecution(command, args, {
+        cwd: options?.cwd,
+        env:
+            Object.fromEntries(
+                Object.entries(options?.env ?? {}).map(([key, value]) => [key, value ?? ""])
+            )
+        ,
+    });
 }
 
 export function activateTaskProvider(config: Config): vscode.Disposable {
diff --git a/src/tools/rust-analyzer/editors/code/src/toolchain.ts b/src/tools/rust-analyzer/editors/code/src/toolchain.ts
index a859ce6ff00..f64dec2ccfb 100644
--- a/src/tools/rust-analyzer/editors/code/src/toolchain.ts
+++ b/src/tools/rust-analyzer/editors/code/src/toolchain.ts
@@ -3,7 +3,7 @@ import * as os from "os";
 import * as path from "path";
 import * as readline from "readline";
 import * as vscode from "vscode";
-import { log, memoizeAsync, unwrapUndefinable } from "./util";
+import { Env, log, memoizeAsync, unwrapUndefinable } from "./util";
 import type { CargoRunnableArgs } from "./lsp_ext";
 
 interface CompilationArtifact {
@@ -37,8 +37,8 @@ interface CompilerMessage {
 export class Cargo {
     constructor(
         readonly rootFolder: string,
-        readonly env: Record<string, string>,
-    ) {}
+        readonly env: Env,
+    ) { }
 
     // Made public for testing purposes
     static artifactSpec(cargoArgs: string[], executableArgs?: string[]): ArtifactSpec {
@@ -156,7 +156,7 @@ export class Cargo {
 
 /** Mirrors `toolchain::cargo()` implementation */
 // FIXME: The server should provide this
-export function cargoPath(env?: Record<string, string>): Promise<string> {
+export function cargoPath(env?: Env): Promise<string> {
     if (env?.["RUSTC_TOOLCHAIN"]) {
         return Promise.resolve("cargo");
     }