Skip to content

Commit a22aeb2

Browse files
committed
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.
1 parent 9cf00eb commit a22aeb2

File tree

2 files changed

+196
-16
lines changed

2 files changed

+196
-16
lines changed

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

Lines changed: 107 additions & 16 deletions
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,89 @@ 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 capture = path.match(/^.*\/toolchains\/(.*)\/bin\/rust-analyzer$/);
157+
158+
if (capture?.length === 2) {
159+
const toolchain = capture[1]!;
160+
if (toolchain.startsWith("stable-")) {
161+
return "1";
162+
} else {
163+
// It is a semver, so we must resolve Rust Analyzer's version.
164+
const raVersion = raVersionResolver(path);
165+
const raDate = raVersion?.match(/^rust-analyzer .*\(.* (\d{4}-\d{2}-\d{2})\)$/);
166+
if (raDate?.length === 2) {
167+
const precedence = toolchain.startsWith("nightly-") ? "/0" : "/1";
168+
return "0-" + raDate[1] + precedence;
169+
} else {
170+
return "2";
171+
}
172+
}
173+
} else {
174+
return "2";
175+
}
176+
}
177+
105178
async function fileExists(uri: vscode.Uri) {
106179
return await vscode.workspace.fs.stat(uri).then(
107180
() => true,
108181
() => false,
109182
);
110183
}
111184

185+
async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise<boolean> {
186+
try {
187+
const toolchainFileContents = new TextDecoder().decode(
188+
await vscode.workspace.fs.readFile(uri),
189+
);
190+
return (
191+
toolchainFileContents.match(/components\s*=\s*\[.*\"rust-analyzer\".*\]/g)?.length === 1
192+
);
193+
} catch (e) {
194+
return false;
195+
}
196+
}
197+
112198
export function isValidExecutable(path: string, extraEnv: Env): boolean {
113199
log.debug("Checking availability of a binary at", path);
114200

@@ -205,3 +291,8 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
205291
},
206292
);
207293
}
294+
295+
export const _private = {
296+
earliestToolchainPath,
297+
orderFromPath,
298+
};
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 () {
56+
assert.fail("Shouldn't get here.");
57+
},
58+
),
59+
"1",
60+
);
61+
});
62+
63+
suite.addTest("Order with invalid path to RA", async () => {
64+
assert.deepStrictEqual(
65+
_private.orderFromPath("some-weird-path", function () {
66+
assert.fail("Shouldn't get here.");
67+
}),
68+
"2",
69+
);
70+
});
71+
72+
suite.addTest("Earliest RA between nightly and stable", async () => {
73+
assert.deepStrictEqual(
74+
_private.earliestToolchainPath(
75+
"/Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer",
76+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
77+
function (path: string) {
78+
assert.deepStrictEqual(
79+
path,
80+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
81+
);
82+
return "rust-analyzer 1.67.0-nightly (b7bc90fe 2022-11-21)";
83+
},
84+
),
85+
"/Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer",
86+
);
87+
});
88+
});
89+
}

0 commit comments

Comments
 (0)