Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #17705 - huntc:resolve-ra, r=Veykril
feat: Use oldest rustup rust-analyzer when toolchain override is present Selects a rust-toolchain declared RA based on its date. The earliest (oldest) RA wins and becomes the one that the workspace uses as a whole. In terms of precedence: nightly > stable-with-version > stable With stable-with-version, we invoke the RA with a `--version` arg and attempt to extract a date. Given the same date as a nightly, the nightly RA will win. Fixes #17663
bors 2024-08-02
parent f149dc5 · parent 77e6999 · commit 670a5ab
-rw-r--r--editors/code/src/bootstrap.ts111
-rw-r--r--editors/code/tests/unit/bootstrap.test.ts96
2 files changed, 191 insertions, 16 deletions
diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts
index 42dd0760d6..daead47e94 100644
--- a/editors/code/src/bootstrap.ts
+++ b/editors/code/src/bootstrap.ts
@@ -4,6 +4,7 @@ import type { Config } from "./config";
import { type Env, log } from "./util";
import type { PersistentState } from "./persistent_state";
import { exec, spawnSync } from "child_process";
+import { TextDecoder } from "node:util";
export async function bootstrap(
context: vscode.ExtensionContext,
@@ -50,26 +51,35 @@ async function getServer(
}
return explicitPath;
}
- if (packageJson.releaseTag === null) return "rust-analyzer";
- if (vscode.workspace.workspaceFolders?.length === 1) {
- // otherwise check if there is a toolchain override for the current vscode workspace
- // and if the toolchain of this override has a rust-analyzer component
- // if so, use the rust-analyzer component
- const toolchainTomlExists = await fileExists(
- vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0]!.uri, "rust-toolchain.toml"),
- );
- if (toolchainTomlExists) {
- const res = spawnSync("rustup", ["which", "rust-analyzer"], {
- encoding: "utf8",
- env: { ...process.env },
- cwd: vscode.workspace.workspaceFolders[0]!.uri.fsPath,
- });
- if (!res.error && res.status === 0) {
- return res.stdout.trim();
+ let toolchainServerPath = undefined;
+ if (vscode.workspace.workspaceFolders) {
+ for (const workspaceFolder of vscode.workspace.workspaceFolders) {
+ // otherwise check if there is a toolchain override for the current vscode workspace
+ // and if the toolchain of this override has a rust-analyzer component
+ // if so, use the rust-analyzer component
+ const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml");
+ if (await hasToolchainFileWithRaDeclared(toolchainUri)) {
+ const res = spawnSync("rustup", ["which", "rust-analyzer"], {
+ encoding: "utf8",
+ env: { ...process.env },
+ cwd: workspaceFolder.uri.fsPath,
+ });
+ if (!res.error && res.status === 0) {
+ toolchainServerPath = earliestToolchainPath(
+ toolchainServerPath,
+ res.stdout.trim(),
+ raVersionResolver,
+ );
+ }
}
}
}
+ if (toolchainServerPath) {
+ return toolchainServerPath;
+ }
+
+ if (packageJson.releaseTag === null) return "rust-analyzer";
// finally, use the bundled one
const ext = process.platform === "win32" ? ".exe" : "";
@@ -102,6 +112,57 @@ async function getServer(
return undefined;
}
+// Given a path to a rust-analyzer executable, resolve its version and return it.
+function raVersionResolver(path: string): string | undefined {
+ const res = spawnSync(path, ["--version"], {
+ encoding: "utf8",
+ });
+ if (!res.error && res.status === 0) {
+ return res.stdout;
+ } else {
+ return undefined;
+ }
+}
+
+// Given a path to two rust-analyzer executables, return the earliest one by date.
+function earliestToolchainPath(
+ path0: string | undefined,
+ path1: string,
+ raVersionResolver: (path: string) => string | undefined,
+): string {
+ if (path0) {
+ if (orderFromPath(path0, raVersionResolver) < orderFromPath(path1, raVersionResolver)) {
+ return path0;
+ } else {
+ return path1;
+ }
+ } else {
+ return path1;
+ }
+}
+
+// Further to extracting a date for comparison, determine the order of a toolchain as follows:
+// Highest - nightly
+// Medium - versioned
+// Lowest - stable
+// Example paths:
+// nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer
+// versioned - /Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer
+// stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer
+function orderFromPath(
+ path: string,
+ raVersionResolver: (path: string) => string | undefined,
+): string {
+ const raVersion = raVersionResolver(path);
+ const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/);
+ if (raDate?.length === 2) {
+ const precedence = path.includes("nightly-") ? "0" : "1";
+ return "0-" + raDate[1] + "/" + precedence;
+ } else {
+ return "2";
+ }
+}
+
async function fileExists(uri: vscode.Uri) {
return await vscode.workspace.fs.stat(uri).then(
() => true,
@@ -109,6 +170,19 @@ async function fileExists(uri: vscode.Uri) {
);
}
+async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise<boolean> {
+ try {
+ const toolchainFileContents = new TextDecoder().decode(
+ await vscode.workspace.fs.readFile(uri),
+ );
+ return (
+ toolchainFileContents.match(/components\s*=\s*\[.*\"rust-analyzer\".*\]/g)?.length === 1
+ );
+ } catch (e) {
+ return false;
+ }
+}
+
export function isValidExecutable(path: string, extraEnv: Env): boolean {
log.debug("Checking availability of a binary at", path);
@@ -207,3 +281,8 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
},
);
}
+
+export const _private = {
+ earliestToolchainPath,
+ orderFromPath,
+};
diff --git a/editors/code/tests/unit/bootstrap.test.ts b/editors/code/tests/unit/bootstrap.test.ts
new file mode 100644
index 0000000000..8aeb72180a
--- /dev/null
+++ b/editors/code/tests/unit/bootstrap.test.ts
@@ -0,0 +1,96 @@
+import * as assert from "assert";
+import { _private } from "../../src/bootstrap";
+import type { Context } from ".";
+
+export async function getTests(ctx: Context) {
+ await ctx.suite("Bootstrap/Select toolchain RA", (suite) => {
+ suite.addTest("Order of nightly RA", async () => {
+ assert.deepStrictEqual(
+ _private.orderFromPath(
+ "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
+ function (path: string) {
+ assert.deepStrictEqual(
+ path,
+ "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
+ );
+ return "rust-analyzer 1.67.0-nightly (b7bc90fe 2022-11-21)";
+ },
+ ),
+ "0-2022-11-21/0",
+ );
+ });
+
+ suite.addTest("Order of versioned RA", async () => {
+ assert.deepStrictEqual(
+ _private.orderFromPath(
+ "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
+ function (path: string) {
+ assert.deepStrictEqual(
+ path,
+ "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
+ );
+ return "rust-analyzer 1.72.1 (d5c2e9c3 2023-09-13)";
+ },
+ ),
+ "0-2023-09-13/1",
+ );
+ });
+
+ suite.addTest("Order of versioned RA when unable to obtain version date", async () => {
+ assert.deepStrictEqual(
+ _private.orderFromPath(
+ "/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
+ function () {
+ return "rust-analyzer 1.72.1";
+ },
+ ),
+ "2",
+ );
+ });
+
+ suite.addTest("Order of stable RA", async () => {
+ assert.deepStrictEqual(
+ _private.orderFromPath(
+ "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
+ function (path: string) {
+ assert.deepStrictEqual(
+ path,
+ "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
+ );
+ return "rust-analyzer 1.79.0 (129f3b99 2024-06-10)";
+ },
+ ),
+ "0-2024-06-10/1",
+ );
+ });
+
+ suite.addTest("Order with invalid path to RA", async () => {
+ assert.deepStrictEqual(
+ _private.orderFromPath("some-weird-path", function () {
+ return undefined;
+ }),
+ "2",
+ );
+ });
+
+ suite.addTest("Earliest RA between nightly and stable", async () => {
+ assert.deepStrictEqual(
+ _private.earliestToolchainPath(
+ "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
+ "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
+ function (path: string) {
+ if (
+ path ===
+ "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer"
+ ) {
+ return "rust-analyzer 1.67.0-nightly (b7bc90fe 2022-11-21)";
+ } else {
+ return "rust-analyzer 1.79.0 (129f3b99 2024-06-10)";
+ }
+ },
+ ),
+ "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
+ );
+ });
+ });
+}