Skip to content

Commit

Permalink
Allow precise update to prerelease.
Browse files Browse the repository at this point in the history
This is a very early implementation and many boundary conditions are not taken into account.
  • Loading branch information
linyihai committed Mar 22, 2024
1 parent 6972630 commit 6e80de3
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 3 deletions.
7 changes: 7 additions & 0 deletions src/cargo/core/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,13 @@ impl Dependency {
self.matches_id(sum.package_id())
}

pub fn matches_prerelease(&self, sum: &Summary) -> bool {
let id = sum.package_id();
self.inner.name == id.name()
&& (self.inner.only_match_name
|| (self.inner.req.matches_prerelease(id.version()) && self.inner.source_id == id.source_id()))
}

/// Returns `true` if the package (`id`) can fulfill this dependency request.
pub fn matches_ignoring_source(&self, id: PackageId) -> bool {
self.package_name() == id.name() && self.version_req().matches(id.version())
Expand Down
42 changes: 39 additions & 3 deletions src/cargo/sources/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,9 @@ use crate::sources::source::QueryKind;
use crate::sources::source::Source;
use crate::sources::PathSource;
use crate::util::cache_lock::CacheLockMode;
use crate::util::hex;
use crate::util::interning::InternedString;
use crate::util::network::PollExt;
use crate::util::{hex, VersionExt};
use crate::util::{restricted_names, CargoResult, Filesystem, GlobalContext, LimitErrorReader};

/// The `.cargo-ok` file is used to track if the source is already unpacked.
Expand Down Expand Up @@ -267,6 +267,7 @@ pub struct RegistrySource<'gctx> {
/// 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)>,
selected_precise_prerelease: HashSet<(InternedString, semver::Version)>,
}

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

Expand Down Expand Up @@ -752,7 +754,13 @@ impl<'gctx> Source for RegistrySource<'gctx> {
if let Some((_, requested)) = self
.source_id
.precise_registry_version(dep.package_name().as_str())
.filter(|(c, _)| req.matches(c))
.filter(|(c, _)| {
if self.gctx.cli_unstable().precise_pre_release {
req.matches_prerelease(c)
} else {
req.matches(c)
}
})
{
req.precise_to(&requested);
}
Expand Down Expand Up @@ -786,11 +794,19 @@ impl<'gctx> Source for RegistrySource<'gctx> {
}
} else {
let mut precise_yanked_in_use = false;
let mut precise_prerelease_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::Exact => {
if self.gctx.cli_unstable().precise_pre_release {
dep.matches_prerelease(s.as_summary())
} else {
dep.matches(s.as_summary())
}
}
QueryKind::Alternatives => true,
QueryKind::Normalized => true,
};
Expand All @@ -801,6 +817,10 @@ impl<'gctx> Source for RegistrySource<'gctx> {
// leak through if they're in a whitelist (aka if they were
// previously in `Cargo.lock`
if !s.is_yanked() {
if req.is_precise() && s.as_summary().package_id().version().is_prerelease()
{
precise_prerelease_in_use = true
}
callback(s);
} else if self.yanked_whitelist.contains(&s.package_id()) {
callback(s);
Expand All @@ -827,6 +847,22 @@ impl<'gctx> Source for RegistrySource<'gctx> {
shell.note("if possible, try a compatible non-yanked version")?;
}
}
if precise_prerelease_in_use && self.gctx.cli_unstable().precise_pre_release {
let name = dep.package_name();
let version = req
.precise_version()
.expect("--precise <prerelease-version> in use");
if self
.selected_precise_prerelease
.insert((name, version.clone()))
{
let mut shell = self.gctx.shell();
shell.warn(format_args!(
"selected prerelease package `{name}@{version}`"
))?;
shell.note("if possible, try a compatible non-prerelease version")?;
}
}
if called {
return Poll::Ready(Ok(()));
}
Expand Down
13 changes: 13 additions & 0 deletions src/cargo/util/semver_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ impl OptVersionReq {
}
}

/// Since Semver does not support prerelease versions,
/// the simplest implementation is taken here without comparing the prerelease section.
/// The logic here is temporary, we'll have to consider more boundary conditions later,
/// and we're not sure if this part of the functionality should be implemented in semver or cargo.
pub fn matches_prerelease(&self, version: &Version) -> bool {
if version.is_prerelease() {
let mut version = version.clone();
version.pre = semver::Prerelease::EMPTY;
return self.matches(&version);
}
self.matches(version)
}

pub fn matches(&self, version: &Version) -> bool {
match self {
OptVersionReq::Any => true,
Expand Down
89 changes: 89 additions & 0 deletions tests/testsuite/precise_pre_release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,92 @@ fn feature_exists() {
.with_stderr("")
.run()
}

#[cargo_test]
fn update_pre_release() {
cargo_test_support::registry::init();

for version in ["0.1.1", "0.1.2-pre.0"] {
cargo_test_support::registry::Package::new("my-dependency", version).publish();
}

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "package"
[dependencies]
my-dependency = "0.1.1"
"#,
)
.file("src/lib.rs", "")
.build();

p.cargo("update -p my-dependency --precise 0.1.2-pre.0 -Zprecise-pre-release")
.masquerade_as_nightly_cargo(&["precise-pre-release"])
// This error is suffering from #12579 but still demonstrates that updating to
// a pre-release does not work on stable
.with_stderr(
r#"[UPDATING] `dummy-registry` index
[WARNING] selected prerelease package `my-dependency@0.1.2-pre.0`
[NOTE] if possible, try a compatible non-prerelease version
[UPDATING] my-dependency v0.1.1 -> v0.1.2-pre.0
"#,
)
.run();
// Use yanked version.
let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"my-dependency\"\nversion = \"0.1.2-pre.0\""));
}

#[cargo_test]
fn update_pre_release_differ() {
cargo_test_support::registry::init();

for version in ["0.1.2", "0.1.2-pre.0", "0.1.2-pre.1"] {
cargo_test_support::registry::Package::new("my-dependency", version).publish();
}

let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "package"
[dependencies]
my-dependency = "0.1.2"
"#,
)
.file("src/lib.rs", "")
.build();

p.cargo("update -p my-dependency --precise 0.1.2-pre.0 -Zprecise-pre-release")
.masquerade_as_nightly_cargo(&["precise-pre-release"])
// This error is suffering from #12579 but still demonstrates that updating to
// a pre-release does not work on stable
.with_stderr(
r#"[UPDATING] `dummy-registry` index
[WARNING] selected prerelease package `my-dependency@0.1.2-pre.0`
[NOTE] if possible, try a compatible non-prerelease version
[DOWNGRADING] my-dependency v0.1.2 -> v0.1.2-pre.0
"#,
)
.run();

p.cargo("update -p my-dependency --precise 0.1.2-pre.1 -Zprecise-pre-release")
.masquerade_as_nightly_cargo(&["precise-pre-release"])
// This error is suffering from #12579 but still demonstrates that updating to
// a pre-release does not work on stable
.with_stderr(
r#"[UPDATING] `dummy-registry` index
[WARNING] selected prerelease package `my-dependency@0.1.2-pre.1`
[NOTE] if possible, try a compatible non-prerelease version
[UPDATING] my-dependency v0.1.2-pre.0 -> v0.1.2-pre.1
"#,
)
.run();

let lockfile = p.read_lockfile();
assert!(lockfile.contains("\nname = \"my-dependency\"\nversion = \"0.1.2-pre.1\""));
}

0 comments on commit 6e80de3

Please sign in to comment.