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