Skip to content

Commit 4a96edc

Browse files
committed
Consider rust-version in cargo add
When `-Zmsrv-policy` is enabled, try to select dependencies which satisfy the target package's `rust-version` field (if present). If the selected version is not also the latest, emit a warning to the user about this discrepancy. Dependency versions without a `rust-version` are considered compatible by default. Implements #10653.
1 parent feb9bcf commit 4a96edc

File tree

1 file changed

+102
-15
lines changed

1 file changed

+102
-15
lines changed

src/cargo/ops/cargo_add/mod.rs

Lines changed: 102 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
8686
&manifest,
8787
raw,
8888
workspace,
89+
&options.spec,
8990
&options.section,
9091
options.config,
9192
&mut registry,
@@ -256,6 +257,7 @@ fn resolve_dependency(
256257
manifest: &LocalManifest,
257258
arg: &DepOp,
258259
ws: &Workspace<'_>,
260+
spec: &Package,
259261
section: &DepTable,
260262
config: &Config,
261263
registry: &mut PackageRegistry<'_>,
@@ -368,7 +370,7 @@ fn resolve_dependency(
368370
}
369371
dependency = dependency.set_source(src);
370372
} else {
371-
let latest = get_latest_dependency(&dependency, false, config, registry)?;
373+
let latest = get_latest_dependency(spec, &dependency, false, config, registry)?;
372374

373375
if dependency.name != latest.name {
374376
config.shell().warn(format!(
@@ -518,6 +520,7 @@ fn get_existing_dependency(
518520
}
519521

520522
fn get_latest_dependency(
523+
spec: &Package,
521524
dependency: &Dependency,
522525
_flag_allow_prerelease: bool,
523526
config: &Config,
@@ -529,27 +532,87 @@ fn get_latest_dependency(
529532
unreachable!("registry dependencies required, found a workspace dependency");
530533
}
531534
MaybeWorkspace::Other(query) => {
532-
let possibilities = loop {
535+
let mut possibilities = loop {
533536
match registry.query_vec(&query, QueryKind::Fuzzy) {
534537
std::task::Poll::Ready(res) => {
535538
break res?;
536539
}
537540
std::task::Poll::Pending => registry.block_until_ready()?,
538541
}
539542
};
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-
})?;
543+
544+
possibilities.sort_by_key(|s| {
545+
// Fallback to a pre-release if no official release is available by sorting them as
546+
// less.
547+
let stable = s.version().pre.is_empty();
548+
(stable, s.version().clone())
549+
});
550+
551+
let mut latest = possibilities.last().ok_or_else(|| {
552+
anyhow::format_err!(
553+
"the crate `{dependency}` could not be found in registry index."
554+
)
555+
})?;
556+
557+
if config.cli_unstable().msrv_policy {
558+
fn parse_msrv(rust_version: impl AsRef<str>) -> (u64, u64, u64) {
559+
// HACK: `rust-version` is a subset of the `VersionReq` syntax that only ever
560+
// has one comparator with a required minor and optional patch, and uses no
561+
// other features. If in the future this syntax is expanded, this code will need
562+
// to be updated.
563+
let version_req = semver::VersionReq::parse(rust_version.as_ref()).unwrap();
564+
assert!(version_req.comparators.len() == 1);
565+
let comp = &version_req.comparators[0];
566+
assert_eq!(comp.op, semver::Op::Caret);
567+
assert_eq!(comp.pre, semver::Prerelease::EMPTY);
568+
(comp.major, comp.minor.unwrap_or(0), comp.patch.unwrap_or(0))
569+
}
570+
571+
if let Some(req_msrv) = spec.rust_version().map(parse_msrv) {
572+
let msrvs = possibilities
573+
.iter()
574+
.map(|s| (s, s.rust_version().map(parse_msrv)))
575+
.collect::<Vec<_>>();
576+
577+
// Find the latest version of the dep which has a compatible rust-version. To
578+
// determine whether or not one rust-version is compatible with another, we
579+
// compare the lowest possible versions they could represent, and treat
580+
// candidates without a rust-version as compatible by default.
581+
let (latest_msrv, _) = msrvs
582+
.iter()
583+
.filter(|(_, v)| v.map(|msrv| req_msrv >= msrv).unwrap_or(true))
584+
.last()
585+
.ok_or_else(|| {
586+
// Failing that, try to find the highest version with the lowest
587+
// rust-version to report to the user.
588+
let lowest_candidate = msrvs
589+
.iter()
590+
.min_set_by_key(|(_, v)| v)
591+
.iter()
592+
.map(|(s, _)| s)
593+
.max_by_key(|s| s.version());
594+
rust_version_incompat_error(
595+
&dependency.name,
596+
spec.rust_version().unwrap(),
597+
lowest_candidate.copied(),
598+
)
599+
})?;
600+
601+
if latest_msrv.version() < latest.version() {
602+
config.shell().warn(format_args!(
603+
"ignoring `{dependency}@{latest_version}` (which has a rust-version of \
604+
{latest_rust_version}) to satisfy this package's rust-version of \
605+
{rust_version}",
606+
latest_version = latest.version(),
607+
latest_rust_version = latest.rust_version().unwrap(),
608+
rust_version = spec.rust_version().unwrap(),
609+
))?;
610+
611+
latest = latest_msrv;
612+
}
613+
}
614+
}
615+
553616
let mut dep = Dependency::from(latest);
554617
if let Some(reg_name) = dependency.registry.as_deref() {
555618
dep = dep.set_registry(reg_name);
@@ -559,6 +622,30 @@ fn get_latest_dependency(
559622
}
560623
}
561624

625+
fn rust_version_incompat_error(
626+
dep: &str,
627+
rust_version: &str,
628+
lowest_rust_version: Option<&Summary>,
629+
) -> anyhow::Error {
630+
let mut error_msg = format!(
631+
"could not find version of crate `{dep}` that satisfies this package's rust-version of \
632+
{rust_version}"
633+
);
634+
635+
if let Some(lowest) = lowest_rust_version {
636+
// rust-version must be present for this candidate since it would have been selected as
637+
// compatible previously if it weren't.
638+
let version = lowest.version();
639+
let rust_version = lowest.rust_version().unwrap();
640+
error_msg.push_str(&format!(
641+
"\nnote: the lowest rust-version available for `{dep}` is {rust_version}, used in \
642+
version {version}"
643+
));
644+
}
645+
646+
anyhow::format_err!(error_msg)
647+
}
648+
562649
fn select_package(
563650
dependency: &Dependency,
564651
config: &Config,

0 commit comments

Comments
 (0)