@@ -4,6 +4,7 @@ import type { Config } from "./config";
4
4
import { type Env , log } from "./util" ;
5
5
import type { PersistentState } from "./persistent_state" ;
6
6
import { exec , spawnSync } from "child_process" ;
7
+ import { TextDecoder } from "node:util" ;
7
8
8
9
export async function bootstrap (
9
10
context : vscode . ExtensionContext ,
@@ -50,26 +51,35 @@ async function getServer(
50
51
}
51
52
return explicitPath ;
52
53
}
53
- if ( packageJson . releaseTag === null ) return "rust-analyzer" ;
54
54
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
+ }
70
75
}
71
76
}
72
77
}
78
+ if ( toolchainServerPath ) {
79
+ return toolchainServerPath ;
80
+ }
81
+
82
+ if ( packageJson . releaseTag === null ) return "rust-analyzer" ;
73
83
74
84
// finally, use the bundled one
75
85
const ext = process . platform === "win32" ? ".exe" : "" ;
@@ -102,13 +112,89 @@ async function getServer(
102
112
return undefined ;
103
113
}
104
114
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 ( / ^ .* \/ t o o l c h a i n s \/ ( .* ) \/ b i n \/ r u s t - a n a l y z e r $ / ) ;
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 ( / ^ r u s t - a n a l y z e r .* \( .* ( \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
+
105
178
async function fileExists ( uri : vscode . Uri ) {
106
179
return await vscode . workspace . fs . stat ( uri ) . then (
107
180
( ) => true ,
108
181
( ) => false ,
109
182
) ;
110
183
}
111
184
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 ( / c o m p o n e n t s \s * = \s * \[ .* \" r u s t - a n a l y z e r \" .* \] / g) ?. length === 1
192
+ ) ;
193
+ } catch ( e ) {
194
+ return false ;
195
+ }
196
+ }
197
+
112
198
export function isValidExecutable ( path : string , extraEnv : Env ) : boolean {
113
199
log . debug ( "Checking availability of a binary at" , path ) ;
114
200
@@ -205,3 +291,8 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
205
291
} ,
206
292
) ;
207
293
}
294
+
295
+ export const _private = {
296
+ earliestToolchainPath,
297
+ orderFromPath,
298
+ } ;
0 commit comments