From a22aeb2c75e66c61e4a6416c169bff15234f59f1 Mon Sep 17 00:00:00 2001 From: huntc Date: Fri, 26 Jul 2024 15:33:18 +1000 Subject: [PATCH 1/6] Determine toolchain RA on age 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. --- .../editors/code/src/bootstrap.ts | 123 +++++++++++++++--- .../editors/code/tests/unit/bootstrap.test.ts | 89 +++++++++++++ 2 files changed, 196 insertions(+), 16 deletions(-) create mode 100644 src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index 527edf19eb45..ebcafd6f72e6 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,69 @@ 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 capture = path.match(/^.*\/toolchains\/(.*)\/bin\/rust-analyzer$/); + + if (capture?.length === 2) { + const toolchain = capture[1]!; + if (toolchain.startsWith("stable-")) { + return "1"; + } else { + // It is a semver, so we must resolve Rust Analyzer's version. + const raVersion = raVersionResolver(path); + const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); + if (raDate?.length === 2) { + const precedence = toolchain.startsWith("nightly-") ? "/0" : "/1"; + return "0-" + raDate[1] + precedence; + } else { + return "2"; + } + } + } else { + return "2"; + } +} + async function fileExists(uri: vscode.Uri) { return await vscode.workspace.fs.stat(uri).then( () => true, @@ -109,6 +182,19 @@ async function fileExists(uri: vscode.Uri) { ); } +async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise { + 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); @@ -205,3 +291,8 @@ async function patchelf(dest: vscode.Uri): Promise { }, ); } + +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 000000000000..78eb8f08cea2 --- /dev/null +++ b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts @@ -0,0 +1,89 @@ +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 () { + assert.fail("Shouldn't get here."); + }, + ), + "1", + ); + }); + + suite.addTest("Order with invalid path to RA", async () => { + assert.deepStrictEqual( + _private.orderFromPath("some-weird-path", function () { + assert.fail("Shouldn't get here."); + }), + "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) { + 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)"; + }, + ), + "/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer", + ); + }); + }); +} From 786ef8393c78819d09d519939dd103b89b000a44 Mon Sep 17 00:00:00 2001 From: huntc Date: Tue, 30 Jul 2024 16:26:56 +1000 Subject: [PATCH 2/6] Checks date for any RA --- .../editors/code/src/bootstrap.ts | 19 ++++++--------- .../editors/code/tests/unit/bootstrap.test.ts | 23 ++++++++++++------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index ebcafd6f72e6..521d0ba4c1d0 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -154,21 +154,16 @@ function orderFromPath( raVersionResolver: (path: string) => string | undefined, ): string { const capture = path.match(/^.*\/toolchains\/(.*)\/bin\/rust-analyzer$/); - if (capture?.length === 2) { const toolchain = capture[1]!; - if (toolchain.startsWith("stable-")) { - return "1"; + // It is a semver, so we must resolve Rust Analyzer's version. + const raVersion = raVersionResolver(path); + const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); + if (raDate?.length === 2) { + const precedence = toolchain.startsWith("nightly-") ? "/0" : "/1"; + return "0-" + raDate[1] + precedence; } else { - // It is a semver, so we must resolve Rust Analyzer's version. - const raVersion = raVersionResolver(path); - const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); - if (raDate?.length === 2) { - const precedence = toolchain.startsWith("nightly-") ? "/0" : "/1"; - return "0-" + raDate[1] + precedence; - } else { - return "2"; - } + return "2"; } } else { return "2"; 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 index 78eb8f08cea2..6e17d73adc47 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts @@ -52,11 +52,15 @@ export async function getTests(ctx: Context) { assert.deepStrictEqual( _private.orderFromPath( "/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer", - function () { - assert.fail("Shouldn't get here."); + 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)"; }, ), - "1", + "0-2024-06-10/1", ); }); @@ -75,11 +79,14 @@ export async function getTests(ctx: Context) { "/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) { - 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)"; + 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", From 6b4c0e03f84fdb10be959cdbd10bdd1f628a51bb Mon Sep 17 00:00:00 2001 From: huntc Date: Tue, 30 Jul 2024 16:38:47 +1000 Subject: [PATCH 3/6] Further simplifications --- .../editors/code/src/bootstrap.ts | 18 ++++++------------ .../editors/code/tests/unit/bootstrap.test.ts | 8 ++++---- 2 files changed, 10 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 521d0ba4c1d0..df49b7b70078 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -153,18 +153,12 @@ function orderFromPath( path: string, raVersionResolver: (path: string) => string | undefined, ): string { - const capture = path.match(/^.*\/toolchains\/(.*)\/bin\/rust-analyzer$/); - if (capture?.length === 2) { - const toolchain = capture[1]!; - // It is a semver, so we must resolve Rust Analyzer's version. - const raVersion = raVersionResolver(path); - const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); - if (raDate?.length === 2) { - const precedence = toolchain.startsWith("nightly-") ? "/0" : "/1"; - return "0-" + raDate[1] + precedence; - } else { - return "2"; - } + // It is a semver, so we must resolve Rust Analyzer's version. + 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 precedence + "-" + raDate[1]; } else { return "2"; } 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 index 6e17d73adc47..884e73e454a3 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts @@ -16,7 +16,7 @@ export async function getTests(ctx: Context) { return "rust-analyzer 1.67.0-nightly (b7bc90fe 2022-11-21)"; }, ), - "0-2022-11-21/0", + "0-2022-11-21", ); }); @@ -32,7 +32,7 @@ export async function getTests(ctx: Context) { return "rust-analyzer 1.72.1 (d5c2e9c3 2023-09-13)"; }, ), - "0-2023-09-13/1", + "1-2023-09-13", ); }); @@ -60,14 +60,14 @@ export async function getTests(ctx: Context) { return "rust-analyzer 1.79.0 (129f3b99 2024-06-10)"; }, ), - "0-2024-06-10/1", + "1-2024-06-10", ); }); suite.addTest("Order with invalid path to RA", async () => { assert.deepStrictEqual( _private.orderFromPath("some-weird-path", function () { - assert.fail("Shouldn't get here."); + return undefined; }), "2", ); From 440b8befaafba57d5c7db854a14ef5d7c7bebd08 Mon Sep 17 00:00:00 2001 From: huntc Date: Tue, 30 Jul 2024 19:24:15 +1000 Subject: [PATCH 4/6] Outdated comment removed --- .../rust-analyzer/editors/code/src/bootstrap.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index df49b7b70078..7bc3b3c36a24 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -15,7 +15,7 @@ export async function bootstrap( if (!path) { throw new Error( "rust-analyzer Language Server is not available. " + - "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).", + "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).", ); } @@ -103,11 +103,11 @@ async function getServer( await vscode.window.showErrorMessage( "Unfortunately we don't ship binaries for your platform yet. " + - "You need to manually clone the rust-analyzer repository and " + - "run `cargo xtask install --server` to build the language server from sources. " + - "If you feel that your platform should be supported, please create an issue " + - "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " + - "will consider it.", + "You need to manually clone the rust-analyzer repository and " + + "run `cargo xtask install --server` to build the language server from sources. " + + "If you feel that your platform should be supported, please create an issue " + + "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " + + "will consider it.", ); return undefined; } @@ -153,7 +153,6 @@ function orderFromPath( path: string, raVersionResolver: (path: string) => string | undefined, ): string { - // It is a semver, so we must resolve Rust Analyzer's version. const raVersion = raVersionResolver(path); const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); if (raDate?.length === 2) { From af4a59b98520412f1e17ae04390593ad260fa678 Mon Sep 17 00:00:00 2001 From: huntc Date: Tue, 30 Jul 2024 19:25:03 +1000 Subject: [PATCH 5/6] Formatting --- .../rust-analyzer/editors/code/src/bootstrap.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index 7bc3b3c36a24..9850dcc9355f 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -15,7 +15,7 @@ export async function bootstrap( if (!path) { throw new Error( "rust-analyzer Language Server is not available. " + - "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).", + "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).", ); } @@ -103,11 +103,11 @@ async function getServer( await vscode.window.showErrorMessage( "Unfortunately we don't ship binaries for your platform yet. " + - "You need to manually clone the rust-analyzer repository and " + - "run `cargo xtask install --server` to build the language server from sources. " + - "If you feel that your platform should be supported, please create an issue " + - "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " + - "will consider it.", + "You need to manually clone the rust-analyzer repository and " + + "run `cargo xtask install --server` to build the language server from sources. " + + "If you feel that your platform should be supported, please create an issue " + + "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " + + "will consider it.", ); return undefined; } From 8ca9034d1f01e7e9e4d7d7c347a8632aae73c58a Mon Sep 17 00:00:00 2001 From: huntc Date: Wed, 31 Jul 2024 21:24:56 +1000 Subject: [PATCH 6/6] Corrected precedence position --- src/tools/rust-analyzer/editors/code/src/bootstrap.ts | 2 +- .../rust-analyzer/editors/code/tests/unit/bootstrap.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index 9850dcc9355f..2bcd9ca0a408 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -157,7 +157,7 @@ function orderFromPath( const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/); if (raDate?.length === 2) { const precedence = path.includes("nightly-") ? "0" : "1"; - return precedence + "-" + raDate[1]; + return "0-" + raDate[1] + "/" + precedence; } else { return "2"; } 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 index 884e73e454a3..8aeb72180a02 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts @@ -16,7 +16,7 @@ export async function getTests(ctx: Context) { return "rust-analyzer 1.67.0-nightly (b7bc90fe 2022-11-21)"; }, ), - "0-2022-11-21", + "0-2022-11-21/0", ); }); @@ -32,7 +32,7 @@ export async function getTests(ctx: Context) { return "rust-analyzer 1.72.1 (d5c2e9c3 2023-09-13)"; }, ), - "1-2023-09-13", + "0-2023-09-13/1", ); }); @@ -60,7 +60,7 @@ export async function getTests(ctx: Context) { return "rust-analyzer 1.79.0 (129f3b99 2024-06-10)"; }, ), - "1-2024-06-10", + "0-2024-06-10/1", ); });