Skip to content

Commit 67271fd

Browse files
committed
Auto merge of #12806 - epage:replace, r=Eh2406
fix(replace): Partial-version spec support ### What does this PR try to resolve? #12614 changed package ID specs to allow fields in the version number to be optional. This earliest branch with this change is `rust-1.74.0` (beta). While `@Eh2406` was investigating version metadata issues in #12772, problems with the partial version change were found - `replace`s that specify version metadata were ignored **(fixed with this PR)** - This also extends out to any other place a PackageIDSpec may show up, like `cargo check -p <name>`@<spec>`` - We explicitly kept the same semantics of version requirements that pre-releases require opt-in. If nothing else, this gives us more room to change semantics in the future if we ever fix the semantics for pre-release. - `replace`s that don't specify version metadata when the `Cargo.lock` contained a version metadata, it would previously be ignored (with a warning) but now match **(unchanged with this PR)** - When the version metadata in `Cargo.lock` differed from the overriding `Cargo.toml`, cargo would panic **(now an error in this PR)** With this PR, we are acknowledging that we changed behavior in taking ignored replaces (because of differences with version metadata) and applying them. Seeing as version metadata is relatively rare, replaces are relatively rare, and differences in it for registries is unsupported, the impact seems very small. The questions before us are - Do we revert #12614 in `master` and `rust-1.74.0` or merge this PR into `master` - If we merge this PR into `master`, do we cherry-pick this into `rust-1.74.0` or revert #12614, giving ourselves more time to find problems ### How should we test and review this PR? The initial commit adds tests that pass as of #12614. Prior to #12614, these tests would have warned that the `replace` was unused and failed because `bar::bar` didn't exist. Each commit then changes the behavior (or not) and updates the corresponding test. ### Additional information
2 parents a275529 + 1e34066 commit 67271fd

File tree

4 files changed

+225
-19
lines changed

4 files changed

+225
-19
lines changed

src/cargo/core/package_id_spec.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,7 @@ impl PackageIdSpec {
176176
}
177177

178178
if let Some(ref v) = self.version {
179-
let req = v.exact_req();
180-
if !req.matches(package_id.version()) {
179+
if !v.matches(package_id.version()) {
181180
return false;
182181
}
183182
}
@@ -444,15 +443,50 @@ mod tests {
444443
fn matching() {
445444
let url = Url::parse("https://example.com").unwrap();
446445
let sid = SourceId::for_registry(&url).unwrap();
447-
let foo = PackageId::new("foo", "1.2.3", sid).unwrap();
448-
let bar = PackageId::new("bar", "1.2.3", sid).unwrap();
449446

447+
let foo = PackageId::new("foo", "1.2.3", sid).unwrap();
450448
assert!(PackageIdSpec::parse("foo").unwrap().matches(foo));
451-
assert!(!PackageIdSpec::parse("foo").unwrap().matches(bar));
449+
assert!(!PackageIdSpec::parse("bar").unwrap().matches(foo));
452450
assert!(PackageIdSpec::parse("foo:1.2.3").unwrap().matches(foo));
453451
assert!(!PackageIdSpec::parse("foo:1.2.2").unwrap().matches(foo));
454452
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
455453
assert!(!PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
456454
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(foo));
455+
456+
let meta = PackageId::new("meta", "1.2.3+hello", sid).unwrap();
457+
assert!(PackageIdSpec::parse("meta").unwrap().matches(meta));
458+
assert!(PackageIdSpec::parse("meta@1").unwrap().matches(meta));
459+
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(meta));
460+
assert!(PackageIdSpec::parse("[email protected]").unwrap().matches(meta));
461+
assert!(!PackageIdSpec::parse("[email protected]")
462+
.unwrap()
463+
.matches(meta));
464+
assert!(PackageIdSpec::parse("[email protected]+hello")
465+
.unwrap()
466+
.matches(meta));
467+
assert!(!PackageIdSpec::parse("[email protected]+bye")
468+
.unwrap()
469+
.matches(meta));
470+
471+
let pre = PackageId::new("pre", "1.2.3-alpha.0", sid).unwrap();
472+
assert!(PackageIdSpec::parse("pre").unwrap().matches(pre));
473+
assert!(!PackageIdSpec::parse("pre@1").unwrap().matches(pre));
474+
assert!(!PackageIdSpec::parse("[email protected]").unwrap().matches(pre));
475+
assert!(!PackageIdSpec::parse("[email protected]").unwrap().matches(pre));
476+
assert!(PackageIdSpec::parse("[email protected]")
477+
.unwrap()
478+
.matches(pre));
479+
assert!(!PackageIdSpec::parse("[email protected]")
480+
.unwrap()
481+
.matches(pre));
482+
assert!(!PackageIdSpec::parse("[email protected]")
483+
.unwrap()
484+
.matches(pre));
485+
assert!(!PackageIdSpec::parse("[email protected]+hello")
486+
.unwrap()
487+
.matches(pre));
488+
assert!(!PackageIdSpec::parse("[email protected]+hello")
489+
.unwrap()
490+
.matches(pre));
457491
}
458492
}

src/cargo/core/resolver/dep_cache.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,20 @@ impl<'a> RegistryQueryer<'a> {
173173
)));
174174
}
175175

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

182191
let replace = if s.source_id() == summary.source_id() {
183192
debug!("Preventing\n{:?}\nfrom replacing\n{:?}", summary, s);

src/cargo/util/semver_ext.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,25 @@ impl PartialVersion {
182182
}
183183
}
184184

185-
pub fn exact_req(&self) -> VersionReq {
186-
VersionReq {
187-
comparators: vec![Comparator {
188-
op: semver::Op::Exact,
189-
major: self.major,
190-
minor: self.minor,
191-
patch: self.patch,
192-
pre: self.pre.as_ref().cloned().unwrap_or_default(),
193-
}],
185+
/// Check if this matches a version, including build metadata
186+
///
187+
/// Build metadata does not affect version precedence but may be necessary for uniquely
188+
/// identifying a package.
189+
pub fn matches(&self, version: &Version) -> bool {
190+
if !version.pre.is_empty() && self.pre.is_none() {
191+
// Pre-release versions must be explicitly opted into, if for no other reason than to
192+
// give us room to figure out and define the semantics
193+
return false;
194194
}
195+
self.major == version.major
196+
&& self.minor.map(|f| f == version.minor).unwrap_or(true)
197+
&& self.patch.map(|f| f == version.patch).unwrap_or(true)
198+
&& self.pre.as_ref().map(|f| f == &version.pre).unwrap_or(true)
199+
&& self
200+
.build
201+
.as_ref()
202+
.map(|f| f == &version.build)
203+
.unwrap_or(true)
195204
}
196205
}
197206

tests/testsuite/replace.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,3 +1298,157 @@ fn override_plus_dep() {
12981298
.with_stderr_contains("error: cyclic package dependency: [..]")
12991299
.run();
13001300
}
1301+
1302+
#[cargo_test]
1303+
fn override_generic_matching_other_versions() {
1304+
Package::new("bar", "0.1.0+a").publish();
1305+
1306+
let bar = git::repo(&paths::root().join("override"))
1307+
.file("Cargo.toml", &basic_manifest("bar", "0.1.0"))
1308+
.file("src/lib.rs", "pub fn bar() {}")
1309+
.build();
1310+
1311+
let p = project()
1312+
.file(
1313+
"Cargo.toml",
1314+
&format!(
1315+
r#"
1316+
[package]
1317+
name = "foo"
1318+
version = "0.0.1"
1319+
authors = []
1320+
1321+
[dependencies]
1322+
bar = "0.1.0"
1323+
1324+
[replace]
1325+
"bar:0.1.0" = {{ git = '{}' }}
1326+
"#,
1327+
bar.url()
1328+
),
1329+
)
1330+
.file(
1331+
"src/lib.rs",
1332+
"extern crate bar; pub fn foo() { bar::bar(); }",
1333+
)
1334+
.build();
1335+
1336+
p.cargo("check")
1337+
.with_stderr(
1338+
"\
1339+
[UPDATING] `dummy-registry` index
1340+
[UPDATING] git repository `[..]`
1341+
[ERROR] failed to get `bar` as a dependency of package `foo v0.0.1 ([..]/foo)`
1342+
1343+
Caused by:
1344+
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
1345+
avoid matching unrelated packages by being more specific
1346+
",
1347+
)
1348+
.with_status(101)
1349+
.run();
1350+
}
1351+
1352+
#[cargo_test]
1353+
fn override_respects_spec_metadata() {
1354+
Package::new("bar", "0.1.0+a").publish();
1355+
1356+
let bar = git::repo(&paths::root().join("override"))
1357+
.file("Cargo.toml", &basic_manifest("bar", "0.1.0+a"))
1358+
.file("src/lib.rs", "pub fn bar() {}")
1359+
.build();
1360+
1361+
let p = project()
1362+
.file(
1363+
"Cargo.toml",
1364+
&format!(
1365+
r#"
1366+
[package]
1367+
name = "foo"
1368+
version = "0.0.1"
1369+
authors = []
1370+
1371+
[dependencies]
1372+
bar = "0.1.0"
1373+
1374+
[replace]
1375+
"bar:0.1.0+notTheBuild" = {{ git = '{}' }}
1376+
"#,
1377+
bar.url()
1378+
),
1379+
)
1380+
.file(
1381+
"src/lib.rs",
1382+
"extern crate bar; pub fn foo() { bar::bar(); }",
1383+
)
1384+
.build();
1385+
1386+
p.cargo("check")
1387+
.with_stderr(
1388+
"\
1389+
[UPDATING] `dummy-registry` index
1390+
[WARNING] package replacement is not used: https://github.com/rust-lang/crates.io-index#[email protected]+notTheBuild
1391+
[DOWNLOADING] crates ...
1392+
[DOWNLOADED] bar v0.1.0+a (registry `dummy-registry`)
1393+
[CHECKING] bar v0.1.0+a
1394+
[CHECKING] foo v0.0.1 ([..]/foo)
1395+
[..]
1396+
[..]
1397+
[..]
1398+
[..]
1399+
[..]
1400+
[..]
1401+
[..]
1402+
error: could not compile `foo` (lib) due to previous error
1403+
",
1404+
)
1405+
.with_status(101)
1406+
.run();
1407+
}
1408+
1409+
#[cargo_test]
1410+
fn override_spec_metadata_is_optional() {
1411+
Package::new("bar", "0.1.0+a").publish();
1412+
1413+
let bar = git::repo(&paths::root().join("override"))
1414+
.file("Cargo.toml", &basic_manifest("bar", "0.1.0+a"))
1415+
.file("src/lib.rs", "pub fn bar() {}")
1416+
.build();
1417+
1418+
let p = project()
1419+
.file(
1420+
"Cargo.toml",
1421+
&format!(
1422+
r#"
1423+
[package]
1424+
name = "foo"
1425+
version = "0.0.1"
1426+
authors = []
1427+
1428+
[dependencies]
1429+
bar = "0.1.0"
1430+
1431+
[replace]
1432+
"bar:0.1.0" = {{ git = '{}' }}
1433+
"#,
1434+
bar.url()
1435+
),
1436+
)
1437+
.file(
1438+
"src/lib.rs",
1439+
"extern crate bar; pub fn foo() { bar::bar(); }",
1440+
)
1441+
.build();
1442+
1443+
p.cargo("check")
1444+
.with_stderr(
1445+
"\
1446+
[UPDATING] `dummy-registry` index
1447+
[UPDATING] git repository `[..]`
1448+
[CHECKING] bar v0.1.0+a (file://[..])
1449+
[CHECKING] foo v0.0.1 ([CWD])
1450+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
1451+
",
1452+
)
1453+
.run();
1454+
}

0 commit comments

Comments
 (0)