Skip to content

Commit 4592422

Browse files
committed
Auto merge of #9467 - ehuss:install-metadata, r=alexcrichton
Fix `cargo install` with a semver metadata version. This fixes an issue where `cargo install cargo-c --version 0.8.0+cargo-0.51` fails (returns a 404 error when downloading) when the index has not yet been populated through other means. The crux of the issue is that the `PackageId` interner was treating `0.8.0+cargo-0.51` and `0.8.0` the same. Due to a chain of events, the interner was getting populated with `0.8.0` first, and then from there on always returned `0.8.0`. The full version information is needed to construct the download URL, so it was failing. The reason the interner was getting populated with a version without the metadata is the following sequence of events: 1. There is [this "fast path" code path](https://github.com/rust-lang/cargo/blob/d1baf0d81d0e4f91881de8e544c71fb49f623047/src/cargo/ops/cargo_install.rs#L570) which checks if a version of a package is already installed *before updating the index*. 2. Since the index doesn't exist yet, the resolver query returns zero entries (because the Registry Source is empty) [here](https://github.com/rust-lang/cargo/blob/d1baf0d81d0e4f91881de8e544c71fb49f623047/src/cargo/ops/common_for_install_and_uninstall.rs#L546-L550). 3. That code checks if the package has been yanked (because it can't tell the difference between "yanked" and "index not downloaded, yet"). 4. It constructs a `PackageId` using a `VersionReq` where the build metadata has been removed (because version reqs don't have build metadata). 5. When the real install continues (the error here is ignored for the purpose of this fast-path check if it is already installed), it downloads the index. However, the `PackageId` values created when parsing the index JSON files are now missing the build metadata because the interner is returning the wrong entries. 6. When the download starts, the URL is built from the `PackageId` missing the build metadata. I only changed `PackageIdInner` to pay attention to the build metadata. This seems a bit fragile, as perhaps `PackageId` should also pay attention to it. However, I don't really want to do an audit of every use of `PackageId`, and offhand I can't think of other situations where it would matter. Closes #9410
2 parents e51522a + 9387a30 commit 4592422

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

src/cargo/core/package_id.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,22 @@ struct PackageIdInner {
3131
source_id: SourceId,
3232
}
3333

34-
// Custom equality that uses full equality of SourceId, rather than its custom equality.
34+
// Custom equality that uses full equality of SourceId, rather than its custom equality,
35+
// and Version, which usually ignores `build` metadata.
36+
//
37+
// The `build` part of the version is usually ignored (like a "comment").
38+
// However, there are some cases where it is important. The download path from
39+
// a registry includes the build metadata, and Cargo uses PackageIds for
40+
// creating download paths. Including it here prevents the PackageId interner
41+
// from getting poisoned with PackageIds where that build metadata is missing.
3542
impl PartialEq for PackageIdInner {
3643
fn eq(&self, other: &Self) -> bool {
3744
self.name == other.name
38-
&& self.version == other.version
45+
&& self.version.major == other.version.major
46+
&& self.version.minor == other.version.minor
47+
&& self.version.patch == other.version.patch
48+
&& self.version.pre == other.version.pre
49+
&& self.version.build == other.version.build
3950
&& self.source_id.full_eq(other.source_id)
4051
}
4152
}
@@ -44,7 +55,11 @@ impl PartialEq for PackageIdInner {
4455
impl Hash for PackageIdInner {
4556
fn hash<S: hash::Hasher>(&self, into: &mut S) {
4657
self.name.hash(into);
47-
self.version.hash(into);
58+
self.version.major.hash(into);
59+
self.version.minor.hash(into);
60+
self.version.patch.hash(into);
61+
self.version.pre.hash(into);
62+
self.version.build.hash(into);
4863
self.source_id.full_hash(into);
4964
}
5065
}
@@ -97,6 +112,8 @@ impl PartialEq for PackageId {
97112
if ptr::eq(self.inner, other.inner) {
98113
return true;
99114
}
115+
// This is here so that PackageId uses SourceId's and Version's idea
116+
// of equality. PackageIdInner uses a more exact notion of equality.
100117
self.inner.name == other.inner.name
101118
&& self.inner.version == other.inner.version
102119
&& self.inner.source_id == other.inner.source_id
@@ -105,6 +122,9 @@ impl PartialEq for PackageId {
105122

106123
impl Hash for PackageId {
107124
fn hash<S: hash::Hasher>(&self, state: &mut S) {
125+
// This is here (instead of derived) so that PackageId uses SourceId's
126+
// and Version's idea of equality. PackageIdInner uses a more exact
127+
// notion of hashing.
108128
self.inner.name.hash(state);
109129
self.inner.version.hash(state);
110130
self.inner.source_id.hash(state);
@@ -166,6 +186,12 @@ impl PackageId {
166186
}
167187
}
168188

189+
/// Returns a value that implements a "stable" hashable value.
190+
///
191+
/// Stable hashing removes the path prefix of the workspace from path
192+
/// packages. This helps with reproducible builds, since this hash is part
193+
/// of the symbol metadata, and we don't want the absolute path where the
194+
/// build is performed to affect the binary output.
169195
pub fn stable_hash(self, workspace: &Path) -> PackageIdStableHash<'_> {
170196
PackageIdStableHash(self, workspace)
171197
}

tests/testsuite/install.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ use std::io::prelude::*;
55

66
use cargo_test_support::cross_compile;
77
use cargo_test_support::git;
8-
use cargo_test_support::registry::{registry_path, registry_url, Package};
8+
use cargo_test_support::registry::{self, registry_path, registry_url, Package};
99
use cargo_test_support::{
1010
basic_manifest, cargo_process, no_such_file_err_msg, project, symlink_supported, t,
1111
};
1212

1313
use cargo_test_support::install::{
1414
assert_has_installed_exe, assert_has_not_installed_exe, cargo_home,
1515
};
16-
use cargo_test_support::paths;
16+
use cargo_test_support::paths::{self, CargoPathExt};
1717
use std::env;
1818
use std::path::PathBuf;
1919

@@ -1739,3 +1739,60 @@ fn locked_install_without_published_lockfile() {
17391739
.with_stderr_contains("[WARNING] no Cargo.lock file published in foo v0.1.0")
17401740
.run();
17411741
}
1742+
1743+
#[cargo_test]
1744+
fn install_semver_metadata() {
1745+
// Check trying to install a package that uses semver metadata.
1746+
// This uses alt registry because the bug this is exercising doesn't
1747+
// trigger with a replaced source.
1748+
registry::alt_init();
1749+
Package::new("foo", "1.0.0+abc")
1750+
.alternative(true)
1751+
.file("src/main.rs", "fn main() {}")
1752+
.publish();
1753+
1754+
cargo_process("install foo --registry alternative --version 1.0.0+abc").run();
1755+
cargo_process("install foo --registry alternative")
1756+
.with_stderr("\
1757+
[UPDATING] `[ROOT]/alternative-registry` index
1758+
[IGNORED] package `foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)` is already installed, use --force to override
1759+
[WARNING] be sure to add [..]
1760+
")
1761+
.run();
1762+
// "Updating" is not displayed here due to the --version fast-path.
1763+
cargo_process("install foo --registry alternative --version 1.0.0+abc")
1764+
.with_stderr("\
1765+
[IGNORED] package `foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)` is already installed, use --force to override
1766+
[WARNING] be sure to add [..]
1767+
")
1768+
.run();
1769+
cargo_process("install foo --registry alternative --version 1.0.0 --force")
1770+
.with_stderr(
1771+
"\
1772+
[UPDATING] `[ROOT]/alternative-registry` index
1773+
[INSTALLING] foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)
1774+
[COMPILING] foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)
1775+
[FINISHED] [..]
1776+
[REPLACING] [ROOT]/home/.cargo/bin/foo[EXE]
1777+
[REPLACED] package [..]
1778+
[WARNING] be sure to add [..]
1779+
",
1780+
)
1781+
.run();
1782+
// Check that from a fresh cache will work without metadata, too.
1783+
paths::home().join(".cargo/registry").rm_rf();
1784+
paths::home().join(".cargo/bin").rm_rf();
1785+
cargo_process("install foo --registry alternative --version 1.0.0")
1786+
.with_stderr("\
1787+
[UPDATING] `[ROOT]/alternative-registry` index
1788+
[DOWNLOADING] crates ...
1789+
[DOWNLOADED] foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)
1790+
[INSTALLING] foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)
1791+
[COMPILING] foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)
1792+
[FINISHED] [..]
1793+
[INSTALLING] [ROOT]/home/.cargo/bin/foo[EXE]
1794+
[INSTALLED] package `foo v1.0.0+abc (registry `[ROOT]/alternative-registry`)` (executable `foo[EXE]`)
1795+
[WARNING] be sure to add [..]
1796+
")
1797+
.run();
1798+
}

0 commit comments

Comments
 (0)