@@ -51,6 +51,8 @@ pub struct AddOptions<'a> {
5151 pub section : DepTable ,
5252 /// Act as if dependencies will be added
5353 pub dry_run : bool ,
54+ /// Whether the minimum supported Rust version should be considered during resolution
55+ pub honor_rust_version : bool ,
5456}
5557
5658/// Add dependencies to a manifest
@@ -86,7 +88,9 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
8688 & manifest,
8789 raw,
8890 workspace,
91+ & options. spec ,
8992 & options. section ,
93+ options. honor_rust_version ,
9094 options. config ,
9195 & mut registry,
9296 )
@@ -256,7 +260,9 @@ fn resolve_dependency(
256260 manifest : & LocalManifest ,
257261 arg : & DepOp ,
258262 ws : & Workspace < ' _ > ,
263+ spec : & Package ,
259264 section : & DepTable ,
265+ honor_rust_version : bool ,
260266 config : & Config ,
261267 registry : & mut PackageRegistry < ' _ > ,
262268) -> CargoResult < DependencyUI > {
@@ -368,7 +374,14 @@ fn resolve_dependency(
368374 }
369375 dependency = dependency. set_source ( src) ;
370376 } else {
371- let latest = get_latest_dependency ( & dependency, false , config, registry) ?;
377+ let latest = get_latest_dependency (
378+ spec,
379+ & dependency,
380+ false ,
381+ honor_rust_version,
382+ config,
383+ registry,
384+ ) ?;
372385
373386 if dependency. name != latest. name {
374387 config. shell ( ) . warn ( format ! (
@@ -518,8 +531,10 @@ fn get_existing_dependency(
518531}
519532
520533fn get_latest_dependency (
534+ spec : & Package ,
521535 dependency : & Dependency ,
522536 _flag_allow_prerelease : bool ,
537+ honor_rust_version : bool ,
523538 config : & Config ,
524539 registry : & mut PackageRegistry < ' _ > ,
525540) -> CargoResult < Dependency > {
@@ -529,27 +544,87 @@ fn get_latest_dependency(
529544 unreachable ! ( "registry dependencies required, found a workspace dependency" ) ;
530545 }
531546 MaybeWorkspace :: Other ( query) => {
532- let possibilities = loop {
547+ let mut possibilities = loop {
533548 match registry. query_vec ( & query, QueryKind :: Fuzzy ) {
534549 std:: task:: Poll :: Ready ( res) => {
535550 break res?;
536551 }
537552 std:: task:: Poll :: Pending => registry. block_until_ready ( ) ?,
538553 }
539554 } ;
540- let latest = possibilities
541- . iter ( )
542- . max_by_key ( |s| {
543- // Fallback to a pre-release if no official release is available by sorting them as
544- // less.
545- let stable = s. version ( ) . pre . is_empty ( ) ;
546- ( stable, s. version ( ) )
547- } )
548- . ok_or_else ( || {
549- anyhow:: format_err!(
550- "the crate `{dependency}` could not be found in registry index."
551- )
552- } ) ?;
555+
556+ possibilities. sort_by_key ( |s| {
557+ // Fallback to a pre-release if no official release is available by sorting them as
558+ // less.
559+ let stable = s. version ( ) . pre . is_empty ( ) ;
560+ ( stable, s. version ( ) . clone ( ) )
561+ } ) ;
562+
563+ let mut latest = possibilities. last ( ) . ok_or_else ( || {
564+ anyhow:: format_err!(
565+ "the crate `{dependency}` could not be found in registry index."
566+ )
567+ } ) ?;
568+
569+ if config. cli_unstable ( ) . msrv_policy && honor_rust_version {
570+ fn parse_msrv ( rust_version : impl AsRef < str > ) -> ( u64 , u64 , u64 ) {
571+ // HACK: `rust-version` is a subset of the `VersionReq` syntax that only ever
572+ // has one comparator with a required minor and optional patch, and uses no
573+ // other features. If in the future this syntax is expanded, this code will need
574+ // to be updated.
575+ let version_req = semver:: VersionReq :: parse ( rust_version. as_ref ( ) ) . unwrap ( ) ;
576+ assert ! ( version_req. comparators. len( ) == 1 ) ;
577+ let comp = & version_req. comparators [ 0 ] ;
578+ assert_eq ! ( comp. op, semver:: Op :: Caret ) ;
579+ assert_eq ! ( comp. pre, semver:: Prerelease :: EMPTY ) ;
580+ ( comp. major , comp. minor . unwrap_or ( 0 ) , comp. patch . unwrap_or ( 0 ) )
581+ }
582+
583+ if let Some ( req_msrv) = spec. rust_version ( ) . map ( parse_msrv) {
584+ let msrvs = possibilities
585+ . iter ( )
586+ . map ( |s| ( s, s. rust_version ( ) . map ( parse_msrv) ) )
587+ . collect :: < Vec < _ > > ( ) ;
588+
589+ // Find the latest version of the dep which has a compatible rust-version. To
590+ // determine whether or not one rust-version is compatible with another, we
591+ // compare the lowest possible versions they could represent, and treat
592+ // candidates without a rust-version as compatible by default.
593+ let ( latest_msrv, _) = msrvs
594+ . iter ( )
595+ . filter ( |( _, v) | v. map ( |msrv| req_msrv >= msrv) . unwrap_or ( true ) )
596+ . last ( )
597+ . ok_or_else ( || {
598+ // Failing that, try to find the highest version with the lowest
599+ // rust-version to report to the user.
600+ let lowest_candidate = msrvs
601+ . iter ( )
602+ . min_set_by_key ( |( _, v) | v)
603+ . iter ( )
604+ . map ( |( s, _) | s)
605+ . max_by_key ( |s| s. version ( ) ) ;
606+ rust_version_incompat_error (
607+ & dependency. name ,
608+ spec. rust_version ( ) . unwrap ( ) ,
609+ lowest_candidate. copied ( ) ,
610+ )
611+ } ) ?;
612+
613+ if latest_msrv. version ( ) < latest. version ( ) {
614+ config. shell ( ) . warn ( format_args ! (
615+ "ignoring `{dependency}@{latest_version}` (which has a rust-version of \
616+ {latest_rust_version}) to satisfy this package's rust-version of \
617+ {rust_version} (use `--ignore-rust-version` to override)",
618+ latest_version = latest. version( ) ,
619+ latest_rust_version = latest. rust_version( ) . unwrap( ) ,
620+ rust_version = spec. rust_version( ) . unwrap( ) ,
621+ ) ) ?;
622+
623+ latest = latest_msrv;
624+ }
625+ }
626+ }
627+
553628 let mut dep = Dependency :: from ( latest) ;
554629 if let Some ( reg_name) = dependency. registry . as_deref ( ) {
555630 dep = dep. set_registry ( reg_name) ;
@@ -559,6 +634,31 @@ fn get_latest_dependency(
559634 }
560635}
561636
637+ fn rust_version_incompat_error (
638+ dep : & str ,
639+ rust_version : & str ,
640+ lowest_rust_version : Option < & Summary > ,
641+ ) -> anyhow:: Error {
642+ let mut error_msg = format ! (
643+ "could not find version of crate `{dep}` that satisfies this package's rust-version of \
644+ {rust_version}\n \
645+ help: use `--ignore-rust-version` to override this behavior"
646+ ) ;
647+
648+ if let Some ( lowest) = lowest_rust_version {
649+ // rust-version must be present for this candidate since it would have been selected as
650+ // compatible previously if it weren't.
651+ let version = lowest. version ( ) ;
652+ let rust_version = lowest. rust_version ( ) . unwrap ( ) ;
653+ error_msg. push_str ( & format ! (
654+ "\n note: the lowest rust-version available for `{dep}` is {rust_version}, used in \
655+ version {version}"
656+ ) ) ;
657+ }
658+
659+ anyhow:: format_err!( error_msg)
660+ }
661+
562662fn select_package (
563663 dependency : & Dependency ,
564664 config : & Config ,
0 commit comments