about summary refs log tree commit diff
path: root/src/tools/rust-analyzer
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-08-02 06:50:43 +0000
committerbors <bors@rust-lang.org>2024-08-02 06:50:43 +0000
commitd646f7b258766d38a67422f05dfe4686851dd245 (patch)
tree109cecdfddf55a54233e1315d615a30b10c86248 /src/tools/rust-analyzer
parent61ebcf631b3c3267f6166fc97917da7c30d4d846 (diff)
parent8ca9034d1f01e7e9e4d7d7c347a8632aae73c58a (diff)
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
Diffstat (limited to 'src/tools/rust-analyzer')
-rw-r--r--src/tools/rust-analyzer/editors/code/src/bootstrap.ts111
-rw-r--r--src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts96
2 files changed, 191 insertions, 16 deletions
diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
index 42dd0760d62..daead47e942 100644
--- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
+++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts
new file mode 100644
index 00000000000..8aeb72180a0
--- /dev/null
+++ b/src/tools/rust-analyzer/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",
+            );
+        });
+    });
+}