Skip to content

Commit

Permalink
Auto merge of rust-lang#13333 - weihanglo:precise-yank, r=Eh2406
Browse files Browse the repository at this point in the history
feat(cargo-update): `--precise` to allow yanked versions
  • Loading branch information
bors committed Jan 29, 2024
2 parents 49433a8 + caeaaa5 commit 3e1a2dd
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 22 deletions.
54 changes: 44 additions & 10 deletions src/cargo/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ pub struct RegistrySource<'cfg> {
/// Otherwise, the resolver would think that those entries no longer
/// exist, and it would trigger updates to unrelated packages.
yanked_whitelist: HashSet<PackageId>,
/// Yanked versions that have already been selected during queries.
///
/// As of this writing, this is for not emitting the `--precise <yanked>`
/// warning twice, with the assumption of (`dep.package_name()` + `--precise`
/// version) being sufficient to uniquely identify the same query result.
selected_precise_yanked: HashSet<(InternedString, semver::Version)>,
}

/// The [`config.json`] file stored in the index.
Expand Down Expand Up @@ -531,6 +537,7 @@ impl<'cfg> RegistrySource<'cfg> {
index: index::RegistryIndex::new(source_id, ops.index_path(), config),
yanked_whitelist: yanked_whitelist.clone(),
ops,
selected_precise_yanked: HashSet::new(),
}
}

Expand Down Expand Up @@ -748,23 +755,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 +786,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");
if self.selected_precise_yanked.insert((name, version.clone())) {
let mut shell = self.config.shell();
shell.warn(format_args!(
"selected package `{name}@{version}` was yanked by the author"
))?;
shell.note("if possible, try a compatible non-yanked version")?;
}
}
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
4 changes: 4 additions & 0 deletions src/doc/man/cargo-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ Cannot be used with `--precise`.
When used with _spec_, allows you to specify a specific version number to set
the package to. If the package comes from a git repository, this can be a git
revision (such as a SHA hash or tag).

While not recommended, you can specify a yanked version of a package (nightly only).
When possible, try other non-yanked SemVer-compatible versions or seek help
from the maintainers of the package.
{{/option}}

{{#option "`-w`" "`--workspace`" }}
Expand Down
5 changes: 5 additions & 0 deletions src/doc/man/generated_txt/cargo-update.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ OPTIONS
to set the package to. If the package comes from a git repository,
this can be a git revision (such as a SHA hash or tag).

While not recommended, you can specify a yanked version of a package
(nightly only). When possible, try other non-yanked
SemVer-compatible versions or seek help from the maintainers of the
package.

-w, --workspace
Attempt to update only packages defined in the workspace. Other
packages are updated only if they don’t already exist in the
Expand Down
5 changes: 4 additions & 1 deletion src/doc/src/commands/cargo-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ Cannot be used with <code>--precise</code>.</dd>
<dt class="option-term" id="option-cargo-update---precise"><a class="option-anchor" href="#option-cargo-update---precise"></a><code>--precise</code> <em>precise</em></dt>
<dd class="option-desc">When used with <em>spec</em>, allows you to specify a specific version number to set
the package to. If the package comes from a git repository, this can be a git
revision (such as a SHA hash or tag).</dd>
revision (such as a SHA hash or tag).</p>
<p>While not recommended, you can specify a yanked version of a package (nightly only).
When possible, try other non-yanked SemVer-compatible versions or seek help
from the maintainers of the package.</dd>


<dt class="option-term" id="option-cargo-update--w"><a class="option-anchor" href="#option-cargo-update--w"></a><code>-w</code></dt>
Expand Down
4 changes: 3 additions & 1 deletion src/doc/src/reference/resolver.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,11 @@ the `links` field if your library is in common use.

[Yanked releases][yank] are those that are marked that they should not be
used. When the resolver is building the graph, it will ignore all yanked
releases unless they already exist in the `Cargo.lock` file.
releases unless they already exist in the `Cargo.lock` file or are explicitly
requested by the [`--precise`] flag of `cargo update` (nightly only).

[yank]: publishing.md#cargo-yank
[`--precise`]: ../commands/cargo-update.md#option-cargo-update---precise

## Dependency updates

Expand Down
4 changes: 4 additions & 0 deletions src/etc/man/cargo-update.1
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ Cannot be used with \fB\-\-precise\fR\&.
When used with \fIspec\fR, allows you to specify a specific version number to set
the package to. If the package comes from a git repository, this can be a git
revision (such as a SHA hash or tag).
.sp
While not recommended, you can specify a yanked version of a package (nightly only).
When possible, try other non\-yanked SemVer\-compatible versions or seek help
from the maintainers of the package.
.RE
.sp
\fB\-w\fR,
Expand Down
103 changes: 103 additions & 0 deletions tests/testsuite/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1370,3 +1370,106 @@ 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] selected package `[email protected]` was yanked by the author
[NOTE] if possible, try a compatible non-yanked version
[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\""));
}

#[cargo_test]
fn precise_yanked_multiple_presence() {
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"
baz = { package = "bar", version = "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")
.masquerade_as_nightly_cargo(&["--precise <yanked-version>"])
.arg("-Zunstable-options")
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[WARNING] selected package `[email protected]` was yanked by the author
[NOTE] if possible, try a compatible non-yanked version
[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\""));
}

0 comments on commit 3e1a2dd

Please sign in to comment.