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

If there's a version in the lock file only use that exact version #12772

Merged
merged 3 commits into from
Oct 19, 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
2 changes: 1 addition & 1 deletion src/cargo/core/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ enum Kind {
/// directive that we found in a lockfile, if present.
pub struct LockedPatchDependency {
/// The original `Dependency` directive, except "locked" so it's version
/// requirement is `=foo` and its `SourceId` has a "precise" listed.
/// requirement is Locked to `foo` and its `SourceId` has a "precise" listed.
pub dependency: Dependency,
/// The `PackageId` that was previously found in a lock file which
/// `dependency` matches.
Expand Down
12 changes: 4 additions & 8 deletions src/cargo/sources/registry/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,11 +439,9 @@ impl<'cfg> RegistryIndex<'cfg> {
/// checking the integrity of a downloaded package matching the checksum in
/// the index file, aka [`IndexSummary`].
pub fn hash(&mut self, pkg: PackageId, load: &mut dyn RegistryData) -> Poll<CargoResult<&str>> {
let req = OptVersionReq::exact(pkg.version());
let req = OptVersionReq::lock_to_exact(pkg.version());
let summary = self.summaries(pkg.name(), &req, load)?;
let summary = ready!(summary)
.filter(|s| s.package_id().version() == pkg.version())
.next();
let summary = ready!(summary).next();
Poll::Ready(Ok(summary
.ok_or_else(|| internal(format!("no hash listed for {}", pkg)))?
.as_summary()
Expand Down Expand Up @@ -697,10 +695,8 @@ impl<'cfg> RegistryIndex<'cfg> {
pkg: PackageId,
load: &mut dyn RegistryData,
) -> Poll<CargoResult<bool>> {
let req = OptVersionReq::exact(pkg.version());
let found = ready!(self.summaries(pkg.name(), &req, load))?
.filter(|s| s.package_id().version() == pkg.version())
.any(|s| s.is_yanked());
let req = OptVersionReq::lock_to_exact(pkg.version());
let found = ready!(self.summaries(pkg.name(), &req, load))?.any(|s| s.is_yanked());
Poll::Ready(Ok(found))
}
}
Expand Down
56 changes: 17 additions & 39 deletions src/cargo/util/semver_ext.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use semver::{Comparator, Op, Version, VersionReq};
use serde_untagged::UntaggedEnumVisitor;
use std::cmp::Ordering;
use std::fmt::{self, Display};

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
Expand Down Expand Up @@ -44,6 +43,13 @@ impl OptVersionReq {
OptVersionReq::Req(VersionReq::exact(version))
}

// Since some registries have allowed crate versions to differ only by build metadata,
// A query using OptVersionReq::exact return nondeterministic results.
// So we `lock_to` the exact version were interested in.
pub fn lock_to_exact(version: &Version) -> Self {
OptVersionReq::Locked(version.clone(), VersionReq::exact(version))
}

pub fn is_exact(&self) -> bool {
match self {
OptVersionReq::Any => false,
Expand Down Expand Up @@ -84,7 +90,16 @@ impl OptVersionReq {
match self {
OptVersionReq::Any => true,
OptVersionReq::Req(req) => req.matches(version),
OptVersionReq::Locked(v, _) => v.cmp_precedence(version) == Ordering::Equal,
OptVersionReq::Locked(v, _) => {
// Generally, cargo is of the opinion that semver metadata should be ignored.
// If your registry has two versions that only differing metadata you get the bugs you deserve.
// We also believe that lock files should ensure reproducibility
// and protect against mutations from the registry.
// In this circumstance these two goals are in conflict, and we pick reproducibility.
// If the lock file tells us that there is a version called `1.0.0+bar` then
// we should not silently use `1.0.0+foo` even though they have the same version.
v == version
}
}
}
}
Expand Down Expand Up @@ -316,40 +331,3 @@ fn is_req(value: &str) -> bool {
};
"<>=^~".contains(first) || value.contains('*') || value.contains(',')
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn locked_has_the_same_with_exact() {
fn test_versions(target_ver: &str, vers: &[&str]) {
let ver = Version::parse(target_ver).unwrap();
let exact = OptVersionReq::exact(&ver);
let mut locked = exact.clone();
locked.lock_to(&ver);
Comment on lines -320 to -330
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we keep this test but change it so it requires metadata to be the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's testing an implementation detail. If we want to add a test for this, we could extend the test added in #11447, to ensure that if we have a lock file we get the version specified in the lock file. I may give that a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have expanded the test to make sure that the version from a lock file is used even if there other versions available from the registry. Confusingly, it passes on head. I am trying to determine what's going on.

for v in vers {
let v = Version::parse(v).unwrap();
assert_eq!(exact.matches(&v), locked.matches(&v));
}
}

test_versions(
"1.0.0",
&["1.0.0", "1.0.1", "0.9.9", "0.10.0", "0.1.0", "1.0.0-pre"],
);
test_versions("0.9.0", &["0.9.0", "0.9.1", "1.9.0", "0.0.9", "0.9.0-pre"]);
test_versions("0.0.2", &["0.0.2", "0.0.1", "0.0.3", "0.0.2-pre"]);
test_versions(
"0.1.0-beta2.a",
&[
"0.1.0-beta2.a",
"0.9.1",
"0.1.0",
"0.1.1-beta2.a",
"0.1.0-beta2",
],
);
test_versions("0.1.0+meta", &["0.1.0", "0.1.0+meta", "0.1.0+any"]);
}
}
3 changes: 1 addition & 2 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1274,8 +1274,7 @@ impl TomlManifest {
replacement.unused_keys(),
&mut cx.warnings,
);
dep.set_version_req(OptVersionReq::exact(&version))
.lock_version(&version);
dep.set_version_req(OptVersionReq::exact(&version));
replace.push((spec, dep));
}
Ok(replace)
Expand Down
51 changes: 51 additions & 0 deletions tests/testsuite/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3597,6 +3597,57 @@ fn differ_only_by_metadata() {
[CHECKING] baz v0.0.1+b
[CHECKING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
",
)
.run();

Package::new("baz", "0.0.1+d").publish();

p.cargo("clean").run();
p.cargo("check")
.with_stderr_contains("[CHECKING] baz v0.0.1+b")
.run();
}

#[cargo_test]
fn differ_only_by_metadata_with_lockfile() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []

[dependencies]
baz = "=0.0.1"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

Package::new("baz", "0.0.1+a").publish();
Package::new("baz", "0.0.1+b").publish();
Package::new("baz", "0.0.1+c").publish();

p.cargo("update --package baz --precise 0.0.1+b")
.with_stderr(
"\
[UPDATING] [..] index
[..] baz v0.0.1+c -> v0.0.1+b
",
)
.run();

p.cargo("check")
.with_stderr(
"\
[DOWNLOADING] crates ...
[DOWNLOADED] [..] v0.0.1+b (registry `dummy-registry`)
[CHECKING] baz v0.0.1+b
[CHECKING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]s
",
)
.run();
Expand Down