1 use std::collections::HashSet;
2 use std::fmt::{self, Formatter};
3 use std::hash;
4 use std::hash::Hash;
5 use std::path::Path;
6 use std::ptr;
7 use std::sync::Mutex;
8 
9 use serde::de;
10 use serde::ser;
11 
12 use crate::core::source::SourceId;
13 use crate::util::interning::InternedString;
14 use crate::util::{CargoResult, ToSemver};
15 
16 lazy_static::lazy_static! {
17     static ref PACKAGE_ID_CACHE: Mutex<HashSet<&'static PackageIdInner>> =
18         Mutex::new(HashSet::new());
19 }
20 
21 /// Identifier for a specific version of a package in a specific source.
22 #[derive(Clone, Copy, Eq, PartialOrd, Ord)]
23 pub struct PackageId {
24     inner: &'static PackageIdInner,
25 }
26 
27 #[derive(PartialOrd, Eq, Ord)]
28 struct PackageIdInner {
29     name: InternedString,
30     version: semver::Version,
31     source_id: SourceId,
32 }
33 
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.
42 impl PartialEq for PackageIdInner {
eq(&self, other: &Self) -> bool43     fn eq(&self, other: &Self) -> bool {
44         self.name == other.name
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
50             && self.source_id.full_eq(other.source_id)
51     }
52 }
53 
54 // Custom hash that is coherent with the custom equality above.
55 impl Hash for PackageIdInner {
hash<S: hash::Hasher>(&self, into: &mut S)56     fn hash<S: hash::Hasher>(&self, into: &mut S) {
57         self.name.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);
63         self.source_id.full_hash(into);
64     }
65 }
66 
67 impl ser::Serialize for PackageId {
serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> where S: ser::Serializer,68     fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
69     where
70         S: ser::Serializer,
71     {
72         s.collect_str(&format_args!(
73             "{} {} ({})",
74             self.inner.name,
75             self.inner.version,
76             self.inner.source_id.as_url()
77         ))
78     }
79 }
80 
81 impl<'de> de::Deserialize<'de> for PackageId {
deserialize<D>(d: D) -> Result<PackageId, D::Error> where D: de::Deserializer<'de>,82     fn deserialize<D>(d: D) -> Result<PackageId, D::Error>
83     where
84         D: de::Deserializer<'de>,
85     {
86         let string = String::deserialize(d)?;
87         let mut s = string.splitn(3, ' ');
88         let name = s.next().unwrap();
89         let name = InternedString::new(name);
90         let version = match s.next() {
91             Some(s) => s,
92             None => return Err(de::Error::custom("invalid serialized PackageId")),
93         };
94         let version = version.to_semver().map_err(de::Error::custom)?;
95         let url = match s.next() {
96             Some(s) => s,
97             None => return Err(de::Error::custom("invalid serialized PackageId")),
98         };
99         let url = if url.starts_with('(') && url.ends_with(')') {
100             &url[1..url.len() - 1]
101         } else {
102             return Err(de::Error::custom("invalid serialized PackageId"));
103         };
104         let source_id = SourceId::from_url(url).map_err(de::Error::custom)?;
105 
106         Ok(PackageId::pure(name, version, source_id))
107     }
108 }
109 
110 impl PartialEq for PackageId {
eq(&self, other: &PackageId) -> bool111     fn eq(&self, other: &PackageId) -> bool {
112         if ptr::eq(self.inner, other.inner) {
113             return true;
114         }
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.
117         self.inner.name == other.inner.name
118             && self.inner.version == other.inner.version
119             && self.inner.source_id == other.inner.source_id
120     }
121 }
122 
123 impl Hash for PackageId {
hash<S: hash::Hasher>(&self, state: &mut S)124     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.
128         self.inner.name.hash(state);
129         self.inner.version.hash(state);
130         self.inner.source_id.hash(state);
131     }
132 }
133 
134 impl PackageId {
new<T: ToSemver>( name: impl Into<InternedString>, version: T, sid: SourceId, ) -> CargoResult<PackageId>135     pub fn new<T: ToSemver>(
136         name: impl Into<InternedString>,
137         version: T,
138         sid: SourceId,
139     ) -> CargoResult<PackageId> {
140         let v = version.to_semver()?;
141         Ok(PackageId::pure(name.into(), v, sid))
142     }
143 
pure(name: InternedString, version: semver::Version, source_id: SourceId) -> PackageId144     pub fn pure(name: InternedString, version: semver::Version, source_id: SourceId) -> PackageId {
145         let inner = PackageIdInner {
146             name,
147             version,
148             source_id,
149         };
150         let mut cache = PACKAGE_ID_CACHE.lock().unwrap();
151         let inner = cache.get(&inner).cloned().unwrap_or_else(|| {
152             let inner = Box::leak(Box::new(inner));
153             cache.insert(inner);
154             inner
155         });
156         PackageId { inner }
157     }
158 
name(self) -> InternedString159     pub fn name(self) -> InternedString {
160         self.inner.name
161     }
version(self) -> &'static semver::Version162     pub fn version(self) -> &'static semver::Version {
163         &self.inner.version
164     }
source_id(self) -> SourceId165     pub fn source_id(self) -> SourceId {
166         self.inner.source_id
167     }
168 
with_precise(self, precise: Option<String>) -> PackageId169     pub fn with_precise(self, precise: Option<String>) -> PackageId {
170         PackageId::pure(
171             self.inner.name,
172             self.inner.version.clone(),
173             self.inner.source_id.with_precise(precise),
174         )
175     }
176 
with_source_id(self, source: SourceId) -> PackageId177     pub fn with_source_id(self, source: SourceId) -> PackageId {
178         PackageId::pure(self.inner.name, self.inner.version.clone(), source)
179     }
180 
map_source(self, to_replace: SourceId, replace_with: SourceId) -> Self181     pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Self {
182         if self.source_id() == to_replace {
183             self.with_source_id(replace_with)
184         } else {
185             self
186         }
187     }
188 
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.
stable_hash(self, workspace: &Path) -> PackageIdStableHash<'_>195     pub fn stable_hash(self, workspace: &Path) -> PackageIdStableHash<'_> {
196         PackageIdStableHash(self, workspace)
197     }
198 }
199 
200 pub struct PackageIdStableHash<'a>(PackageId, &'a Path);
201 
202 impl<'a> Hash for PackageIdStableHash<'a> {
hash<S: hash::Hasher>(&self, state: &mut S)203     fn hash<S: hash::Hasher>(&self, state: &mut S) {
204         self.0.inner.name.hash(state);
205         self.0.inner.version.hash(state);
206         self.0.inner.source_id.stable_hash(self.1, state);
207     }
208 }
209 
210 impl fmt::Display for PackageId {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result211     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
212         write!(f, "{} v{}", self.inner.name, self.inner.version)?;
213 
214         if !self.inner.source_id.is_default_registry() {
215             write!(f, " ({})", self.inner.source_id)?;
216         }
217 
218         Ok(())
219     }
220 }
221 
222 impl fmt::Debug for PackageId {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result223     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
224         f.debug_struct("PackageId")
225             .field("name", &self.inner.name)
226             .field("version", &self.inner.version.to_string())
227             .field("source", &self.inner.source_id.to_string())
228             .finish()
229     }
230 }
231 
232 #[cfg(test)]
233 mod tests {
234     use super::PackageId;
235     use crate::core::source::SourceId;
236     use crate::sources::CRATES_IO_INDEX;
237     use crate::util::IntoUrl;
238 
239     #[test]
invalid_version_handled_nicely()240     fn invalid_version_handled_nicely() {
241         let loc = CRATES_IO_INDEX.into_url().unwrap();
242         let repo = SourceId::for_registry(&loc).unwrap();
243 
244         assert!(PackageId::new("foo", "1.0", repo).is_err());
245         assert!(PackageId::new("foo", "1", repo).is_err());
246         assert!(PackageId::new("foo", "bar", repo).is_err());
247         assert!(PackageId::new("foo", "", repo).is_err());
248     }
249 
250     #[test]
debug()251     fn debug() {
252         let loc = CRATES_IO_INDEX.into_url().unwrap();
253         let pkg_id = PackageId::new("foo", "1.0.0", SourceId::for_registry(&loc).unwrap()).unwrap();
254         assert_eq!(
255             r#"PackageId { name: "foo", version: "1.0.0", source: "registry `https://github.com/rust-lang/crates.io-index`" }"#,
256             format!("{:?}", pkg_id)
257         );
258 
259         let expected = r#"
260 PackageId {
261     name: "foo",
262     version: "1.0.0",
263     source: "registry `https://github.com/rust-lang/crates.io-index`",
264 }
265 "#
266         .trim();
267 
268         // Can be removed once trailing commas in Debug have reached the stable
269         // channel.
270         let expected_without_trailing_comma = r#"
271 PackageId {
272     name: "foo",
273     version: "1.0.0",
274     source: "registry `https://github.com/rust-lang/crates.io-index`"
275 }
276 "#
277         .trim();
278 
279         let actual = format!("{:#?}", pkg_id);
280         if actual.ends_with(",\n}") {
281             assert_eq!(actual, expected);
282         } else {
283             assert_eq!(actual, expected_without_trailing_comma);
284         }
285     }
286 
287     #[test]
display()288     fn display() {
289         let loc = CRATES_IO_INDEX.into_url().unwrap();
290         let pkg_id = PackageId::new("foo", "1.0.0", SourceId::for_registry(&loc).unwrap()).unwrap();
291         assert_eq!("foo v1.0.0", pkg_id.to_string());
292     }
293 }
294