Merge pull request #20313 from Veykril/push-qmorsnlvwlrr

fix: Fix runnables extra env not substituting env vars
This commit is contained in:
Lukas Wirth 2025-07-28 14:29:27 +00:00 committed by GitHub
commit 5c479edc49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 50 additions and 50 deletions

View File

@ -8,10 +8,9 @@ import type { Disposable } from "vscode";
export type RunnableEnvCfgItem = { export type RunnableEnvCfgItem = {
mask?: string; mask?: string;
env: Record<string, string>; env: { [key: string]: { toString(): string } | null };
platform?: string | string[]; platform?: string | string[];
}; };
export type RunnableEnvCfg = Record<string, string> | RunnableEnvCfgItem[];
type ShowStatusBar = "always" | "never" | { documentSelector: vscode.DocumentSelector }; type ShowStatusBar = "always" | "never" | { documentSelector: vscode.DocumentSelector };
@ -261,18 +260,13 @@ export class Config {
return this.get<boolean | undefined>("testExplorer"); return this.get<boolean | undefined>("testExplorer");
} }
runnablesExtraEnv(label: string): Record<string, string> | undefined { runnablesExtraEnv(label: string): Env {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const serverEnv = this.serverExtraEnv;
const item = this.get<any>("runnables.extraEnv") ?? this.get<any>("runnableEnv"); let extraEnv =
if (!item) return undefined; this.get<
// eslint-disable-next-line @typescript-eslint/no-explicit-any RunnableEnvCfgItem[] | { [key: string]: { toString(): string } | null } | null
const fixRecord = (r: Record<string, any>) => { >("runnables.extraEnv") ?? {};
for (const key in r) { if (!extraEnv) return serverEnv;
if (typeof r[key] !== "string") {
r[key] = String(r[key]);
}
}
};
const platform = process.platform; const platform = process.platform;
const checkPlatform = (it: RunnableEnvCfgItem) => { const checkPlatform = (it: RunnableEnvCfgItem) => {
@ -283,19 +277,25 @@ export class Config {
return true; return true;
}; };
if (item instanceof Array) { if (extraEnv instanceof Array) {
const env = {}; const env = {};
for (const it of item) { for (const it of extraEnv) {
const masked = !it.mask || new RegExp(it.mask).test(label); const masked = !it.mask || new RegExp(it.mask).test(label);
if (masked && checkPlatform(it)) { if (masked && checkPlatform(it)) {
Object.assign(env, it.env); Object.assign(env, it.env);
} }
} }
fixRecord(env); extraEnv = env;
return env;
} }
fixRecord(item); const runnableExtraEnv = substituteVariablesInEnv(
return item; Object.fromEntries(
Object.entries(extraEnv).map(([k, v]) => [
k,
typeof v === "string" ? v : v?.toString(),
]),
),
);
return { ...runnableExtraEnv, ...serverEnv };
} }
get restartServerOnConfigChange() { get restartServerOnConfigChange() {

View File

@ -6,7 +6,14 @@ import type * as ra from "./lsp_ext";
import { Cargo } from "./toolchain"; import { Cargo } from "./toolchain";
import type { Ctx } from "./ctx"; import type { Ctx } from "./ctx";
import { createTaskFromRunnable, prepareEnv } from "./run"; 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"; import type { Config } from "./config";
// Here we want to keep track on everything that's currently running // Here we want to keep track on everything that's currently running
@ -206,10 +213,7 @@ type SourceFileMap = {
destination: string; destination: string;
}; };
async function discoverSourceFileMap( async function discoverSourceFileMap(env: Env, cwd: string): Promise<SourceFileMap | undefined> {
env: Record<string, string>,
cwd: string,
): Promise<SourceFileMap | undefined> {
const sysroot = env["RUSTC_TOOLCHAIN"]; const sysroot = env["RUSTC_TOOLCHAIN"];
if (sysroot) { if (sysroot) {
// let's try to use the default toolchain // let's try to use the default toolchain
@ -232,7 +236,7 @@ type PropertyFetcher<Config, Input, Key extends keyof Config> = (
type DebugConfigProvider<Type extends string, DebugConfig extends BaseDebugConfig<Type>> = { type DebugConfigProvider<Type extends string, DebugConfig extends BaseDebugConfig<Type>> = {
executableProperty: keyof DebugConfig; executableProperty: keyof DebugConfig;
environmentProperty: PropertyFetcher<DebugConfig, Record<string, string>, keyof DebugConfig>; environmentProperty: PropertyFetcher<DebugConfig, Env, keyof DebugConfig>;
runnableArgsProperty: PropertyFetcher<DebugConfig, ra.CargoRunnableArgs, keyof DebugConfig>; runnableArgsProperty: PropertyFetcher<DebugConfig, ra.CargoRunnableArgs, keyof DebugConfig>;
sourceFileMapProperty?: keyof DebugConfig; sourceFileMapProperty?: keyof DebugConfig;
type: Type; type: Type;
@ -276,7 +280,7 @@ const knownEngines: {
"environment", "environment",
Object.entries(env).map((entry) => ({ Object.entries(env).map((entry) => ({
name: entry[0], name: entry[0],
value: entry[1], value: entry[1] ?? "",
})), })),
], ],
runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [ runnableArgsProperty: (runnableArgs: ra.CargoRunnableArgs) => [
@ -304,10 +308,7 @@ const knownEngines: {
}, },
}; };
async function getDebugExecutable( async function getDebugExecutable(runnableArgs: ra.CargoRunnableArgs, env: Env): Promise<string> {
runnableArgs: ra.CargoRunnableArgs,
env: Record<string, string>,
): Promise<string> {
const cargo = new Cargo(runnableArgs.workspaceRoot || ".", env); const cargo = new Cargo(runnableArgs.workspaceRoot || ".", env);
const executable = await cargo.executableFromArgs(runnableArgs); const executable = await cargo.executableFromArgs(runnableArgs);
@ -328,7 +329,7 @@ function getDebugConfig(
runnable: ra.Runnable, runnable: ra.Runnable,
runnableArgs: ra.CargoRunnableArgs, runnableArgs: ra.CargoRunnableArgs,
executable: string, executable: string,
env: Record<string, string>, env: Env,
sourceFileMap?: Record<string, string>, sourceFileMap?: Record<string, string>,
): vscode.DebugConfiguration { ): vscode.DebugConfiguration {
const { const {
@ -380,14 +381,14 @@ type CodeLldbDebugConfig = {
args: string[]; args: string[];
sourceMap: Record<string, string> | undefined; sourceMap: Record<string, string> | undefined;
sourceLanguages: ["rust"]; sourceLanguages: ["rust"];
env: Record<string, string>; env: Env;
} & BaseDebugConfig<"lldb">; } & BaseDebugConfig<"lldb">;
type NativeDebugConfig = { type NativeDebugConfig = {
target: string; target: string;
// See https://github.com/WebFreak001/code-debug/issues/359 // See https://github.com/WebFreak001/code-debug/issues/359
arguments: string; arguments: string;
env: Record<string, string>; env: Env;
valuesFormatting: "prettyPrinters"; valuesFormatting: "prettyPrinters";
} & BaseDebugConfig<"gdb">; } & BaseDebugConfig<"gdb">;

View File

@ -7,7 +7,7 @@ import type { CtxInit } from "./ctx";
import { makeDebugConfig } from "./debug"; import { makeDebugConfig } from "./debug";
import type { Config } from "./config"; import type { Config } from "./config";
import type { LanguageClient } from "vscode-languageclient/node"; import type { LanguageClient } from "vscode-languageclient/node";
import { log, unwrapUndefinable, type RustEditor } from "./util"; import { Env, log, unwrapUndefinable, type RustEditor } from "./util";
const quickPickButtons = [ const quickPickButtons = [
{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@ -122,11 +122,8 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
} }
} }
export function prepareBaseEnv( export function prepareBaseEnv(inheritEnv: boolean, base?: Env): Env {
inheritEnv: boolean, const env: Env = { RUST_BACKTRACE: "short" };
base?: Record<string, string>,
): Record<string, string> {
const env: Record<string, string> = { RUST_BACKTRACE: "short" };
if (inheritEnv) { if (inheritEnv) {
Object.assign(env, process.env); Object.assign(env, process.env);
} }
@ -136,11 +133,7 @@ export function prepareBaseEnv(
return env; return env;
} }
export function prepareEnv( export function prepareEnv(inheritEnv: boolean, runnableEnv?: Env, runnableEnvCfg?: Env): Env {
inheritEnv: boolean,
runnableEnv?: Record<string, string>,
runnableEnvCfg?: Record<string, string>,
): Record<string, string> {
const env = prepareBaseEnv(inheritEnv, runnableEnv); const env = prepareBaseEnv(inheritEnv, runnableEnv);
if (runnableEnvCfg) { if (runnableEnvCfg) {

View File

@ -1,6 +1,7 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import type { Config } from "./config"; import type { Config } from "./config";
import * as toolchain from "./toolchain"; import * as toolchain from "./toolchain";
import { Env } from "./util";
// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // 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. // our configuration should be compatible with it so use the same key.
@ -117,8 +118,8 @@ export async function buildRustTask(
export async function targetToExecution( export async function targetToExecution(
definition: TaskDefinition, definition: TaskDefinition,
options?: { options?: {
env?: { [key: string]: string };
cwd?: string; cwd?: string;
env?: Env;
}, },
cargo?: string, cargo?: string,
): Promise<vscode.ProcessExecution | vscode.ShellExecution> { ): Promise<vscode.ProcessExecution | vscode.ShellExecution> {
@ -131,7 +132,12 @@ export async function targetToExecution(
command = definition.command; command = definition.command;
args = definition.args || []; 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 { export function activateTaskProvider(config: Config): vscode.Disposable {

View File

@ -3,7 +3,7 @@ import * as os from "os";
import * as path from "path"; import * as path from "path";
import * as readline from "readline"; import * as readline from "readline";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { log, memoizeAsync, unwrapUndefinable } from "./util"; import { Env, log, memoizeAsync, unwrapUndefinable } from "./util";
import type { CargoRunnableArgs } from "./lsp_ext"; import type { CargoRunnableArgs } from "./lsp_ext";
interface CompilationArtifact { interface CompilationArtifact {
@ -37,7 +37,7 @@ interface CompilerMessage {
export class Cargo { export class Cargo {
constructor( constructor(
readonly rootFolder: string, readonly rootFolder: string,
readonly env: Record<string, string>, readonly env: Env,
) {} ) {}
// Made public for testing purposes // Made public for testing purposes
@ -156,7 +156,7 @@ export class Cargo {
/** Mirrors `toolchain::cargo()` implementation */ /** Mirrors `toolchain::cargo()` implementation */
// FIXME: The server should provide this // 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"]) { if (env?.["RUSTC_TOOLCHAIN"]) {
return Promise.resolve("cargo"); return Promise.resolve("cargo");
} }