diff --git a/src/cargo/ops/cargo_add/mod.rs b/src/cargo/ops/cargo_add/mod.rs index 5c519ac09f0c..2d531263d7e9 100644 --- a/src/cargo/ops/cargo_add/mod.rs +++ b/src/cargo/ops/cargo_add/mod.rs @@ -86,6 +86,7 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<( &manifest, raw, workspace, + &options.spec, &options.section, options.config, &mut registry, @@ -256,6 +257,7 @@ fn resolve_dependency( manifest: &LocalManifest, arg: &DepOp, ws: &Workspace<'_>, + spec: &Package, section: &DepTable, config: &Config, registry: &mut PackageRegistry<'_>, @@ -368,7 +370,7 @@ fn resolve_dependency( } dependency = dependency.set_source(src); } else { - let latest = get_latest_dependency(&dependency, false, config, registry)?; + let latest = get_latest_dependency(spec, &dependency, false, config, registry)?; if dependency.name != latest.name { config.shell().warn(format!( @@ -518,6 +520,7 @@ fn get_existing_dependency( } fn get_latest_dependency( + spec: &Package, dependency: &Dependency, _flag_allow_prerelease: bool, config: &Config, @@ -529,7 +532,7 @@ fn get_latest_dependency( unreachable!("registry dependencies required, found a workspace dependency"); } MaybeWorkspace::Other(query) => { - let possibilities = loop { + let mut possibilities = loop { match registry.query_vec(&query, QueryKind::Fuzzy) { std::task::Poll::Ready(res) => { break res?; @@ -537,19 +540,78 @@ fn get_latest_dependency( std::task::Poll::Pending => registry.block_until_ready()?, } }; - let latest = possibilities - .iter() - .max_by_key(|s| { - // Fallback to a pre-release if no official release is available by sorting them as - // less. - let stable = s.version().pre.is_empty(); - (stable, s.version()) - }) - .ok_or_else(|| { - anyhow::format_err!( - "the crate `{dependency}` could not be found in registry index." - ) - })?; + + possibilities.sort_by_key(|s| { + // Fallback to a pre-release if no official release is available by sorting them as + // less. + let stable = s.version().pre.is_empty(); + (stable, s.version().clone()) + }); + + let mut latest = possibilities.last().ok_or_else(|| { + anyhow::format_err!( + "the crate `{dependency}` could not be found in registry index." + ) + })?; + + if config.cli_unstable().msrv_policy { + if let Some((req_major, req_minor, req_patch)) = + spec.rust_version().map(parse_rust_version) + { + let rust_versions = possibilities + .iter() + .map(|s| { + let rust_version = s.rust_version().map(parse_rust_version); + (s, rust_version) + }) + .collect::>(); + + // Find the latest version of the dep which has a compatible rust-version, + // treating candidates without a rust-version as compatible. + let (latest_msrv, _) = rust_versions + .iter() + .filter(|(_, v)| { + v.map(|(major, minor, patch)| { + // Determine whether or not the rust-versions are compatible by + // comparing the lowest possible versions each of them could + // represent. + (req_major, req_minor, req_patch.unwrap_or(0)) + >= (major, minor, patch.unwrap_or(0)) + }) + .unwrap_or(true) + }) + .last() + .ok_or_else(|| { + let mut error_msg = format!( + "could not find version of crate `{dependency}` that satisfies \ + this package's rust-version" + ); + + if let Some((lowest, _)) = rust_versions + .iter() + .filter(|(_, v)| v.is_some()) + .min_by_key(|(_, v)| v) + { + let lowest_version = lowest.rust_version().unwrap(); + error_msg.push_str(&format!( + "\nnote: the lowest rust-version available for `{dependency}` \ + is {lowest_version}" + )); + } + + anyhow::anyhow!(error_msg) + })?; + + if latest_msrv.version() < latest.version() { + config.shell().warn(format_args!( + "selecting older version of `{dependency}` to satisfy this package's \ + rust-version" + ))?; + latest = latest_msrv; + } + } + } + let mut dep = Dependency::from(latest); if let Some(reg_name) = dependency.registry.as_deref() { dep = dep.set_registry(reg_name); @@ -559,6 +621,21 @@ fn get_latest_dependency( } } +fn parse_rust_version(rust_version: impl AsRef) -> (u64, u64, Option) { + // HACK: `rust-version` is a subset of the `VersionReq` syntax that only ever has one comparator + // with a required minor and optional patch, and uses no other features. If in the future this + // syntax is expanded, this code will need to be updated. + + let version = semver::VersionReq::parse(rust_version.as_ref()).unwrap(); + assert!(version.comparators.len() == 1); + + let version = &version.comparators[0]; + assert_eq!(version.op, semver::Op::Caret); + assert_eq!(version.pre, semver::Prerelease::EMPTY); + + (version.major, version.minor.unwrap(), version.patch) +} + fn select_package( dependency: &Dependency, config: &Config,