1
- use crate :: cmd:: { cfg_spinner, fail_spinner, succeed_spinner} ;
1
+ use crate :: cmd:: { cfg_spinner, fail_spinner, run_stage , succeed_spinner} ;
2
2
use crate :: errors:: * ;
3
3
use crate :: parse:: Opts ;
4
+ use cargo_lock:: Lockfile ;
5
+ use cargo_metadata:: MetadataCommand ;
4
6
use console:: Emoji ;
5
7
use directories:: ProjectDirs ;
6
8
use flate2:: read:: GzDecoder ;
@@ -18,6 +20,7 @@ use tar::Archive;
18
20
use tokio:: io:: AsyncWriteExt ;
19
21
20
22
static INSTALLING : Emoji < ' _ , ' _ > = Emoji ( "📥" , "" ) ;
23
+ static GENERATING_LOCKFILE : Emoji < ' _ , ' _ > = Emoji ( "🔏" , "" ) ;
21
24
22
25
// For each of the tools installed in this file, we preferentially
23
26
// manually download it. If that can't be achieved due to a platform
@@ -89,6 +92,33 @@ impl Tools {
89
92
///
90
93
/// If tools are installed, this will create a CLI spinner automatically.
91
94
pub async fn new ( dir : & Path , global_opts : & Opts ) -> Result < Self , InstallError > {
95
+ // First, make sure `Cargo.lock` exists, since we'll need it for determining the
96
+ // right version of `wasm-bindgen`
97
+ let metadata = MetadataCommand :: new ( )
98
+ . no_deps ( )
99
+ . exec ( )
100
+ . map_err ( |err| InstallError :: MetadataFailed { source : err } ) ?;
101
+ let workspace_root = metadata. workspace_root . into_std_path_buf ( ) ;
102
+ let lockfile_path = workspace_root. join ( "Cargo.lock" ) ;
103
+ if !lockfile_path. exists ( ) {
104
+ let lf_msg = format ! ( "{} Generating Cargo lockfile" , GENERATING_LOCKFILE ) ;
105
+ let lf_spinner = cfg_spinner ( ProgressBar :: new_spinner ( ) , & lf_msg) ;
106
+ let ( _stdout, _stderr, exit_code) = run_stage (
107
+ vec ! [ "cargo generate-lockfile" ] ,
108
+ & workspace_root,
109
+ & lf_spinner,
110
+ & lf_msg,
111
+ Vec :: new ( ) ,
112
+ )
113
+ . map_err ( |err| InstallError :: LockfileGenerationFailed { source : err } ) ?;
114
+ if exit_code != 0 {
115
+ // The output has already been handled, just terminate
116
+ return Err ( InstallError :: LockfileGenerationNonZero { code : exit_code } ) ;
117
+ }
118
+ }
119
+ let lockfile = Lockfile :: load ( lockfile_path)
120
+ . map_err ( |err| InstallError :: LockfileLoadFailed { source : err } ) ?;
121
+
92
122
let target = get_tools_dir ( dir, global_opts. no_system_tools_cache ) ?;
93
123
94
124
// Instantiate the tools
@@ -104,8 +134,8 @@ impl Tools {
104
134
) ;
105
135
106
136
// Get the statuses of all the tools
107
- let wb_status = wasm_bindgen. get_status ( & target) ?;
108
- let wo_status = wasm_opt. get_status ( & target) ?;
137
+ let wb_status = wasm_bindgen. get_status ( & target, & lockfile ) ?;
138
+ let wo_status = wasm_opt. get_status ( & target, & lockfile ) ?;
109
139
// Figure out if everything is present
110
140
// This is the only case in which we don't have to start the spinner
111
141
if let ( ToolStatus :: Available ( wb_path) , ToolStatus :: Available ( wo_path) ) =
@@ -253,7 +283,11 @@ impl Tool {
253
283
/// installed globally on the user's system, etc. If this returns
254
284
/// `ToolStatus::NeedsInstall`, we can be sure that there are binaries
255
285
/// available, and the same if it returns `ToolStatus::NeedsLatestInstall`.
256
- pub fn get_status ( & self , target : & Path ) -> Result < ToolStatus , InstallError > {
286
+ pub fn get_status (
287
+ & self ,
288
+ target : & Path ,
289
+ lockfile : & Lockfile ,
290
+ ) -> Result < ToolStatus , InstallError > {
257
291
// The status information will be incomplete from this first pass
258
292
let initial_status = {
259
293
// If there's a directory that matches with a given user version, we'll use it.
@@ -268,24 +302,30 @@ impl Tool {
268
302
// If they've given us a version, we'll check if that directory exists (we don't
269
303
// care about any others)
270
304
if let Some ( version) = & self . user_given_version {
271
- let expected_path = target. join ( format ! ( "{}-{}" , self . name, version) ) ;
272
- Ok ( if fs:: metadata ( & expected_path) . is_ok ( ) {
273
- ToolStatus :: Available (
274
- expected_path
275
- . join ( & self . final_path )
276
- . to_string_lossy ( )
277
- . to_string ( ) ,
278
- )
305
+ // If the user wants the latets version, just force an update
306
+ if version == "latest" {
307
+ Ok ( ToolStatus :: NeedsLatestInstall )
279
308
} else {
280
- ToolStatus :: NeedsInstall {
281
- version : version. to_string ( ) ,
282
- // This will be filled in on the second pass-through
283
- artifact_name : String :: new ( ) ,
284
- }
285
- } )
309
+ let expected_path = target. join ( format ! ( "{}-{}" , self . name, version) ) ;
310
+ Ok ( if fs:: metadata ( & expected_path) . is_ok ( ) {
311
+ ToolStatus :: Available (
312
+ expected_path
313
+ . join ( & self . final_path )
314
+ . to_string_lossy ( )
315
+ . to_string ( ) ,
316
+ )
317
+ } else {
318
+ ToolStatus :: NeedsInstall {
319
+ version : version. to_string ( ) ,
320
+ // This will be filled in on the second pass-through
321
+ artifact_name : String :: new ( ) ,
322
+ }
323
+ } )
324
+ }
286
325
} else {
287
- // We have no further information from the user, so we'll use the latest version
288
- // that's installed, or we'll install the latest version.
326
+ // We have no further information from the user, so we'll use whatever matches
327
+ // the user's `Cargo.lock`, or, if they haven't specified anything, we'll try
328
+ // the latest version.
289
329
// Either way, we need to know what we've got installed already by walking the
290
330
// directory.
291
331
let mut versions: Vec < String > = Vec :: new ( ) ;
@@ -307,22 +347,50 @@ impl Tool {
307
347
// Now order those from most recent to least recent
308
348
versions. sort ( ) ;
309
349
let versions = versions. into_iter ( ) . rev ( ) . collect :: < Vec < String > > ( ) ;
310
- // If there are any at all, pick the first one
311
- if !versions. is_empty ( ) {
312
- let latest_available_version = & versions[ 0 ] ;
313
- // We know the directory for this version had a valid name, so we can
314
- // determine exactly where it was
315
- let path_to_latest_version = target. join ( format ! (
316
- "{}-{}/{}" ,
317
- self . name, latest_available_version, self . final_path
318
- ) ) ;
319
- Ok ( ToolStatus :: Available (
320
- path_to_latest_version. to_string_lossy ( ) . to_string ( ) ,
321
- ) )
322
- } else {
323
- // We don't check the latest version here because we haven't started the
324
- // spinner yet
325
- Ok ( ToolStatus :: NeedsLatestInstall )
350
+
351
+ // Now figure out what would match the current setup by checking `Cargo.lock`
352
+ // (it's entirely possible that there are multiple versions
353
+ // of `wasm-bindgen` in here, but that would be the user's problem).
354
+ // It doesn't matter that we do this erroneously for other tools, since they'll
355
+ // just return `None`.
356
+ match self . get_pkg_version_from_lockfile ( lockfile) ? {
357
+ Some ( version) => {
358
+ if versions. contains ( & version) {
359
+ let path_to_version = target
360
+ . join ( format ! ( "{}-{}/{}" , self . name, version, self . final_path) ) ;
361
+ Ok ( ToolStatus :: Available (
362
+ path_to_version. to_string_lossy ( ) . to_string ( ) ,
363
+ ) )
364
+ } else {
365
+ Ok ( ToolStatus :: NeedsInstall {
366
+ version,
367
+ // This will be filled in on the second pass-through
368
+ artifact_name : String :: new ( ) ,
369
+ } )
370
+ }
371
+ }
372
+ // There's nothing in the lockfile, so we'll go with the latest we have
373
+ // installed
374
+ None => {
375
+ // If there are any at all, pick the first one
376
+ if !versions. is_empty ( ) {
377
+ let latest_available_version = & versions[ 0 ] ;
378
+ // We know the directory for this version had a valid name, so we
379
+ // can determine exactly where it
380
+ // was
381
+ let path_to_latest_version = target. join ( format ! (
382
+ "{}-{}/{}" ,
383
+ self . name, latest_available_version, self . final_path
384
+ ) ) ;
385
+ Ok ( ToolStatus :: Available (
386
+ path_to_latest_version. to_string_lossy ( ) . to_string ( ) ,
387
+ ) )
388
+ } else {
389
+ // We don't check the latest version here because we haven't started
390
+ // the spinner yet
391
+ Ok ( ToolStatus :: NeedsLatestInstall )
392
+ }
393
+ }
326
394
}
327
395
}
328
396
}
@@ -548,6 +616,19 @@ impl Tool {
548
616
. unwrap ( )
549
617
. to_string ( ) )
550
618
}
619
+ /// Gets the version of a specific package in `Cargo.lock`, assuming it has
620
+ /// already been generated.
621
+ fn get_pkg_version_from_lockfile (
622
+ & self ,
623
+ lockfile : & Lockfile ,
624
+ ) -> Result < Option < String > , InstallError > {
625
+ let version = lockfile
626
+ . packages
627
+ . iter ( )
628
+ . find ( |p| p. name . as_str ( ) == self . name )
629
+ . map ( |p| p. version . to_string ( ) ) ;
630
+ Ok ( version)
631
+ }
551
632
}
552
633
553
634
/// A tool's status on-system.
0 commit comments