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

feat(cargo-update): --precise to allow yanked versions #13333

Merged
merged 7 commits into from
Jan 29, 2024
47 changes: 37 additions & 10 deletions src/cargo/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,23 +748,27 @@ impl<'cfg> Source for RegistrySource<'cfg> {
.precise_registry_version(dep.package_name().as_str())
.filter(|(c, _)| req.matches(c))
{
req.update_precise(&requested);
req.precise_to(&requested);
}

let mut called = false;
let callback = &mut |s| {
called = true;
f(s);
};

// If this is a locked dependency, then it came from a lock file and in
// theory the registry is known to contain this version. If, however, we
// come back with no summaries, then our registry may need to be
// updated, so we fall back to performing a lazy update.
if kind == QueryKind::Exact && req.is_locked() && !self.ops.is_updated() {
debug!("attempting query without update");
let mut called = false;
ready!(self
.index
.query_inner(dep.package_name(), &req, &mut *self.ops, &mut |s| {
if dep.matches(s.as_summary()) {
// We are looking for a package from a lock file so we do not care about yank
called = true;
f(s);
callback(s)
}
},))?;
if called {
Expand All @@ -775,24 +779,47 @@ impl<'cfg> Source for RegistrySource<'cfg> {
Poll::Pending
}
} else {
let mut called = false;
let mut precise_yanked_in_use = false;
ready!(self
.index
.query_inner(dep.package_name(), &req, &mut *self.ops, &mut |s| {
let matched = match kind {
QueryKind::Exact => dep.matches(s.as_summary()),
QueryKind::Fuzzy => true,
};
if !matched {
return;
}
// Next filter out all yanked packages. Some yanked packages may
// leak through if they're in a whitelist (aka if they were
// previously in `Cargo.lock`
if matched
&& (!s.is_yanked() || self.yanked_whitelist.contains(&s.package_id()))
{
f(s);
called = true;
if !s.is_yanked() {
callback(s);
} else if self.yanked_whitelist.contains(&s.package_id()) {
callback(s);
} else if req.is_precise() {
precise_yanked_in_use = true;
if self.config.cli_unstable().unstable_options {
callback(s);
}
}
}))?;
if precise_yanked_in_use {
self.config
.cli_unstable()
.fail_if_stable_opt("--precise <yanked-version>", 4225)?;
let name = dep.package_name();
let version = req
.precise_version()
.expect("--precise <yanked-version> in use");
let source = self.source_id();
let mut shell = self.config.shell();
shell.warn(format_args!(
"yanked package `{name}@{version}` is selected by the `--precise` flag from {source}",
))?;
shell.note("it is not recommended to depend on a yanked version")?;
shell.note("if possible, try other SemVer-compatbile versions")?;
}
if called {
return Poll::Ready(Ok(()));
}
Expand Down
37 changes: 27 additions & 10 deletions src/cargo/util/semver_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ pub enum OptVersionReq {
/// The exact locked version and the original version requirement.
Locked(Version, VersionReq),
/// The exact requested version and the original version requirement.
UpdatePrecise(Version, VersionReq),
///
/// This looks identical to [`OptVersionReq::Locked`] but has a different
/// meaning, and is used for the `--precise` field of `cargo update`.
/// See comments in [`OptVersionReq::matches`] for more.
Precise(Version, VersionReq),
}

impl OptVersionReq {
Expand All @@ -51,7 +55,7 @@ impl OptVersionReq {
pub fn is_exact(&self) -> bool {
match self {
OptVersionReq::Any => false,
OptVersionReq::Req(req) | OptVersionReq::UpdatePrecise(_, req) => {
OptVersionReq::Req(req) | OptVersionReq::Precise(_, req) => {
req.comparators.len() == 1 && {
let cmp = &req.comparators[0];
cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some()
Expand All @@ -67,21 +71,34 @@ impl OptVersionReq {
let version = version.clone();
*self = match self {
Any => Locked(version, VersionReq::STAR),
Req(req) | Locked(_, req) | UpdatePrecise(_, req) => Locked(version, req.clone()),
Req(req) | Locked(_, req) | Precise(_, req) => Locked(version, req.clone()),
};
}

pub fn update_precise(&mut self, version: &Version) {
/// Makes the requirement precise to the requested version.
///
/// This is used for the `--precise` field of `cargo update`.
pub fn precise_to(&mut self, version: &Version) {
use OptVersionReq::*;
let version = version.clone();
*self = match self {
Any => UpdatePrecise(version, VersionReq::STAR),
Req(req) | Locked(_, req) | UpdatePrecise(_, req) => {
UpdatePrecise(version, req.clone())
}
Any => Precise(version, VersionReq::STAR),
Req(req) | Locked(_, req) | Precise(_, req) => Precise(version, req.clone()),
};
}

pub fn is_precise(&self) -> bool {
matches!(self, OptVersionReq::Precise(..))
}

/// Gets the version to which this req is precise to, if any.
pub fn precise_version(&self) -> Option<&Version> {
match self {
OptVersionReq::Precise(version, _) => Some(version),
_ => None,
}
}

pub fn is_locked(&self) -> bool {
matches!(self, OptVersionReq::Locked(..))
}
Expand All @@ -108,7 +125,7 @@ impl OptVersionReq {
// we should not silently use `1.0.0+foo` even though they have the same version.
v == version
}
OptVersionReq::UpdatePrecise(v, _) => {
OptVersionReq::Precise(v, _) => {
// This is used for the `--precise` field of cargo update.
//
// Unfortunately crates.io allowed versions to differ only
Expand All @@ -135,7 +152,7 @@ impl Display for OptVersionReq {
OptVersionReq::Any => f.write_str("*"),
OptVersionReq::Req(req)
| OptVersionReq::Locked(_, req)
| OptVersionReq::UpdatePrecise(_, req) => Display::fmt(req, f),
| OptVersionReq::Precise(_, req) => Display::fmt(req, f),
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions tests/testsuite/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1370,3 +1370,64 @@ fn update_precise_git_revisions() {
assert!(p.read_lockfile().contains(&head_id));
assert!(!p.read_lockfile().contains(&tag_commit_id));
}

#[cargo_test]
fn precise_yanked() {
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.1.1").yanked(true).publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"

[dependencies]
bar = "0.1"
"#,
)
.file("src/lib.rs", "")
.build();

p.cargo("generate-lockfile").run();

// Use non-yanked version.
let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.0\""));

p.cargo("update --precise 0.1.1 bar")
.with_status(101)
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[ERROR] failed to get `bar` as a dependency of package `foo v0.0.0 ([CWD])`

Caused by:
failed to query replaced source registry `crates-io`

Caused by:
the `--precise <yanked-version>` flag is unstable[..]
See [..]
See [..]
",
)
.run();

p.cargo("update --precise 0.1.1 bar")
.masquerade_as_nightly_cargo(&["--precise <yanked-version>"])
.arg("-Zunstable-options")
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[WARNING] yanked package `[email protected]` is selected by the `--precise` flag from registry `dummy-registry`
[NOTE] it is not recommended to depend on a yanked version
[NOTE] if possible, try other SemVer-compatbile versions
[UPDATING] bar v0.1.0 -> v0.1.1
",
)
.run();

// Use yanked version.
let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"bar\"\nversion = \"0.1.1\""));
}