mirror of https://github.com/rust-lang/cargo
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.
This commit is contained in:
parent
feb9bcf630
commit
4a96edc515
|
@ -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,79 @@ 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 {
|
||||
fn parse_msrv(rust_version: impl AsRef<str>) -> (u64, u64, u64) {
|
||||
// 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_req = semver::VersionReq::parse(rust_version.as_ref()).unwrap();
|
||||
assert!(version_req.comparators.len() == 1);
|
||||
let comp = &version_req.comparators[0];
|
||||
assert_eq!(comp.op, semver::Op::Caret);
|
||||
assert_eq!(comp.pre, semver::Prerelease::EMPTY);
|
||||
(comp.major, comp.minor.unwrap_or(0), comp.patch.unwrap_or(0))
|
||||
}
|
||||
|
||||
if let Some(req_msrv) = spec.rust_version().map(parse_msrv) {
|
||||
let msrvs = possibilities
|
||||
.iter()
|
||||
.map(|s| (s, s.rust_version().map(parse_msrv)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Find the latest version of the dep which has a compatible rust-version. To
|
||||
// determine whether or not one rust-version is compatible with another, we
|
||||
// compare the lowest possible versions they could represent, and treat
|
||||
// candidates without a rust-version as compatible by default.
|
||||
let (latest_msrv, _) = msrvs
|
||||
.iter()
|
||||
.filter(|(_, v)| v.map(|msrv| req_msrv >= msrv).unwrap_or(true))
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
// Failing that, try to find the highest version with the lowest
|
||||
// rust-version to report to the user.
|
||||
let lowest_candidate = msrvs
|
||||
.iter()
|
||||
.min_set_by_key(|(_, v)| v)
|
||||
.iter()
|
||||
.map(|(s, _)| s)
|
||||
.max_by_key(|s| s.version());
|
||||
rust_version_incompat_error(
|
||||
&dependency.name,
|
||||
spec.rust_version().unwrap(),
|
||||
lowest_candidate.copied(),
|
||||
)
|
||||
})?;
|
||||
|
||||
if latest_msrv.version() < latest.version() {
|
||||
config.shell().warn(format_args!(
|
||||
"ignoring `{dependency}@{latest_version}` (which has a rust-version of \
|
||||
{latest_rust_version}) to satisfy this package's rust-version of \
|
||||
{rust_version}",
|
||||
latest_version = latest.version(),
|
||||
latest_rust_version = latest.rust_version().unwrap(),
|
||||
rust_version = spec.rust_version().unwrap(),
|
||||
))?;
|
||||
|
||||
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 +622,30 @@ fn get_latest_dependency(
|
|||
}
|
||||
}
|
||||
|
||||
fn rust_version_incompat_error(
|
||||
dep: &str,
|
||||
rust_version: &str,
|
||||
lowest_rust_version: Option<&Summary>,
|
||||
) -> anyhow::Error {
|
||||
let mut error_msg = format!(
|
||||
"could not find version of crate `{dep}` that satisfies this package's rust-version of \
|
||||
{rust_version}"
|
||||
);
|
||||
|
||||
if let Some(lowest) = lowest_rust_version {
|
||||
// rust-version must be present for this candidate since it would have been selected as
|
||||
// compatible previously if it weren't.
|
||||
let version = lowest.version();
|
||||
let rust_version = lowest.rust_version().unwrap();
|
||||
error_msg.push_str(&format!(
|
||||
"\nnote: the lowest rust-version available for `{dep}` is {rust_version}, used in \
|
||||
version {version}"
|
||||
));
|
||||
}
|
||||
|
||||
anyhow::format_err!(error_msg)
|
||||
}
|
||||
|
||||
fn select_package(
|
||||
dependency: &Dependency,
|
||||
config: &Config,
|
||||
|
|
Loading…
Reference in New Issue