Skip to content

Commit d646f7b

Browse files
committed
Auto merge of rust-lang#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 rust-lang#17663
2 parents 61ebcf6 + 8ca9034 commit d646f7b

File tree

2 files changed

+191
-16
lines changed

2 files changed

+191
-16
lines changed

src/tools/rust-analyzer/editors/code/src/bootstrap.ts

+95-16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Config } from "./config";
44
import { type Env, log } from "./util";
55
import type { PersistentState } from "./persistent_state";
66
import { exec, spawnSync } from "child_process";
7+
import { TextDecoder } from "node:util";
78

89
export async function bootstrap(
910
context: vscode.ExtensionContext,
@@ -50,26 +51,35 @@ async function getServer(
5051
}
5152
return explicitPath;
5253
}
53-
if (packageJson.releaseTag === null) return "rust-analyzer";
5454

55-
if (vscode.workspace.workspaceFolders?.length === 1) {
56-
// otherwise check if there is a toolchain override for the current vscode workspace
57-
// and if the toolchain of this override has a rust-analyzer component
58-
// if so, use the rust-analyzer component
59-
const toolchainTomlExists = await fileExists(
60-
vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0]!.uri, "rust-toolchain.toml"),
61-
);
62-
if (toolchainTomlExists) {
63-
const res = spawnSync("rustup", ["which", "rust-analyzer"], {
64-
encoding: "utf8",
65-
env: { ...process.env },
66-
cwd: vscode.workspace.workspaceFolders[0]!.uri.fsPath,
67-
});
68-
if (!res.error && res.status === 0) {
69-
return res.stdout.trim();
55+
let toolchainServerPath = undefined;
56+
if (vscode.workspace.workspaceFolders) {
57+
for (const workspaceFolder of vscode.workspace.workspaceFolders) {
58+
// otherwise check if there is a toolchain override for the current vscode workspace
59+
// and if the toolchain of this override has a rust-analyzer component
60+
// if so, use the rust-analyzer component
61+
const toolchainUri = vscode.Uri.joinPath(workspaceFolder.uri, "rust-toolchain.toml");
62+
if (await hasToolchainFileWithRaDeclared(toolchainUri)) {
63+
const res = spawnSync("rustup", ["which", "rust-analyzer"], {
64+
encoding: "utf8",
65+
env: { ...process.env },
66+
cwd: workspaceFolder.uri.fsPath,
67+
});
68+
if (!res.error && res.status === 0) {
69+
toolchainServerPath = earliestToolchainPath(
70+
toolchainServerPath,
71+
res.stdout.trim(),
72+
raVersionResolver,
73+
);
74+
}
7075
}
7176
}
7277
}
78+
if (toolchainServerPath) {
79+
return toolchainServerPath;
80+
}
81+
82+
if (packageJson.releaseTag === null) return "rust-analyzer";
7383

7484
// finally, use the bundled one
7585
const ext = process.platform === "win32" ? ".exe" : "";
@@ -102,13 +112,77 @@ async function getServer(
102112
return undefined;
103113
}
104114

115+
// Given a path to a rust-analyzer executable, resolve its version and return it.
116+
function raVersionResolver(path: string): string | undefined {
117+
const res = spawnSync(path, ["--version"], {
118+
encoding: "utf8",
119+
});
120+
if (!res.error && res.status === 0) {
121+
return res.stdout;
122+
} else {
123+
return undefined;
124+
}
125+
}
126+
127+
// Given a path to two rust-analyzer executables, return the earliest one by date.
128+
function earliestToolchainPath(
129+
path0: string | undefined,
130+
path1: string,
131+
raVersionResolver: (path: string) => string | undefined,
132+
): string {
133+
if (path0) {
134+
if (orderFromPath(path0, raVersionResolver) < orderFromPath(path1, raVersionResolver)) {
135+
return path0;
136+
} else {
137+
return path1;
138+
}
139+
} else {
140+
return path1;
141+
}
142+
}
143+
144+
// Further to extracting a date for comparison, determine the order of a toolchain as follows:
145+
// Highest - nightly
146+
// Medium - versioned
147+
// Lowest - stable
148+
// Example paths:
149+
// nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer
150+
// versioned - /Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer
151+
// stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer
152+
function orderFromPath(
153+
path: string,
154+
raVersionResolver: (path: string) => string | undefined,
155+
): string {
156+
const raVersion = raVersionResolver(path);
157+
const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/);
158+
if (raDate?.length === 2) {
159+
const precedence = path.includes("nightly-") ? "0" : "1";
160+
return "0-" + raDate[1] + "/" + precedence;
161+
} else {
162+
return "2";
163+
}
164+
}
165+
105166
async function fileExists(uri: vscode.Uri) {
106167
return await vscode.workspace.fs.stat(uri).then(
107168
() => true,
108169
() => false,
109170
);
110171
}
111172

173+
async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise<boolean> {
174+
try {
175+
const toolchainFileContents = new TextDecoder().decode(
176+
await vscode.workspace.fs.readFile(uri),
177+
);
178+
return (
179+
toolchainFileContents.match(/components\s*=\s*\[.*\"rust-analyzer\".*\]/g)?.length === 1
180+
);
181+
} catch (e) {
182+
return false;
183+
}
184+
}
185+
112186
export function isValidExecutable(path: string, extraEnv: Env): boolean {
113187
log.debug("Checking availability of a binary at", path);
114188

@@ -207,3 +281,8 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
207281
},
208282
);
209283
}
284+
285+
export const _private = {
286+
earliestToolchainPath,
287+
orderFromPath,
288+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import * as assert from "assert";
2+
import { _private } from "../../src/bootstrap";
3+
import type { Context } from ".";
4+
5+
export async function getTests(ctx: Context) {
6+
await ctx.suite("Bootstrap/Select toolchain RA", (suite) => {
7+
suite.addTest("Order of nightly RA", async () => {
8+
assert.deepStrictEqual(
9+
_private.orderFromPath(
10+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
11+
function (path: string) {
12+
assert.deepStrictEqual(
13+
path,
14+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
15+
);
16+
return "rust-analyzer 1.67.0-nightly (b7bc90fe 2022-11-21)";
17+
},
18+
),
19+
"0-2022-11-21/0",
20+
);
21+
});
22+
23+
suite.addTest("Order of versioned RA", async () => {
24+
assert.deepStrictEqual(
25+
_private.orderFromPath(
26+
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
27+
function (path: string) {
28+
assert.deepStrictEqual(
29+
path,
30+
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
31+
);
32+
return "rust-analyzer 1.72.1 (d5c2e9c3 2023-09-13)";
33+
},
34+
),
35+
"0-2023-09-13/1",
36+
);
37+
});
38+
39+
suite.addTest("Order of versioned RA when unable to obtain version date", async () => {
40+
assert.deepStrictEqual(
41+
_private.orderFromPath(
42+
"/Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer",
43+
function () {
44+
return "rust-analyzer 1.72.1";
45+
},
46+
),
47+
"2",
48+
);
49+
});
50+
51+
suite.addTest("Order of stable RA", async () => {
52+
assert.deepStrictEqual(
53+
_private.orderFromPath(
54+
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
55+
function (path: string) {
56+
assert.deepStrictEqual(
57+
path,
58+
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
59+
);
60+
return "rust-analyzer 1.79.0 (129f3b99 2024-06-10)";
61+
},
62+
),
63+
"0-2024-06-10/1",
64+
);
65+
});
66+
67+
suite.addTest("Order with invalid path to RA", async () => {
68+
assert.deepStrictEqual(
69+
_private.orderFromPath("some-weird-path", function () {
70+
return undefined;
71+
}),
72+
"2",
73+
);
74+
});
75+
76+
suite.addTest("Earliest RA between nightly and stable", async () => {
77+
assert.deepStrictEqual(
78+
_private.earliestToolchainPath(
79+
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
80+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
81+
function (path: string) {
82+
if (
83+
path ===
84+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer"
85+
) {
86+
return "rust-analyzer 1.67.0-nightly (b7bc90fe 2022-11-21)";
87+
} else {
88+
return "rust-analyzer 1.79.0 (129f3b99 2024-06-10)";
89+
}
90+
},
91+
),
92+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
93+
);
94+
});
95+
});
96+
}

0 commit comments

Comments
 (0)