-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Consider rust-version when selecting packages for cargo add #12078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,8 @@ pub struct AddOptions<'a> { | |
pub section: DepTable, | ||
/// Act as if dependencies will be added | ||
pub dry_run: bool, | ||
/// Whether the minimum supported Rust version should be considered during resolution | ||
pub honor_rust_version: bool, | ||
} | ||
|
||
/// Add dependencies to a manifest | ||
|
@@ -86,7 +88,9 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<( | |
&manifest, | ||
raw, | ||
workspace, | ||
&options.spec, | ||
&options.section, | ||
options.honor_rust_version, | ||
options.config, | ||
&mut registry, | ||
) | ||
|
@@ -256,7 +260,9 @@ fn resolve_dependency( | |
manifest: &LocalManifest, | ||
arg: &DepOp, | ||
ws: &Workspace<'_>, | ||
spec: &Package, | ||
section: &DepTable, | ||
honor_rust_version: bool, | ||
config: &Config, | ||
registry: &mut PackageRegistry<'_>, | ||
) -> CargoResult<DependencyUI> { | ||
|
@@ -368,7 +374,14 @@ 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, | ||
honor_rust_version, | ||
config, | ||
registry, | ||
)?; | ||
|
||
if dependency.name != latest.name { | ||
config.shell().warn(format!( | ||
|
@@ -518,8 +531,10 @@ fn get_existing_dependency( | |
} | ||
|
||
fn get_latest_dependency( | ||
spec: &Package, | ||
dependency: &Dependency, | ||
_flag_allow_prerelease: bool, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. side note: no clue what this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that is a vestige of an old cargo-edit version of cargo-add and can be removed. |
||
honor_rust_version: bool, | ||
config: &Config, | ||
registry: &mut PackageRegistry<'_>, | ||
) -> CargoResult<Dependency> { | ||
|
@@ -529,27 +544,87 @@ 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?; | ||
} | ||
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 && honor_rust_version { | ||
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} (use `--ignore-rust-version` to override)", | ||
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 +634,31 @@ 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}\n\ | ||
help: use `--ignore-rust-version` to override this behavior" | ||
); | ||
|
||
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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[workspace] | ||
|
||
[package] | ||
name = "cargo-list-test-fixture" | ||
version = "0.0.0" | ||
rust-version = "1.68" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use cargo_test_support::compare::assert_ui; | ||
use cargo_test_support::prelude::*; | ||
use cargo_test_support::Project; | ||
|
||
use crate::cargo_add::init_registry; | ||
use cargo_test_support::curr_dir; | ||
|
||
#[cargo_test] | ||
fn case() { | ||
init_registry(); | ||
|
||
cargo_test_support::registry::Package::new("rust-version-user", "0.1.0") | ||
.rust_version("1.66") | ||
.publish(); | ||
cargo_test_support::registry::Package::new("rust-version-user", "0.2.1") | ||
.rust_version("1.72") | ||
.publish(); | ||
|
||
let project = Project::from_template(curr_dir!().join("in")); | ||
let project_root = project.root(); | ||
let cwd = &project_root; | ||
|
||
snapbox::cmd::Command::cargo_ui() | ||
.arg("-Zmsrv-policy") | ||
.arg("add") | ||
.arg("--ignore-rust-version") | ||
.arg_line("rust-version-user") | ||
.current_dir(cwd) | ||
.masquerade_as_nightly_cargo(&["msrv-policy"]) | ||
.assert() | ||
.success() | ||
.stdout_matches_path(curr_dir!().join("stdout.log")) | ||
.stderr_matches_path(curr_dir!().join("stderr.log")); | ||
|
||
assert_ui().subset_matches(curr_dir!().join("out"), &project_root); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[workspace] | ||
|
||
[package] | ||
name = "cargo-list-test-fixture" | ||
version = "0.0.0" | ||
rust-version = "1.68" | ||
|
||
[dependencies] | ||
rust-version-user = "0.2.1" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Updating `dummy-registry` index | ||
Adding rust-version-user v0.2.1 to dependencies. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[workspace] | ||
|
||
[package] | ||
name = "cargo-list-test-fixture" | ||
version = "0.0.0" | ||
rust-version = "1.56" |
Uh oh!
There was an error while loading. Please reload this page.