Skip to content
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

Consider rust-version when selecting packages for cargo add #12078

Merged
merged 4 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/bin/cargo/commands/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ The package will be removed from your features.")
Example uses:
- Depending on multiple versions of a crate
- Depend on crates with the same name from different registries"),
flag(
"ignore-rust-version",
"Ignore `rust-version` specification in packages (unstable)"
cassaundra marked this conversation as resolved.
Show resolved Hide resolved
),
])
.arg_manifest_path()
.arg_package("Package to modify")
Expand Down Expand Up @@ -188,12 +192,24 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {

let dependencies = parse_dependencies(config, args)?;

let ignore_rust_version = args.flag("ignore-rust-version");
if ignore_rust_version && !config.cli_unstable().msrv_policy {
return Err(CliError::new(
anyhow::format_err!(
"`--ignore-rust-version` is unstable; pass `-Zmsrv-policy` to enable support for it"
),
101,
));
}
let honor_rust_version = !ignore_rust_version;

let options = AddOptions {
config,
spec,
dependencies,
section,
dry_run,
honor_rust_version,
};
add(&ws, &options)?;

Expand Down
130 changes: 115 additions & 15 deletions src/cargo/ops/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -518,8 +531,10 @@ fn get_existing_dependency(
}

fn get_latest_dependency(
spec: &Package,
dependency: &Dependency,
_flag_allow_prerelease: bool,
Copy link
Contributor Author

@cassaundra cassaundra May 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

side note: no clue what this _flag_allow_prerelease is. doesn't appear to ever have been used in cargo. I'm guessing a holdover from cargo-edit?

Copy link
Contributor

Choose a reason for hiding this comment

The 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> {
Expand All @@ -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);
Expand All @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions src/doc/man/cargo-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ crates, the features for a specific crate may be enabled with
which enables all specified features.
{{/option}}

{{#option "`--ignore-rust-version`" }}
Ignore `rust-version` specification in packages.

This option is unstable and available only on the
[nightly channel](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html)
and requires the `-Z unstable-options` flag to enable.
See <https://github.com/rust-lang/cargo/issues/5579> for more information.
{{/option}}

{{/options}}


Expand Down
9 changes: 9 additions & 0 deletions src/doc/man/generated_txt/cargo-add.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ OPTIONS
be enabled with package-name/feature-name syntax. This flag may be
specified multiple times, which enables all specified features.

--ignore-rust-version
Ignore rust-version specification in packages.

This option is unstable and available only on the nightly channel
<https://doc.rust-lang.org/book/appendix-07-nightly-rust.html> and
requires the -Z unstable-options flag to enable. See
<https://github.com/rust-lang/cargo/issues/5579> for more
information.

Display Options
-v, --verbose
Use verbose output. May be specified twice for “very verbose”
Expand Down
8 changes: 8 additions & 0 deletions src/doc/src/commands/cargo-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ crates, the features for a specific crate may be enabled with
which enables all specified features.</dd>


<dt class="option-term" id="option-cargo-add---ignore-rust-version"><a class="option-anchor" href="#option-cargo-add---ignore-rust-version"></a><code>--ignore-rust-version</code></dt>
<dd class="option-desc">Ignore <code>rust-version</code> specification in packages.</p>
<p>This option is unstable and available only on the
<a href="https://doc.rust-lang.org/book/appendix-07-nightly-rust.html">nightly channel</a>
and requires the <code>-Z unstable-options</code> flag to enable.
See <a href="https://github.com/rust-lang/cargo/issues/5579">https://github.com/rust-lang/cargo/issues/5579</a> for more information.</dd>


</dl>


Expand Down
10 changes: 10 additions & 0 deletions src/etc/man/cargo-add.1
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ crates, the features for a specific crate may be enabled with
\fBpackage\-name/feature\-name\fR syntax. This flag may be specified multiple times,
which enables all specified features.
.RE
.sp
\fB\-\-ignore\-rust\-version\fR
.RS 4
Ignore \fBrust\-version\fR specification in packages.
.sp
This option is unstable and available only on the
\fInightly channel\fR <https://doc.rust\-lang.org/book/appendix\-07\-nightly\-rust.html>
and requires the \fB\-Z unstable\-options\fR flag to enable.
See <https://github.com/rust\-lang/cargo/issues/5579> for more information.
.RE
.SS "Display Options"
.sp
\fB\-v\fR,
Expand Down
4 changes: 4 additions & 0 deletions tests/testsuite/cargo_add/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ mod quiet;
mod registry;
mod rename;
mod require_weak;
mod rust_version_ignore;
mod rust_version_incompatible;
mod rust_version_latest;
mod rust_version_older;
mod sorted_table_with_dotted_item;
mod target;
mod target_cfg;
Expand Down
6 changes: 6 additions & 0 deletions tests/testsuite/cargo_add/rust_version_ignore/in/Cargo.toml
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"
Empty file.
36 changes: 36 additions & 0 deletions tests/testsuite/cargo_add/rust_version_ignore/mod.rs
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);
}
9 changes: 9 additions & 0 deletions tests/testsuite/cargo_add/rust_version_ignore/out/Cargo.toml
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"
Empty file.
2 changes: 2 additions & 0 deletions tests/testsuite/cargo_add/rust_version_ignore/stderr.log
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.
Empty file.
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"
Empty file.
Loading