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