@@ -4,15 +4,16 @@ use std::sync::Arc;
4
4
use std:: { env, fs} ;
5
5
6
6
use anyhow:: { bail, format_err} ;
7
+ use semver:: VersionReq ;
7
8
use tempfile:: Builder as TempFileBuilder ;
8
9
9
10
use crate :: core:: compiler:: Freshness ;
10
11
use crate :: core:: compiler:: { CompileKind , DefaultExecutor , Executor } ;
11
- use crate :: core:: { Edition , Package , PackageId , Source , SourceId , Workspace } ;
12
+ use crate :: core:: { Dependency , Edition , Package , PackageId , Source , SourceId , Workspace } ;
12
13
use crate :: ops:: common_for_install_and_uninstall:: * ;
13
- use crate :: sources:: { GitSource , SourceConfigMap } ;
14
+ use crate :: sources:: { GitSource , PathSource , SourceConfigMap } ;
14
15
use crate :: util:: errors:: { CargoResult , CargoResultExt } ;
15
- use crate :: util:: { paths, Config , Filesystem } ;
16
+ use crate :: util:: { paths, Config , Filesystem , Rustc , ToSemver } ;
16
17
use crate :: { drop_println, ops} ;
17
18
18
19
struct Transaction {
@@ -65,7 +66,9 @@ pub fn install(
65
66
} else {
66
67
let mut succeeded = vec ! [ ] ;
67
68
let mut failed = vec ! [ ] ;
68
- let mut first = true ;
69
+ // "Tracks whether or not the source (such as a registry or git repo) has been updated.
70
+ // This is used to avoid updating it multiple times when installing multiple crates.
71
+ let mut did_update = false ;
69
72
for krate in krates {
70
73
let root = root. clone ( ) ;
71
74
let map = map. clone ( ) ;
@@ -80,15 +83,19 @@ pub fn install(
80
83
opts,
81
84
force,
82
85
no_track,
83
- first ,
86
+ !did_update ,
84
87
) {
85
- Ok ( ( ) ) => succeeded. push ( krate) ,
88
+ Ok ( still_needs_update) => {
89
+ succeeded. push ( krate) ;
90
+ did_update |= !still_needs_update;
91
+ }
86
92
Err ( e) => {
87
93
crate :: display_error ( & e, & mut config. shell ( ) ) ;
88
- failed. push ( krate)
94
+ failed. push ( krate) ;
95
+ // We assume an update was performed if we got an error.
96
+ did_update = true ;
89
97
}
90
98
}
91
- first = false ;
92
99
}
93
100
94
101
let mut summary = vec ! [ ] ;
@@ -133,6 +140,11 @@ pub fn install(
133
140
Ok ( ( ) )
134
141
}
135
142
143
+ // Returns whether a subsequent call should attempt to update again.
144
+ // The `needs_update_if_source_is_index` parameter indicates whether or not the source index should
145
+ // be updated. This is used ensure it is only updated once when installing multiple crates.
146
+ // The return value here is used so that the caller knows what to pass to the
147
+ // `needs_update_if_source_is_index` parameter when `install_one` is called again.
136
148
fn install_one (
137
149
config : & Config ,
138
150
root : & Filesystem ,
@@ -144,8 +156,8 @@ fn install_one(
144
156
opts : & ops:: CompileOptions ,
145
157
force : bool ,
146
158
no_track : bool ,
147
- is_first_install : bool ,
148
- ) -> CargoResult < ( ) > {
159
+ needs_update_if_source_is_index : bool ,
160
+ ) -> CargoResult < bool > {
149
161
if let Some ( name) = krate {
150
162
if name == "." {
151
163
bail ! (
@@ -155,72 +167,110 @@ fn install_one(
155
167
)
156
168
}
157
169
}
158
- let pkg = if source_id. is_git ( ) {
159
- select_pkg (
160
- GitSource :: new ( source_id, config) ?,
161
- krate,
162
- vers,
163
- config,
164
- true ,
165
- & mut |git| git. read_packages ( ) ,
166
- ) ?
167
- } else if source_id. is_path ( ) {
168
- let mut src = path_source ( source_id, config) ?;
169
- if !src. path ( ) . is_dir ( ) {
170
- bail ! (
171
- "`{}` is not a directory. \
172
- --path must point to a directory containing a Cargo.toml file.",
173
- src. path( ) . display( )
174
- )
175
- }
176
- if !src. path ( ) . join ( "Cargo.toml" ) . exists ( ) {
177
- if from_cwd {
178
- bail ! (
179
- "`{}` is not a crate root; specify a crate to \
180
- install from crates.io, or use --path or --git to \
181
- specify an alternate source",
182
- src. path( ) . display( )
183
- ) ;
170
+
171
+ let dst = root. join ( "bin" ) . into_path_unlocked ( ) ;
172
+
173
+ let pkg = {
174
+ let dep = {
175
+ if let Some ( krate) = krate {
176
+ let vers = if let Some ( vers_flag) = vers {
177
+ Some ( parse_semver_flag ( vers_flag) ?. to_string ( ) )
178
+ } else {
179
+ if source_id. is_registry ( ) {
180
+ // Avoid pre-release versions from crate.io
181
+ // unless explicitly asked for
182
+ Some ( String :: from ( "*" ) )
183
+ } else {
184
+ None
185
+ }
186
+ } ;
187
+ Some ( Dependency :: parse_no_deprecated (
188
+ krate,
189
+ vers. as_deref ( ) ,
190
+ source_id,
191
+ ) ?)
184
192
} else {
193
+ None
194
+ }
195
+ } ;
196
+
197
+ if source_id. is_git ( ) {
198
+ let mut source = GitSource :: new ( source_id, config) ?;
199
+ select_pkg (
200
+ & mut source,
201
+ dep,
202
+ |git : & mut GitSource < ' _ > | git. read_packages ( ) ,
203
+ config,
204
+ ) ?
205
+ } else if source_id. is_path ( ) {
206
+ let mut src = path_source ( source_id, config) ?;
207
+ if !src. path ( ) . is_dir ( ) {
185
208
bail ! (
186
- "`{}` does not contain a Cargo.toml file . \
187
- --path must point to a directory containing a Cargo.toml file.",
209
+ "`{}` is not a directory . \
210
+ --path must point to a directory containing a Cargo.toml file.",
188
211
src. path( ) . display( )
189
212
)
190
213
}
191
- }
192
- src. update ( ) ?;
193
- select_pkg ( src, krate, vers, config, false , & mut |path| {
194
- path. read_packages ( )
195
- } ) ?
196
- } else {
197
- select_pkg (
198
- map. load ( source_id, & HashSet :: new ( ) ) ?,
199
- krate,
200
- vers,
201
- config,
202
- is_first_install,
203
- & mut |_| {
214
+ if !src. path ( ) . join ( "Cargo.toml" ) . exists ( ) {
215
+ if from_cwd {
216
+ bail ! (
217
+ "`{}` is not a crate root; specify a crate to \
218
+ install from crates.io, or use --path or --git to \
219
+ specify an alternate source",
220
+ src. path( ) . display( )
221
+ ) ;
222
+ } else {
223
+ bail ! (
224
+ "`{}` does not contain a Cargo.toml file. \
225
+ --path must point to a directory containing a Cargo.toml file.",
226
+ src. path( ) . display( )
227
+ )
228
+ }
229
+ }
230
+ select_pkg (
231
+ & mut src,
232
+ dep,
233
+ |path : & mut PathSource < ' _ > | path. read_packages ( ) ,
234
+ config,
235
+ ) ?
236
+ } else {
237
+ if let Some ( dep) = dep {
238
+ let mut source = map. load ( source_id, & HashSet :: new ( ) ) ?;
239
+ if let Ok ( Some ( pkg) ) = installed_exact_package (
240
+ dep. clone ( ) ,
241
+ & mut source,
242
+ config,
243
+ opts,
244
+ root,
245
+ & dst,
246
+ force,
247
+ ) {
248
+ let msg = format ! (
249
+ "package `{}` is already installed, use --force to override" ,
250
+ pkg
251
+ ) ;
252
+ config. shell ( ) . status ( "Ignored" , & msg) ?;
253
+ return Ok ( true ) ;
254
+ }
255
+ select_dep_pkg ( & mut source, dep, config, needs_update_if_source_is_index) ?
256
+ } else {
204
257
bail ! (
205
258
"must specify a crate to install from \
206
259
crates.io, or use --path or --git to \
207
260
specify alternate source"
208
261
)
209
- } ,
210
- ) ?
262
+ }
263
+ }
211
264
} ;
212
265
213
- let ( mut ws, git_package) = if source_id. is_git ( ) {
266
+ let ( mut ws, rustc, target) = make_ws_rustc_target ( config, opts, & source_id, pkg. clone ( ) ) ?;
267
+ let pkg = if source_id. is_git ( ) {
214
268
// Don't use ws.current() in order to keep the package source as a git source so that
215
269
// install tracking uses the correct source.
216
- ( Workspace :: new ( pkg. manifest_path ( ) , config) ?, Some ( & pkg) )
217
- } else if source_id. is_path ( ) {
218
- ( Workspace :: new ( pkg. manifest_path ( ) , config) ?, None )
270
+ pkg
219
271
} else {
220
- ( Workspace :: ephemeral ( pkg , config , None , false ) ? , None )
272
+ ws . current ( ) ? . clone ( )
221
273
} ;
222
- ws. set_ignore_lock ( config. lock_update_allowed ( ) ) ;
223
- ws. set_require_optional_deps ( false ) ;
224
274
225
275
let mut td_opt = None ;
226
276
let mut needs_cleanup = false ;
@@ -238,8 +288,6 @@ fn install_one(
238
288
ws. set_target_dir ( target_dir) ;
239
289
}
240
290
241
- let pkg = git_package. map_or_else ( || ws. current ( ) , |pkg| Ok ( pkg) ) ?;
242
-
243
291
if from_cwd {
244
292
if pkg. manifest ( ) . edition ( ) == Edition :: Edition2015 {
245
293
config. shell ( ) . warn (
@@ -265,20 +313,9 @@ fn install_one(
265
313
bail ! ( "specified package `{}` has no binaries" , pkg) ;
266
314
}
267
315
268
- // Preflight checks to check up front whether we'll overwrite something.
269
- // We have to check this again afterwards, but may as well avoid building
270
- // anything if we're gonna throw it away anyway.
271
- let dst = root. join ( "bin" ) . into_path_unlocked ( ) ;
272
- let rustc = config. load_global_rustc ( Some ( & ws) ) ?;
273
- let requested_kind = opts. build_config . single_requested_kind ( ) ?;
274
- let target = match & requested_kind {
275
- CompileKind :: Host => rustc. host . as_str ( ) ,
276
- CompileKind :: Target ( target) => target. short_name ( ) ,
277
- } ;
278
-
279
316
// Helper for --no-track flag to make sure it doesn't overwrite anything.
280
317
let no_track_duplicates = || -> CargoResult < BTreeMap < String , Option < PackageId > > > {
281
- let duplicates: BTreeMap < String , Option < PackageId > > = exe_names ( pkg, & opts. filter )
318
+ let duplicates: BTreeMap < String , Option < PackageId > > = exe_names ( & pkg, & opts. filter )
282
319
. into_iter ( )
283
320
. filter ( |name| dst. join ( name) . exists ( ) )
284
321
. map ( |name| ( name, None ) )
@@ -300,22 +337,17 @@ fn install_one(
300
337
// Check for conflicts.
301
338
no_track_duplicates ( ) ?;
302
339
} else {
303
- let tracker = InstallTracker :: load ( config, root) ?;
304
- let ( freshness, _duplicates) =
305
- tracker. check_upgrade ( & dst, pkg, force, opts, target, & rustc. verbose_version ) ?;
306
- if freshness == Freshness :: Fresh {
340
+ if is_installed ( & pkg, config, opts, & rustc, & target, root, & dst, force) ? {
307
341
let msg = format ! (
308
342
"package `{}` is already installed, use --force to override" ,
309
343
pkg
310
344
) ;
311
345
config. shell ( ) . status ( "Ignored" , & msg) ?;
312
- return Ok ( ( ) ) ;
346
+ return Ok ( false ) ;
313
347
}
314
- // Unlock while building.
315
- drop ( tracker) ;
316
348
}
317
349
318
- config. shell ( ) . status ( "Installing" , pkg) ?;
350
+ config. shell ( ) . status ( "Installing" , & pkg) ?;
319
351
320
352
check_yanked_install ( & ws) ?;
321
353
@@ -356,7 +388,7 @@ fn install_one(
356
388
} else {
357
389
let tracker = InstallTracker :: load ( config, root) ?;
358
390
let ( _freshness, duplicates) =
359
- tracker. check_upgrade ( & dst, pkg, force, opts, target, & rustc. verbose_version ) ?;
391
+ tracker. check_upgrade ( & dst, & pkg, force, opts, & target, & rustc. verbose_version ) ?;
360
392
( Some ( tracker) , duplicates)
361
393
} ;
362
394
@@ -417,15 +449,15 @@ fn install_one(
417
449
418
450
if let Some ( mut tracker) = tracker {
419
451
tracker. mark_installed (
420
- pkg,
452
+ & pkg,
421
453
& successful_bins,
422
454
vers. map ( |s| s. to_string ( ) ) ,
423
455
opts,
424
- target,
456
+ & target,
425
457
& rustc. verbose_version ,
426
458
) ;
427
459
428
- if let Err ( e) = remove_orphaned_bins ( & ws, & mut tracker, & duplicates, pkg, & dst) {
460
+ if let Err ( e) = remove_orphaned_bins ( & ws, & mut tracker, & duplicates, & pkg, & dst) {
429
461
// Don't hard error on remove.
430
462
config
431
463
. shell ( )
@@ -467,7 +499,7 @@ fn install_one(
467
499
"Installed" ,
468
500
format ! ( "package `{}` {}" , pkg, executables( successful_bins. iter( ) ) ) ,
469
501
) ?;
470
- Ok ( ( ) )
502
+ Ok ( false )
471
503
} else {
472
504
if !to_install. is_empty ( ) {
473
505
config. shell ( ) . status (
@@ -492,7 +524,128 @@ fn install_one(
492
524
) ,
493
525
) ?;
494
526
}
495
- Ok ( ( ) )
527
+ Ok ( false )
528
+ }
529
+ }
530
+
531
+ fn is_installed (
532
+ pkg : & Package ,
533
+ config : & Config ,
534
+ opts : & ops:: CompileOptions ,
535
+ rustc : & Rustc ,
536
+ target : & str ,
537
+ root : & Filesystem ,
538
+ dst : & Path ,
539
+ force : bool ,
540
+ ) -> CargoResult < bool > {
541
+ let tracker = InstallTracker :: load ( config, root) ?;
542
+ let ( freshness, _duplicates) =
543
+ tracker. check_upgrade ( dst, pkg, force, opts, target, & rustc. verbose_version ) ?;
544
+ Ok ( freshness == Freshness :: Fresh )
545
+ }
546
+
547
+ /// Checks if vers can only be satisfied by exactly one version of a package in a registry, and it's
548
+ /// already installed. If this is the case, we can skip interacting with a registry to check if
549
+ /// newer versions may be installable, as no newer version can exist.
550
+ fn installed_exact_package < T > (
551
+ dep : Dependency ,
552
+ source : & mut T ,
553
+ config : & Config ,
554
+ opts : & ops:: CompileOptions ,
555
+ root : & Filesystem ,
556
+ dst : & Path ,
557
+ force : bool ,
558
+ ) -> CargoResult < Option < Package > >
559
+ where
560
+ T : Source ,
561
+ {
562
+ if !dep. is_locked ( ) {
563
+ // If the version isn't exact, we may need to update the registry and look for a newer
564
+ // version - we can't know if the package is installed without doing so.
565
+ return Ok ( None ) ;
566
+ }
567
+ // Try getting the package from the registry without updating it, to avoid a potentially
568
+ // expensive network call in the case that the package is already installed.
569
+ // If this fails, the caller will possibly do an index update and try again, this is just a
570
+ // best-effort check to see if we can avoid hitting the network.
571
+ if let Ok ( pkg) = select_dep_pkg ( source, dep, config, false ) {
572
+ let ( _ws, rustc, target) =
573
+ make_ws_rustc_target ( & config, opts, & source. source_id ( ) , pkg. clone ( ) ) ?;
574
+ if let Ok ( true ) = is_installed ( & pkg, config, opts, & rustc, & target, root, & dst, force) {
575
+ return Ok ( Some ( pkg) ) ;
576
+ }
577
+ }
578
+ Ok ( None )
579
+ }
580
+
581
+ fn make_ws_rustc_target < ' cfg > (
582
+ config : & ' cfg Config ,
583
+ opts : & ops:: CompileOptions ,
584
+ source_id : & SourceId ,
585
+ pkg : Package ,
586
+ ) -> CargoResult < ( Workspace < ' cfg > , Rustc , String ) > {
587
+ let mut ws = if source_id. is_git ( ) || source_id. is_path ( ) {
588
+ Workspace :: new ( pkg. manifest_path ( ) , config) ?
589
+ } else {
590
+ Workspace :: ephemeral ( pkg, config, None , false ) ?
591
+ } ;
592
+ ws. set_ignore_lock ( config. lock_update_allowed ( ) ) ;
593
+ ws. set_require_optional_deps ( false ) ;
594
+
595
+ let rustc = config. load_global_rustc ( Some ( & ws) ) ?;
596
+ let target = match & opts. build_config . single_requested_kind ( ) ? {
597
+ CompileKind :: Host => rustc. host . as_str ( ) . to_owned ( ) ,
598
+ CompileKind :: Target ( target) => target. short_name ( ) . to_owned ( ) ,
599
+ } ;
600
+
601
+ Ok ( ( ws, rustc, target) )
602
+ }
603
+
604
+ /// Parses x.y.z as if it were =x.y.z, and gives CLI-specific error messages in the case of invalid
605
+ /// values.
606
+ fn parse_semver_flag ( v : & str ) -> CargoResult < VersionReq > {
607
+ // If the version begins with character <, >, =, ^, ~ parse it as a
608
+ // version range, otherwise parse it as a specific version
609
+ let first = v
610
+ . chars ( )
611
+ . next ( )
612
+ . ok_or_else ( || format_err ! ( "no version provided for the `--vers` flag" ) ) ?;
613
+
614
+ let is_req = "<>=^~" . contains ( first) || v. contains ( '*' ) ;
615
+ if is_req {
616
+ match v. parse :: < VersionReq > ( ) {
617
+ Ok ( v) => Ok ( v) ,
618
+ Err ( _) => bail ! (
619
+ "the `--vers` provided, `{}`, is \
620
+ not a valid semver version requirement\n \n \
621
+ Please have a look at \
622
+ https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html \
623
+ for the correct format",
624
+ v
625
+ ) ,
626
+ }
627
+ } else {
628
+ match v. to_semver ( ) {
629
+ Ok ( v) => Ok ( VersionReq :: exact ( & v) ) ,
630
+ Err ( e) => {
631
+ let mut msg = format ! (
632
+ "the `--vers` provided, `{}`, is \
633
+ not a valid semver version: {}\n ",
634
+ v, e
635
+ ) ;
636
+
637
+ // If it is not a valid version but it is a valid version
638
+ // requirement, add a note to the warning
639
+ if v. parse :: < VersionReq > ( ) . is_ok ( ) {
640
+ msg. push_str ( & format ! (
641
+ "\n if you want to specify semver range, \
642
+ add an explicit qualifier, like ^{}",
643
+ v
644
+ ) ) ;
645
+ }
646
+ bail ! ( msg) ;
647
+ }
648
+ }
496
649
}
497
650
}
498
651
0 commit comments