Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--editors/code/src/toolchain.ts41
-rw-r--r--editors/code/tests/unit/launch_config.test.ts24
2 files changed, 43 insertions, 22 deletions
diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts
index 784ee260d0..6ae365c71c 100644
--- a/editors/code/src/toolchain.ts
+++ b/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 { Env, log, memoizeAsync, unwrapUndefinable } from "./util";
+import { Env, isWindows, log, memoizeAsync, unwrapUndefinable } from "./util";
import type { CargoRunnableArgs } from "./lsp_ext";
interface CompilationArtifact {
@@ -178,20 +178,22 @@ async function getPathForExecutableWithEnv(
executableName: "cargo" | "rustc" | "rustup",
env: Env,
): Promise<string> {
- const envVar = env[executableName.toUpperCase()];
+ const envVar = getEnvVar(env, executableName.toUpperCase());
if (envVar) {
return envVar;
}
- if (await lookupInPath(executableName, env["PATH"] ?? "")) {
+ if (await lookupInPath(executableName, getEnvVar(env, "PATH") ?? "")) {
return executableName;
}
- const cargoHome = getCargoHomeFromPath(env["CARGO_HOME"]);
+ const cargoHome = getCargoHomeFromPath(getEnvVar(env, "CARGO_HOME"));
if (cargoHome) {
- const standardPath = vscode.Uri.joinPath(cargoHome, "bin", executableName);
- if (await isFileAtUri(standardPath)) {
- return standardPath.fsPath;
+ for (const candidate of executableCandidates(executableName)) {
+ const standardPath = vscode.Uri.joinPath(cargoHome, "bin", candidate);
+ if (await isFileAtUri(standardPath)) {
+ return standardPath.fsPath;
+ }
}
}
@@ -211,8 +213,10 @@ const getPathForExecutable = memoizeAsync(
const cargoHome = getCargoHome();
if (cargoHome) {
- const standardPath = vscode.Uri.joinPath(cargoHome, "bin", executableName);
- if (await isFileAtUri(standardPath)) return standardPath.fsPath;
+ for (const candidate of executableCandidates(executableName)) {
+ const standardPath = vscode.Uri.joinPath(cargoHome, "bin", candidate);
+ if (await isFileAtUri(standardPath)) return standardPath.fsPath;
+ }
}
return executableName;
},
@@ -220,8 +224,7 @@ const getPathForExecutable = memoizeAsync(
async function lookupInPath(exec: string, paths: string): Promise<boolean> {
const candidates = paths.split(path.delimiter).flatMap((dirInPath) => {
- const candidate = path.join(dirInPath, exec);
- return os.type() === "Windows_NT" ? [candidate, `${candidate}.exe`] : [candidate];
+ return executableCandidates(exec).map((candidate) => path.join(dirInPath, candidate));
});
for await (const isFile of candidates.map(isFileAtPath)) {
@@ -232,6 +235,22 @@ async function lookupInPath(exec: string, paths: string): Promise<boolean> {
return false;
}
+function executableCandidates(executableName: string): string[] {
+ // Keep the extension-side probe aligned with `crates/toolchain::probe_for_binary()`, which
+ // checks both the bare executable name and the platform suffix such as `.exe` on Windows.
+ // That matters for both PATH lookups and `$CARGO_HOME/bin/<tool>` fallbacks.
+ return isWindows ? [executableName, `${executableName}.exe`] : [executableName];
+}
+
+function getEnvVar(env: Env, name: string): string | undefined {
+ if (!isWindows) {
+ return env[name];
+ }
+
+ const foldedName = name.toLowerCase();
+ return Object.entries(env).find(([key]) => key.toLowerCase() === foldedName)?.[1];
+}
+
function getCargoHome(): vscode.Uri | null {
const envVar = process.env["CARGO_HOME"];
return getCargoHomeFromPath(envVar);
diff --git a/editors/code/tests/unit/launch_config.test.ts b/editors/code/tests/unit/launch_config.test.ts
index 9bba33f8e9..8da62cd241 100644
--- a/editors/code/tests/unit/launch_config.test.ts
+++ b/editors/code/tests/unit/launch_config.test.ts
@@ -3,6 +3,7 @@ import * as os from "os";
import * as path from "path";
import { mkdtemp, mkdir, rm, writeFile } from "fs/promises";
import { Cargo, cargoPath } from "../../src/toolchain";
+import { isWindows, normalizeDriveLetter } from "../../src/util";
import type { Context } from ".";
export async function getTests(ctx: Context) {
@@ -109,13 +110,11 @@ export async function getTests(ctx: Context) {
suite.addTest("resolves cargo from provided PATH", async () => {
const tempDir = await mkdtemp(path.join(os.tmpdir(), "ra-cargo-path-"));
try {
- const cargoBinary = path.join(
- tempDir,
- process.platform === "win32" ? "cargo.exe" : "cargo",
- );
+ const cargoBinary = path.join(tempDir, isWindows ? "cargo.exe" : "cargo");
await writeFile(cargoBinary, "");
- assert.strictEqual(await cargoPath({ PATH: tempDir }), "cargo");
+ const pathKey = isWindows ? "Path" : "PATH";
+ assert.strictEqual(await cargoPath({ [pathKey]: tempDir }), "cargo");
} finally {
await rm(tempDir, { recursive: true, force: true });
}
@@ -126,15 +125,12 @@ export async function getTests(ctx: Context) {
try {
const binDir = path.join(cargoHome, "bin");
await mkdir(binDir);
- const cargoBinary = path.join(
- binDir,
- process.platform === "win32" ? "cargo.exe" : "cargo",
- );
+ const cargoBinary = path.join(binDir, isWindows ? "cargo.exe" : "cargo");
await writeFile(cargoBinary, "");
assert.strictEqual(
- await cargoPath({ PATH: "", CARGO_HOME: cargoHome }),
- cargoBinary,
+ normalizeComparablePath(await cargoPath({ PATH: "", CARGO_HOME: cargoHome })),
+ normalizeComparablePath(cargoBinary),
);
} finally {
await rm(cargoHome, { recursive: true, force: true });
@@ -142,3 +138,9 @@ export async function getTests(ctx: Context) {
});
});
}
+
+function normalizeComparablePath(filePath: string): string {
+ // Windows path comparisons should ignore drive-letter casing because `fsPath`
+ // may normalize it differently while still pointing to the same file.
+ return normalizeDriveLetter(filePath, isWindows);
+}