Skip to content

Commit

Permalink
Auto merge of #12847 - epage:backport, r=weihanglo
Browse files Browse the repository at this point in the history
[beta-1.74.0] Partial-version spec support

Beta backports:
- #12806

In order to make CI pass, the following PRs are also cherry-picked:
- f0d3cdf from #12800
  • Loading branch information
bors committed Oct 18, 2023
2 parents 22a976c + 01a543e commit ecb9851
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 24 deletions.
44 changes: 39 additions & 5 deletions src/cargo/core/package_id_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,7 @@ impl PackageIdSpec {
}

if let Some(ref v) = self.version {
let req = v.exact_req();
if !req.matches(package_id.version()) {
if !v.matches(package_id.version()) {
return false;
}
}
Expand Down Expand Up @@ -444,15 +443,50 @@ mod tests {
fn matching() {
let url = Url::parse("https://example.com").unwrap();
let sid = SourceId::for_registry(&url).unwrap();
let foo = PackageId::new("foo", "1.2.3", sid).unwrap();
let bar = PackageId::new("bar", "1.2.3", sid).unwrap();

let foo = PackageId::new("foo", "1.2.3", sid).unwrap();
assert!(PackageIdSpec::parse("foo").unwrap().matches(foo));
assert!(!PackageIdSpec::parse("foo").unwrap().matches(bar));
assert!(!PackageIdSpec::parse("bar").unwrap().matches(foo));
assert!(PackageIdSpec::parse("foo:1.2.3").unwrap().matches(foo));
assert!(!PackageIdSpec::parse("foo:1.2.2").unwrap().matches(foo));
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
assert!(!PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(foo));

let meta = PackageId::new("meta", "1.2.3+hello", sid).unwrap();
assert!(PackageIdSpec::parse("meta").unwrap().matches(meta));
assert!(PackageIdSpec::parse("meta@1").unwrap().matches(meta));
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(meta));
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(meta));
assert!(!PackageIdSpec::parse("[email protected]")
.unwrap()
.matches(meta));
assert!(PackageIdSpec::parse("[email protected]+hello")
.unwrap()
.matches(meta));
assert!(!PackageIdSpec::parse("[email protected]+bye")
.unwrap()
.matches(meta));

let pre = PackageId::new("pre", "1.2.3-alpha.0", sid).unwrap();
assert!(PackageIdSpec::parse("pre").unwrap().matches(pre));
assert!(!PackageIdSpec::parse("pre@1").unwrap().matches(pre));
assert!(!PackageIdSpec::parse("[email protected]").unwrap().matches(pre));
assert!(!PackageIdSpec::parse("[email protected]").unwrap().matches(pre));
assert!(PackageIdSpec::parse("[email protected]")
.unwrap()
.matches(pre));
assert!(!PackageIdSpec::parse("[email protected]")
.unwrap()
.matches(pre));
assert!(!PackageIdSpec::parse("[email protected]")
.unwrap()
.matches(pre));
assert!(!PackageIdSpec::parse("[email protected]+hello")
.unwrap()
.matches(pre));
assert!(!PackageIdSpec::parse("[email protected]+hello")
.unwrap()
.matches(pre));
}
}
19 changes: 14 additions & 5 deletions src/cargo/core/resolver/dep_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,20 @@ impl<'a> RegistryQueryer<'a> {
)));
}

// The dependency should be hard-coded to have the same name and an
// exact version requirement, so both of these assertions should
// never fail.
assert_eq!(s.version(), summary.version());
assert_eq!(s.name(), summary.name());
assert_eq!(
s.name(),
summary.name(),
"dependency should be hard coded to have the same name"
);
if s.version() != summary.version() {
return Poll::Ready(Err(anyhow::anyhow!(
"replacement specification `{}` matched {} and tried to override it with {}\n\
avoid matching unrelated packages by being more specific",
spec,
summary.version(),
s.version(),
)));
}

let replace = if s.source_id() == summary.source_id() {
debug!("Preventing\n{:?}\nfrom replacing\n{:?}", summary, s);
Expand Down
27 changes: 18 additions & 9 deletions src/cargo/util/semver_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,16 +186,25 @@ impl PartialVersion {
}
}

pub fn exact_req(&self) -> VersionReq {
VersionReq {
comparators: vec![Comparator {
op: semver::Op::Exact,
major: self.major,
minor: self.minor,
patch: self.patch,
pre: self.pre.as_ref().cloned().unwrap_or_default(),
}],
/// Check if this matches a version, including build metadata
///
/// Build metadata does not affect version precedence but may be necessary for uniquely
/// identifying a package.
pub fn matches(&self, version: &Version) -> bool {
if !version.pre.is_empty() && self.pre.is_none() {
// Pre-release versions must be explicitly opted into, if for no other reason than to
// give us room to figure out and define the semantics
return false;
}
self.major == version.major
&& self.minor.map(|f| f == version.minor).unwrap_or(true)
&& self.patch.map(|f| f == version.patch).unwrap_or(true)
&& self.pre.as_ref().map(|f| f == &version.pre).unwrap_or(true)
&& self
.build
.as_ref()
.map(|f| f == &version.build)
.unwrap_or(true)
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/testsuite/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2004,7 +2004,7 @@ fn crate_versions() {
let output_path = p.root().join("target/doc/foo/index.html");
let output_documentation = fs::read_to_string(&output_path).unwrap();

assert!(output_documentation.contains("Version 1.2.4"));
assert!(output_documentation.contains("1.2.4"));
}

#[cargo_test]
Expand All @@ -2028,7 +2028,7 @@ fn crate_versions_flag_is_overridden() {
};
let asserts = |html: String| {
assert!(!html.contains("1.2.4"));
assert!(html.contains("Version 2.0.3"));
assert!(html.contains("2.0.3"));
};

p.cargo("doc")
Expand Down
154 changes: 154 additions & 0 deletions tests/testsuite/replace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,3 +1298,157 @@ fn override_plus_dep() {
.with_stderr_contains("error: cyclic package dependency: [..]")
.run();
}

#[cargo_test]
fn override_generic_matching_other_versions() {
Package::new("bar", "0.1.0+a").publish();

let bar = git::repo(&paths::root().join("override"))
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
.file("src/lib.rs", "pub fn bar() {}")
.build();

let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies]
bar = "0.1.0"
[replace]
"bar:0.1.0" = {{ git = '{}' }}
"#,
bar.url()
),
)
.file(
"src/lib.rs",
"extern crate bar; pub fn foo() { bar::bar(); }",
)
.build();

p.cargo("check")
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[UPDATING] git repository `[..]`
[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([..]/foo)`
Caused by:
replacement specification `https://github.com/rust-lang/crates.io-index#[email protected]` matched 0.1.0+a and tried to override it with 0.1.0
avoid matching unrelated packages by being more specific
",
)
.with_status(101)
.run();
}

#[cargo_test]
fn override_respects_spec_metadata() {
Package::new("bar", "0.1.0+a").publish();

let bar = git::repo(&paths::root().join("override"))
.file("Cargo.toml", &basic_manifest("bar", "0.1.0+a"))
.file("src/lib.rs", "pub fn bar() {}")
.build();

let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies]
bar = "0.1.0"
[replace]
"bar:0.1.0+notTheBuild" = {{ git = '{}' }}
"#,
bar.url()
),
)
.file(
"src/lib.rs",
"extern crate bar; pub fn foo() { bar::bar(); }",
)
.build();

p.cargo("check")
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[WARNING] package replacement is not used: https://github.com/rust-lang/crates.io-index#[email protected]+notTheBuild
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.1.0+a (registry `dummy-registry`)
[CHECKING] bar v0.1.0+a
[CHECKING] foo v0.0.1 ([..]/foo)
[..]
[..]
[..]
[..]
[..]
[..]
[..]
error: could not compile `foo` (lib) due to previous error
",
)
.with_status(101)
.run();
}

#[cargo_test]
fn override_spec_metadata_is_optional() {
Package::new("bar", "0.1.0+a").publish();

let bar = git::repo(&paths::root().join("override"))
.file("Cargo.toml", &basic_manifest("bar", "0.1.0+a"))
.file("src/lib.rs", "pub fn bar() {}")
.build();

let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies]
bar = "0.1.0"
[replace]
"bar:0.1.0" = {{ git = '{}' }}
"#,
bar.url()
),
)
.file(
"src/lib.rs",
"extern crate bar; pub fn foo() { bar::bar(); }",
)
.build();

p.cargo("check")
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[UPDATING] git repository `[..]`
[CHECKING] bar v0.1.0+a (file://[..])
[CHECKING] foo v0.0.1 ([CWD])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
",
)
.run();
}
8 changes: 5 additions & 3 deletions tests/testsuite/rustdocflags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,19 @@ fn whitespace() {
.with_status(101)
.run();

const SPACED_VERSION: &str = "a\nb\tc\u{00a0}d";
p.cargo("doc")
.env_remove("__CARGO_TEST_FORCE_ARGFILE") // Not applicable for argfile.
.env(
"RUSTDOCFLAGS",
format!("--crate-version {}", SPACED_VERSION),
"--crate-version 1111\n2222\t3333\u{00a0}4444",
)
.run();

let contents = p.read_file("target/doc/foo/index.html");
assert!(contents.contains(SPACED_VERSION));
assert!(contents.contains("1111"));
assert!(contents.contains("2222"));
assert!(contents.contains("3333"));
assert!(contents.contains("4444"));
}

#[cargo_test]
Expand Down

0 comments on commit ecb9851

Please sign in to comment.