]> git.proxmox.com Git - cargo.git/blame - src/cargo/core/package_id_spec.rs
Add comment on relationship of RunCustomBuild and UnitFor::host.
[cargo.git] / src / cargo / core / package_id_spec.rs
CommitLineData
bc60f64b 1use std::collections::HashMap;
83d877e9 2use std::fmt;
bc60f64b 3
83d877e9 4use semver::Version;
5415a341 5use serde::{de, ser};
32562e92 6use url::Url;
83d877e9 7
77a7e3fe 8use crate::core::interning::InternedString;
04ddd4d0
DW
9use crate::core::PackageId;
10use crate::util::errors::{CargoResult, CargoResultExt};
2415a298 11use crate::util::{validate_package_name, IntoUrl, ToSemver};
83d877e9 12
e82e9c19 13/// Some or all of the data required to identify a package:
7868945b
RD
14///
15/// 1. the package name (a `String`, required)
16/// 2. the package version (a `Version`, optional)
17/// 3. the package source (a `Url`, optional)
18///
f7c91ba6 19/// If any of the optional fields are omitted, then the package ID may be ambiguous, there may be
7868945b 20/// more than one package/version/url combo that will match. However, often just the name is
f7c91ba6 21/// sufficient to uniquely define a package ID.
5415a341 22#[derive(Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
83d877e9 23pub struct PackageIdSpec {
77a7e3fe 24 name: InternedString,
83d877e9
AC
25 version: Option<Version>,
26 url: Option<Url>,
27}
28
29impl PackageIdSpec {
7868945b
RD
30 /// Parses a spec string and returns a `PackageIdSpec` if the string was valid.
31 ///
32 /// # Examples
33 /// Some examples of valid strings
34 ///
35 /// ```
36 /// use cargo::core::PackageIdSpec;
37 ///
38 /// let specs = vec![
0c3851c0
JC
39 /// "https://crates.io/foo#1.2.3",
40 /// "https://crates.io/foo#bar:1.2.3",
7868945b
RD
41 /// "crates.io/foo",
42 /// "crates.io/foo#1.2.3",
43 /// "crates.io/foo#bar",
44 /// "crates.io/foo#bar:1.2.3",
45 /// "foo",
46 /// "foo:1.2.3",
47 /// ];
48 /// for spec in specs {
49 /// assert!(PackageIdSpec::parse(spec).is_ok());
50 /// }
83d877e9 51 pub fn parse(spec: &str) -> CargoResult<PackageIdSpec> {
23591fe5 52 if spec.contains('/') {
930134c7 53 if let Ok(url) = spec.into_url() {
f1062ce3 54 return PackageIdSpec::from_url(url);
83d877e9
AC
55 }
56 if !spec.contains("://") {
f1062ce3
CF
57 if let Ok(url) = Url::parse(&format!("cargo://{}", spec)) {
58 return PackageIdSpec::from_url(url);
83d877e9
AC
59 }
60 }
61 }
80fe0e6d 62 let mut parts = spec.splitn(2, ':');
83d877e9
AC
63 let name = parts.next().unwrap();
64 let version = match parts.next() {
cffd5b24 65 Some(version) => Some(version.to_semver()?),
83d877e9
AC
66 None => None,
67 };
080f0b34 68 validate_package_name(name, "pkgid", "")?;
83d877e9 69 Ok(PackageIdSpec {
77a7e3fe 70 name: InternedString::new(name),
0247dc42 71 version,
83d877e9
AC
72 url: None,
73 })
74 }
75
7868945b 76 /// Roughly equivalent to `PackageIdSpec::parse(spec)?.query(i)`
dae87a26 77 pub fn query_str<I>(spec: &str, i: I) -> CargoResult<PackageId>
1e682848 78 where
dae87a26 79 I: IntoIterator<Item = PackageId>,
bc60f64b 80 {
1e682848 81 let spec = PackageIdSpec::parse(spec)
3a18c89a 82 .chain_err(|| anyhow::format_err!("invalid package ID specification: `{}`", spec))?;
bc60f64b
AC
83 spec.query(i)
84 }
85
7868945b
RD
86 /// Convert a `PackageId` to a `PackageIdSpec`, which will have both the `Version` and `Url`
87 /// fields filled in.
dae87a26 88 pub fn from_package_id(package_id: PackageId) -> PackageIdSpec {
83d877e9 89 PackageIdSpec {
77a7e3fe 90 name: package_id.name(),
7a2facba
AC
91 version: Some(package_id.version().clone()),
92 url: Some(package_id.source_id().url().clone()),
83d877e9
AC
93 }
94 }
95
7868945b 96 /// Tries to convert a valid `Url` to a `PackageIdSpec`.
83d877e9 97 fn from_url(mut url: Url) -> CargoResult<PackageIdSpec> {
32562e92 98 if url.query().is_some() {
3a18c89a 99 anyhow::bail!("cannot have a query string in a pkgid: {}", url)
83d877e9 100 }
32562e92
SS
101 let frag = url.fragment().map(|s| s.to_owned());
102 url.set_fragment(None);
83d877e9 103 let (name, version) = {
e5a11190
E
104 let mut path = url
105 .path_segments()
3a18c89a 106 .ok_or_else(|| anyhow::format_err!("pkgid urls must have a path: {}", url))?;
e95044e3 107 let path_name = path.next_back().ok_or_else(|| {
3a18c89a 108 anyhow::format_err!(
1e682848
AC
109 "pkgid urls must have at least one path \
110 component: {}",
111 url
112 )
82655b46 113 })?;
83d877e9
AC
114 match frag {
115 Some(fragment) => {
80fe0e6d 116 let mut parts = fragment.splitn(2, ':');
83d877e9
AC
117 let name_or_version = parts.next().unwrap();
118 match parts.next() {
119 Some(part) => {
c7de4859 120 let version = part.to_semver()?;
77a7e3fe 121 (InternedString::new(name_or_version), Some(version))
83d877e9
AC
122 }
123 None => {
1e682848 124 if name_or_version.chars().next().unwrap().is_alphabetic() {
77a7e3fe 125 (InternedString::new(name_or_version), None)
83d877e9 126 } else {
c7de4859 127 let version = name_or_version.to_semver()?;
77a7e3fe 128 (InternedString::new(path_name), Some(version))
83d877e9
AC
129 }
130 }
131 }
132 }
77a7e3fe 133 None => (InternedString::new(path_name), None),
83d877e9
AC
134 }
135 };
136 Ok(PackageIdSpec {
0247dc42
E
137 name,
138 version,
83d877e9
AC
139 url: Some(url),
140 })
141 }
142
77a7e3fe
E
143 pub fn name(&self) -> InternedString {
144 self.name
1e682848 145 }
8a6f10a7 146
1e682848
AC
147 pub fn version(&self) -> Option<&Version> {
148 self.version.as_ref()
149 }
8a6f10a7 150
1e682848
AC
151 pub fn url(&self) -> Option<&Url> {
152 self.url.as_ref()
153 }
83d877e9 154
0d038a90
AC
155 pub fn set_url(&mut self, url: Url) {
156 self.url = Some(url);
157 }
158
4a64d05e 159 /// Checks whether the given `PackageId` matches the `PackageIdSpec`.
dae87a26 160 pub fn matches(&self, package_id: PackageId) -> bool {
77a7e3fe 161 if self.name() != package_id.name() {
1e682848
AC
162 return false;
163 }
83d877e9 164
23591fe5
LL
165 if let Some(ref v) = self.version {
166 if v != package_id.version() {
167 return false;
168 }
83d877e9
AC
169 }
170
171 match self.url {
7a2facba 172 Some(ref u) => u == package_id.source_id().url(),
1e682848 173 None => true,
83d877e9
AC
174 }
175 }
bc60f64b 176
7868945b
RD
177 /// Checks a list of `PackageId`s to find 1 that matches this `PackageIdSpec`. If 0, 2, or
178 /// more are found, then this returns an error.
dae87a26 179 pub fn query<I>(&self, i: I) -> CargoResult<PackageId>
1e682848 180 where
dae87a26 181 I: IntoIterator<Item = PackageId>,
bc60f64b
AC
182 {
183 let mut ids = i.into_iter().filter(|p| self.matches(*p));
184 let ret = match ids.next() {
185 Some(id) => id,
3a18c89a 186 None => anyhow::bail!(
f7c91ba6 187 "package ID specification `{}` \
1e682848
AC
188 matched no packages",
189 self
190 ),
bc60f64b
AC
191 };
192 return match ids.next() {
193 Some(other) => {
1e682848
AC
194 let mut msg = format!(
195 "There are multiple `{}` packages in \
196 your project, and the specification \
197 `{}` is ambiguous.\n\
198 Please re-run this command \
199 with `-p <spec>` where `<spec>` is one \
200 of the following:",
201 self.name(),
202 self
203 );
bc60f64b
AC
204 let mut vec = vec![ret, other];
205 vec.extend(ids);
23591fe5 206 minimize(&mut msg, &vec, self);
3a18c89a 207 Err(anyhow::format_err!("{}", msg))
bc60f64b 208 }
1e682848 209 None => Ok(ret),
bc60f64b
AC
210 };
211
dae87a26 212 fn minimize(msg: &mut String, ids: &[PackageId], spec: &PackageIdSpec) {
bc60f64b 213 let mut version_cnt = HashMap::new();
23591fe5 214 for id in ids {
bc60f64b
AC
215 *version_cnt.entry(id.version()).or_insert(0) += 1;
216 }
23591fe5 217 for id in ids {
bc60f64b 218 if version_cnt[id.version()] == 1 {
1e682848 219 msg.push_str(&format!("\n {}:{}", spec.name(), id.version()));
bc60f64b 220 } else {
1e682848 221 msg.push_str(&format!("\n {}", PackageIdSpec::from_package_id(*id)));
bc60f64b
AC
222 }
223 }
224 }
225 }
83d877e9
AC
226}
227
213afc02 228impl fmt::Display for PackageIdSpec {
b8b7faee 229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83d877e9
AC
230 let mut printed_name = false;
231 match self.url {
232 Some(ref url) => {
32562e92 233 if url.scheme() == "cargo" {
82655b46 234 write!(f, "{}{}", url.host().unwrap(), url.path())?;
83d877e9 235 } else {
82655b46 236 write!(f, "{}", url)?;
83d877e9 237 }
77a7e3fe 238 if url.path_segments().unwrap().next_back().unwrap() != &*self.name {
83d877e9 239 printed_name = true;
82655b46 240 write!(f, "#{}", self.name)?;
83d877e9
AC
241 }
242 }
1e682848
AC
243 None => {
244 printed_name = true;
245 write!(f, "{}", self.name)?
246 }
83d877e9 247 }
23591fe5 248 if let Some(ref v) = self.version {
1e682848 249 write!(f, "{}{}", if printed_name { ":" } else { "#" }, v)?;
83d877e9
AC
250 }
251 Ok(())
252 }
253}
254
5415a341
EH
255impl ser::Serialize for PackageIdSpec {
256 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
257 where
258 S: ser::Serializer,
259 {
260 self.to_string().serialize(s)
261 }
262}
263
264impl<'de> de::Deserialize<'de> for PackageIdSpec {
265 fn deserialize<D>(d: D) -> Result<PackageIdSpec, D::Error>
266 where
267 D: de::Deserializer<'de>,
268 {
269 let string = String::deserialize(d)?;
270 PackageIdSpec::parse(&string).map_err(de::Error::custom)
271 }
272}
273
83d877e9
AC
274#[cfg(test)]
275mod tests {
32562e92 276 use super::PackageIdSpec;
77a7e3fe 277 use crate::core::interning::InternedString;
04ddd4d0 278 use crate::core::{PackageId, SourceId};
cffd5b24 279 use crate::util::ToSemver;
e5a11190 280 use url::Url;
83d877e9
AC
281
282 #[test]
283 fn good_parsing() {
284 fn ok(spec: &str, expected: PackageIdSpec) {
285 let parsed = PackageIdSpec::parse(spec).unwrap();
286 assert_eq!(parsed, expected);
25e537aa 287 assert_eq!(parsed.to_string(), spec);
83d877e9
AC
288 }
289
1e682848 290 ok(
0c3851c0 291 "https://crates.io/foo#1.2.3",
1e682848 292 PackageIdSpec {
77a7e3fe 293 name: InternedString::new("foo"),
cffd5b24 294 version: Some("1.2.3".to_semver().unwrap()),
0c3851c0 295 url: Some(Url::parse("https://crates.io/foo").unwrap()),
1e682848
AC
296 },
297 );
298 ok(
0c3851c0 299 "https://crates.io/foo#bar:1.2.3",
1e682848 300 PackageIdSpec {
77a7e3fe 301 name: InternedString::new("bar"),
cffd5b24 302 version: Some("1.2.3".to_semver().unwrap()),
0c3851c0 303 url: Some(Url::parse("https://crates.io/foo").unwrap()),
1e682848
AC
304 },
305 );
306 ok(
307 "crates.io/foo",
308 PackageIdSpec {
77a7e3fe 309 name: InternedString::new("foo"),
1e682848
AC
310 version: None,
311 url: Some(Url::parse("cargo://crates.io/foo").unwrap()),
312 },
313 );
314 ok(
315 "crates.io/foo#1.2.3",
316 PackageIdSpec {
77a7e3fe 317 name: InternedString::new("foo"),
cffd5b24 318 version: Some("1.2.3".to_semver().unwrap()),
1e682848
AC
319 url: Some(Url::parse("cargo://crates.io/foo").unwrap()),
320 },
321 );
322 ok(
323 "crates.io/foo#bar",
324 PackageIdSpec {
77a7e3fe 325 name: InternedString::new("bar"),
1e682848
AC
326 version: None,
327 url: Some(Url::parse("cargo://crates.io/foo").unwrap()),
328 },
329 );
330 ok(
331 "crates.io/foo#bar:1.2.3",
332 PackageIdSpec {
77a7e3fe 333 name: InternedString::new("bar"),
cffd5b24 334 version: Some("1.2.3".to_semver().unwrap()),
1e682848
AC
335 url: Some(Url::parse("cargo://crates.io/foo").unwrap()),
336 },
337 );
338 ok(
339 "foo",
340 PackageIdSpec {
77a7e3fe 341 name: InternedString::new("foo"),
1e682848
AC
342 version: None,
343 url: None,
344 },
345 );
346 ok(
347 "foo:1.2.3",
348 PackageIdSpec {
77a7e3fe 349 name: InternedString::new("foo"),
cffd5b24 350 version: Some("1.2.3".to_semver().unwrap()),
1e682848
AC
351 url: None,
352 },
353 );
83d877e9
AC
354 }
355
356 #[test]
357 fn bad_parsing() {
358 assert!(PackageIdSpec::parse("baz:").is_err());
b43e6dd2 359 assert!(PackageIdSpec::parse("baz:*").is_err());
83d877e9 360 assert!(PackageIdSpec::parse("baz:1.0").is_err());
0c3851c0
JC
361 assert!(PackageIdSpec::parse("https://baz:1.0").is_err());
362 assert!(PackageIdSpec::parse("https://#baz:1.0").is_err());
83d877e9
AC
363 }
364
365 #[test]
366 fn matching() {
0c3851c0 367 let url = Url::parse("https://example.com").unwrap();
dc7422b6 368 let sid = SourceId::for_registry(&url).unwrap();
e5a11190
E
369 let foo = PackageId::new("foo", "1.2.3", sid).unwrap();
370 let bar = PackageId::new("bar", "1.2.3", sid).unwrap();
83d877e9 371
dae87a26
E
372 assert!(PackageIdSpec::parse("foo").unwrap().matches(foo));
373 assert!(!PackageIdSpec::parse("foo").unwrap().matches(bar));
374 assert!(PackageIdSpec::parse("foo:1.2.3").unwrap().matches(foo));
375 assert!(!PackageIdSpec::parse("foo:1.2.2").unwrap().matches(foo));
83d877e9
AC
376 }
377}