]>
Commit | Line | Data |
---|---|---|
e023a667 | 1 | use std::collections::{btree_map, BTreeMap, BTreeSet}; |
bbc4938e | 2 | use std::env; |
3 | use std::io::prelude::*; | |
4 | use std::io::SeekFrom; | |
5 | use std::path::{Path, PathBuf}; | |
85854b18 | 6 | use std::rc::Rc; |
bbc4938e | 7 | |
3a18c89a | 8 | use anyhow::{bail, format_err}; |
bbc4938e | 9 | use serde::{Deserialize, Serialize}; |
10 | ||
e9428cba | 11 | use crate::core::compiler::Freshness; |
85854b18 | 12 | use crate::core::{Dependency, FeatureValue, Package, PackageId, Source, SourceId}; |
e023a667 | 13 | use crate::ops::{self, CompileFilter, CompileOptions}; |
bbc4938e | 14 | use crate::sources::PathSource; |
15 | use crate::util::errors::{CargoResult, CargoResultExt}; | |
137e518d | 16 | use crate::util::Config; |
e9428cba | 17 | use crate::util::{FileLock, Filesystem}; |
e023a667 EH |
18 | |
19 | /// On-disk tracking for which package installed which binary. | |
20 | /// | |
f7b29716 EH |
21 | /// v1 is an older style, v2 is a new style that tracks more information, and |
22 | /// is both backwards and forwards compatible. Cargo keeps both files in sync, | |
23 | /// updating both v1 and v2 at the same time. Additionally, if it detects | |
24 | /// changes in v1 that are not in v2 (such as when an older version of Cargo | |
25 | /// is used), it will automatically propagate those changes to v2. | |
e023a667 EH |
26 | /// |
27 | /// This maintains a filesystem lock, preventing other instances of Cargo from | |
28 | /// modifying at the same time. Drop the value to unlock. | |
9ecdfe04 | 29 | /// |
f7b29716 EH |
30 | /// It is intended that v1 should be retained for a while during a longish |
31 | /// transition period, and then v1 can be removed. | |
e023a667 EH |
32 | pub struct InstallTracker { |
33 | v1: CrateListingV1, | |
34 | v2: CrateListingV2, | |
35 | v1_lock: FileLock, | |
f7b29716 | 36 | v2_lock: FileLock, |
e023a667 EH |
37 | } |
38 | ||
9ecdfe04 | 39 | /// Tracking information for the set of installed packages. |
e023a667 EH |
40 | #[derive(Default, Deserialize, Serialize)] |
41 | struct CrateListingV2 { | |
f7b29716 | 42 | /// Map of every installed package. |
e023a667 | 43 | installs: BTreeMap<PackageId, InstallInfo>, |
f7b29716 EH |
44 | /// Forwards compatibility. Unknown keys from future versions of Cargo |
45 | /// will be stored here and retained when the file is saved. | |
e023a667 EH |
46 | #[serde(flatten)] |
47 | other: BTreeMap<String, serde_json::Value>, | |
bbc4938e | 48 | } |
49 | ||
9ecdfe04 EH |
50 | /// Tracking information for the installation of a single package. |
51 | /// | |
52 | /// This tracks the settings that were used when the package was installed. | |
53 | /// Future attempts to install the same package will check these settings to | |
54 | /// determine if it needs to be rebuilt/reinstalled. If nothing has changed, | |
55 | /// then Cargo will inform the user that it is "up to date". | |
56 | /// | |
f7b29716 | 57 | /// This is only used for the v2 format. |
e023a667 EH |
58 | #[derive(Debug, Deserialize, Serialize)] |
59 | struct InstallInfo { | |
60 | /// Version requested via `--version`. | |
61 | /// None if `--version` not specified. Currently not used, possibly may be | |
62 | /// used in the future. | |
63 | version_req: Option<String>, | |
64 | /// Set of binary names installed. | |
65 | bins: BTreeSet<String>, | |
66 | /// Set of features explicitly enabled. | |
67 | features: BTreeSet<String>, | |
68 | all_features: bool, | |
69 | no_default_features: bool, | |
70 | /// Either "debug" or "release". | |
71 | profile: String, | |
72 | /// The installation target. | |
73 | /// Either the host or the value specified in `--target`. | |
74 | /// None if unknown (when loading from v1). | |
75 | target: Option<String>, | |
76 | /// Output of `rustc -V`. | |
77 | /// None if unknown (when loading from v1). | |
78 | /// Currently not used, possibly may be used in the future. | |
79 | rustc: Option<String>, | |
80 | /// Forwards compatibility. | |
81 | #[serde(flatten)] | |
82 | other: BTreeMap<String, serde_json::Value>, | |
83 | } | |
bbc4938e | 84 | |
9ecdfe04 | 85 | /// Tracking information for the set of installed packages. |
e023a667 | 86 | #[derive(Default, Deserialize, Serialize)] |
bbc4938e | 87 | pub struct CrateListingV1 { |
f7b29716 | 88 | /// Map of installed package id to the set of binary names for that package. |
1b71fa3e | 89 | v1: BTreeMap<PackageId, BTreeSet<String>>, |
bbc4938e | 90 | } |
91 | ||
e023a667 EH |
92 | impl InstallTracker { |
93 | /// Create an InstallTracker from information on disk. | |
94 | pub fn load(config: &Config, root: &Filesystem) -> CargoResult<InstallTracker> { | |
e023a667 | 95 | let v1_lock = root.open_rw(Path::new(".crates.toml"), config, "crate metadata")?; |
f7b29716 | 96 | let v2_lock = root.open_rw(Path::new(".crates2.json"), config, "crate metadata")?; |
e023a667 EH |
97 | |
98 | let v1 = (|| -> CargoResult<_> { | |
99 | let mut contents = String::new(); | |
100 | v1_lock.file().read_to_string(&mut contents)?; | |
101 | if contents.is_empty() { | |
102 | Ok(CrateListingV1::default()) | |
103 | } else { | |
104 | Ok(toml::from_str(&contents) | |
105 | .chain_err(|| format_err!("invalid TOML found for metadata"))?) | |
106 | } | |
107 | })() | |
108 | .chain_err(|| { | |
109 | format_err!( | |
110 | "failed to parse crate metadata at `{}`", | |
111 | v1_lock.path().to_string_lossy() | |
112 | ) | |
113 | })?; | |
114 | ||
115 | let v2 = (|| -> CargoResult<_> { | |
f7b29716 EH |
116 | let mut contents = String::new(); |
117 | v2_lock.file().read_to_string(&mut contents)?; | |
118 | let mut v2 = if contents.is_empty() { | |
119 | CrateListingV2::default() | |
120 | } else { | |
121 | serde_json::from_str(&contents) | |
122 | .chain_err(|| format_err!("invalid JSON found for metadata"))? | |
123 | }; | |
d12a8bae | 124 | v2.sync_v1(&v1); |
f7b29716 | 125 | Ok(v2) |
e023a667 EH |
126 | })() |
127 | .chain_err(|| { | |
128 | format_err!( | |
129 | "failed to parse crate metadata at `{}`", | |
f7b29716 | 130 | v2_lock.path().to_string_lossy() |
e023a667 EH |
131 | ) |
132 | })?; | |
133 | ||
134 | Ok(InstallTracker { | |
135 | v1, | |
136 | v2, | |
137 | v1_lock, | |
138 | v2_lock, | |
e023a667 EH |
139 | }) |
140 | } | |
141 | ||
142 | /// Checks if the given package should be built, and checks if executables | |
143 | /// already exist in the destination directory. | |
144 | /// | |
145 | /// Returns a tuple `(freshness, map)`. `freshness` indicates if the | |
146 | /// package should be built (`Dirty`) or if it is already up-to-date | |
9ecdfe04 EH |
147 | /// (`Fresh`) and should be skipped. The map maps binary names to the |
148 | /// PackageId that installed it (which is None if not known). | |
149 | /// | |
150 | /// If there are no duplicates, then it will be considered `Dirty` (i.e., | |
151 | /// it is OK to build/install). | |
152 | /// | |
153 | /// `force=true` will always be considered `Dirty` (i.e., it will always | |
154 | /// be rebuilt/reinstalled). | |
e023a667 EH |
155 | /// |
156 | /// Returns an error if there is a duplicate and `--force` is not used. | |
157 | pub fn check_upgrade( | |
158 | &self, | |
159 | dst: &Path, | |
160 | pkg: &Package, | |
161 | force: bool, | |
e4918c45 | 162 | opts: &CompileOptions, |
e023a667 EH |
163 | target: &str, |
164 | _rustc: &str, | |
165 | ) -> CargoResult<(Freshness, BTreeMap<String, Option<PackageId>>)> { | |
166 | let exes = exe_names(pkg, &opts.filter); | |
9ecdfe04 | 167 | // Check if any tracked exe's are already installed. |
e023a667 EH |
168 | let duplicates = self.find_duplicates(dst, &exes); |
169 | if force || duplicates.is_empty() { | |
170 | return Ok((Freshness::Dirty, duplicates)); | |
171 | } | |
9ecdfe04 EH |
172 | // Check if all duplicates come from packages of the same name. If |
173 | // there are duplicates from other packages, then --force will be | |
174 | // required. | |
175 | // | |
176 | // There may be multiple matching duplicates if different versions of | |
177 | // the same package installed different binaries. | |
5a9ff8a6 EH |
178 | // |
179 | // This does not check the source_id in order to allow the user to | |
180 | // switch between different sources. For example, installing from git, | |
181 | // and then switching to the official crates.io release or vice-versa. | |
182 | // If the source_id were included, then the user would get possibly | |
183 | // confusing errors like "package `foo 1.0.0` is already installed" | |
184 | // and the change of source may not be obvious why it fails. | |
e023a667 EH |
185 | let matching_duplicates: Vec<PackageId> = duplicates |
186 | .values() | |
187 | .filter_map(|v| match v { | |
188 | Some(dupe_pkg_id) if dupe_pkg_id.name() == pkg.name() => Some(*dupe_pkg_id), | |
189 | _ => None, | |
190 | }) | |
191 | .collect(); | |
192 | ||
9ecdfe04 EH |
193 | // If both sets are the same length, that means all duplicates come |
194 | // from packages with the same name. | |
f7b29716 | 195 | if matching_duplicates.len() == duplicates.len() { |
9ecdfe04 | 196 | // Determine if it is dirty or fresh. |
e023a667 EH |
197 | let source_id = pkg.package_id().source_id(); |
198 | if source_id.is_path() { | |
9ecdfe04 | 199 | // `cargo install --path ...` is always rebuilt. |
e023a667 EH |
200 | return Ok((Freshness::Dirty, duplicates)); |
201 | } | |
ec21e12d | 202 | let is_up_to_date = |dupe_pkg_id| { |
e023a667 EH |
203 | let info = self |
204 | .v2 | |
205 | .installs | |
206 | .get(dupe_pkg_id) | |
207 | .expect("dupes must be in sync"); | |
208 | let precise_equal = if source_id.is_git() { | |
9ecdfe04 EH |
209 | // Git sources must have the exact same hash to be |
210 | // considered "fresh". | |
e023a667 EH |
211 | dupe_pkg_id.source_id().precise() == source_id.precise() |
212 | } else { | |
213 | true | |
214 | }; | |
215 | ||
216 | dupe_pkg_id.version() == pkg.version() | |
217 | && dupe_pkg_id.source_id() == source_id | |
218 | && precise_equal | |
9ecdfe04 | 219 | && info.is_up_to_date(opts, target, &exes) |
ec21e12d EH |
220 | }; |
221 | if matching_duplicates.iter().all(is_up_to_date) { | |
e023a667 EH |
222 | Ok((Freshness::Fresh, duplicates)) |
223 | } else { | |
224 | Ok((Freshness::Dirty, duplicates)) | |
225 | } | |
226 | } else { | |
227 | // Format the error message. | |
228 | let mut msg = String::new(); | |
229 | for (bin, p) in duplicates.iter() { | |
230 | msg.push_str(&format!("binary `{}` already exists in destination", bin)); | |
231 | if let Some(p) = p.as_ref() { | |
232 | msg.push_str(&format!(" as part of `{}`\n", p)); | |
233 | } else { | |
d5541331 | 234 | msg.push('\n'); |
e023a667 EH |
235 | } |
236 | } | |
237 | msg.push_str("Add --force to overwrite"); | |
238 | bail!("{}", msg); | |
239 | } | |
240 | } | |
241 | ||
9ecdfe04 EH |
242 | /// Check if any executables are already installed. |
243 | /// | |
244 | /// Returns a map of duplicates, the key is the executable name and the | |
245 | /// value is the PackageId that is already installed. The PackageId is | |
246 | /// None if it is an untracked executable. | |
e023a667 EH |
247 | fn find_duplicates( |
248 | &self, | |
249 | dst: &Path, | |
250 | exes: &BTreeSet<String>, | |
251 | ) -> BTreeMap<String, Option<PackageId>> { | |
252 | exes.iter() | |
253 | .filter_map(|name| { | |
254 | if !dst.join(&name).exists() { | |
255 | None | |
e023a667 | 256 | } else { |
f7b29716 | 257 | let p = self.v2.package_for_bin(name); |
e023a667 EH |
258 | Some((name.clone(), p)) |
259 | } | |
260 | }) | |
261 | .collect() | |
262 | } | |
263 | ||
264 | /// Mark that a package was installed. | |
265 | pub fn mark_installed( | |
266 | &mut self, | |
267 | package: &Package, | |
268 | bins: &BTreeSet<String>, | |
269 | version_req: Option<String>, | |
e4918c45 | 270 | opts: &CompileOptions, |
593a02f2 AC |
271 | target: &str, |
272 | rustc: &str, | |
e023a667 | 273 | ) { |
f7b29716 EH |
274 | self.v2 |
275 | .mark_installed(package, bins, version_req, opts, target, rustc); | |
e023a667 EH |
276 | self.v1.mark_installed(package, bins); |
277 | } | |
278 | ||
279 | /// Save tracking information to disk. | |
280 | pub fn save(&self) -> CargoResult<()> { | |
281 | self.v1.save(&self.v1_lock).chain_err(|| { | |
282 | format_err!( | |
283 | "failed to write crate metadata at `{}`", | |
284 | self.v1_lock.path().to_string_lossy() | |
285 | ) | |
286 | })?; | |
287 | ||
f7b29716 EH |
288 | self.v2.save(&self.v2_lock).chain_err(|| { |
289 | format_err!( | |
290 | "failed to write crate metadata at `{}`", | |
291 | self.v2_lock.path().to_string_lossy() | |
292 | ) | |
293 | })?; | |
e023a667 EH |
294 | Ok(()) |
295 | } | |
296 | ||
297 | /// Iterator of all installed binaries. | |
298 | /// Items are `(pkg_id, bins)` where `bins` is the set of binaries that | |
299 | /// package installed. | |
300 | pub fn all_installed_bins(&self) -> impl Iterator<Item = (&PackageId, &BTreeSet<String>)> { | |
301 | self.v1.v1.iter() | |
302 | } | |
303 | ||
304 | /// Set of binaries installed by a particular package. | |
305 | /// Returns None if the package is not installed. | |
306 | pub fn installed_bins(&self, pkg_id: PackageId) -> Option<&BTreeSet<String>> { | |
307 | self.v1.v1.get(&pkg_id) | |
308 | } | |
309 | ||
310 | /// Remove a package from the tracker. | |
311 | pub fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) { | |
312 | self.v1.remove(pkg_id, bins); | |
f7b29716 | 313 | self.v2.remove(pkg_id, bins); |
e023a667 EH |
314 | } |
315 | } | |
316 | ||
3673016f | 317 | impl CrateListingV1 { |
e023a667 EH |
318 | fn mark_installed(&mut self, pkg: &Package, bins: &BTreeSet<String>) { |
319 | // Remove bins from any other packages. | |
320 | for other_bins in self.v1.values_mut() { | |
321 | for bin in bins { | |
322 | other_bins.remove(bin); | |
323 | } | |
324 | } | |
9ecdfe04 | 325 | // Remove entries where `bins` is empty. |
e023a667 EH |
326 | let to_remove = self |
327 | .v1 | |
328 | .iter() | |
329 | .filter_map(|(&p, set)| if set.is_empty() { Some(p) } else { None }) | |
330 | .collect::<Vec<_>>(); | |
331 | for p in to_remove.iter() { | |
332 | self.v1.remove(p); | |
333 | } | |
334 | // Add these bins. | |
e023a667 EH |
335 | self.v1 |
336 | .entry(pkg.package_id()) | |
9ecdfe04 EH |
337 | .or_insert_with(BTreeSet::new) |
338 | .append(&mut bins.clone()); | |
e023a667 EH |
339 | } |
340 | ||
341 | fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) { | |
342 | let mut installed = match self.v1.entry(pkg_id) { | |
343 | btree_map::Entry::Occupied(e) => e, | |
344 | btree_map::Entry::Vacant(..) => panic!("v1 unexpected missing `{}`", pkg_id), | |
345 | }; | |
346 | ||
347 | for bin in bins { | |
348 | installed.get_mut().remove(bin); | |
349 | } | |
350 | if installed.get().is_empty() { | |
351 | installed.remove(); | |
352 | } | |
353 | } | |
354 | ||
355 | fn save(&self, lock: &FileLock) -> CargoResult<()> { | |
356 | let mut file = lock.file(); | |
357 | file.seek(SeekFrom::Start(0))?; | |
358 | file.set_len(0)?; | |
359 | let data = toml::to_string(self)?; | |
360 | file.write_all(data.as_bytes())?; | |
361 | Ok(()) | |
2a350a32 | 362 | } |
3673016f | 363 | } |
364 | ||
e023a667 | 365 | impl CrateListingV2 { |
9ecdfe04 EH |
366 | /// Incorporate any changes from v1 into self. |
367 | /// This handles the initial upgrade to v2, *and* handles the case | |
368 | /// where v2 is in use, and a v1 update is made, then v2 is used again. | |
369 | /// i.e., `cargo +new install foo ; cargo +old install bar ; cargo +new install bar` | |
370 | /// For now, v1 is the source of truth, so its values are trusted over v2. | |
d12a8bae | 371 | fn sync_v1(&mut self, v1: &CrateListingV1) { |
e023a667 EH |
372 | // Make the `bins` entries the same. |
373 | for (pkg_id, bins) in &v1.v1 { | |
374 | self.installs | |
375 | .entry(*pkg_id) | |
376 | .and_modify(|info| info.bins = bins.clone()) | |
377 | .or_insert_with(|| InstallInfo::from_v1(bins)); | |
378 | } | |
379 | // Remove any packages that aren't present in v1. | |
380 | let to_remove: Vec<_> = self | |
381 | .installs | |
382 | .keys() | |
383 | .filter(|pkg_id| !v1.v1.contains_key(pkg_id)) | |
384 | .cloned() | |
385 | .collect(); | |
386 | for pkg_id in to_remove { | |
387 | self.installs.remove(&pkg_id); | |
388 | } | |
e023a667 EH |
389 | } |
390 | ||
391 | fn package_for_bin(&self, bin_name: &str) -> Option<PackageId> { | |
392 | self.installs | |
393 | .iter() | |
394 | .find(|(_, info)| info.bins.contains(bin_name)) | |
395 | .map(|(pkg_id, _)| *pkg_id) | |
396 | } | |
397 | ||
398 | fn mark_installed( | |
399 | &mut self, | |
400 | pkg: &Package, | |
401 | bins: &BTreeSet<String>, | |
402 | version_req: Option<String>, | |
e4918c45 | 403 | opts: &CompileOptions, |
593a02f2 AC |
404 | target: &str, |
405 | rustc: &str, | |
e023a667 EH |
406 | ) { |
407 | // Remove bins from any other packages. | |
408 | for info in &mut self.installs.values_mut() { | |
409 | for bin in bins { | |
410 | info.bins.remove(bin); | |
411 | } | |
412 | } | |
9ecdfe04 | 413 | // Remove entries where `bins` is empty. |
e023a667 EH |
414 | let to_remove = self |
415 | .installs | |
416 | .iter() | |
417 | .filter_map(|(&p, info)| if info.bins.is_empty() { Some(p) } else { None }) | |
418 | .collect::<Vec<_>>(); | |
419 | for p in to_remove.iter() { | |
420 | self.installs.remove(p); | |
421 | } | |
422 | // Add these bins. | |
423 | if let Some(info) = self.installs.get_mut(&pkg.package_id()) { | |
9ecdfe04 | 424 | info.bins.append(&mut bins.clone()); |
e023a667 | 425 | info.version_req = version_req; |
85854b18 EH |
426 | info.features = feature_set(&opts.cli_features.features); |
427 | info.all_features = opts.cli_features.all_features; | |
428 | info.no_default_features = !opts.cli_features.uses_default_features; | |
77ee608d | 429 | info.profile = opts.build_config.requested_profile.to_string(); |
593a02f2 AC |
430 | info.target = Some(target.to_string()); |
431 | info.rustc = Some(rustc.to_string()); | |
e023a667 EH |
432 | } else { |
433 | self.installs.insert( | |
434 | pkg.package_id(), | |
435 | InstallInfo { | |
436 | version_req, | |
437 | bins: bins.clone(), | |
85854b18 EH |
438 | features: feature_set(&opts.cli_features.features), |
439 | all_features: opts.cli_features.all_features, | |
440 | no_default_features: !opts.cli_features.uses_default_features, | |
77ee608d | 441 | profile: opts.build_config.requested_profile.to_string(), |
593a02f2 AC |
442 | target: Some(target.to_string()), |
443 | rustc: Some(rustc.to_string()), | |
e023a667 EH |
444 | other: BTreeMap::new(), |
445 | }, | |
446 | ); | |
447 | } | |
448 | } | |
449 | ||
450 | fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) { | |
451 | let mut info_entry = match self.installs.entry(pkg_id) { | |
452 | btree_map::Entry::Occupied(e) => e, | |
453 | btree_map::Entry::Vacant(..) => panic!("v2 unexpected missing `{}`", pkg_id), | |
454 | }; | |
455 | ||
456 | for bin in bins { | |
457 | info_entry.get_mut().bins.remove(bin); | |
458 | } | |
459 | if info_entry.get().bins.is_empty() { | |
460 | info_entry.remove(); | |
461 | } | |
462 | } | |
463 | ||
464 | fn save(&self, lock: &FileLock) -> CargoResult<()> { | |
465 | let mut file = lock.file(); | |
466 | file.seek(SeekFrom::Start(0))?; | |
467 | file.set_len(0)?; | |
9ecdfe04 EH |
468 | let data = serde_json::to_string(self)?; |
469 | file.write_all(data.as_bytes())?; | |
e023a667 EH |
470 | Ok(()) |
471 | } | |
472 | } | |
473 | ||
474 | impl InstallInfo { | |
475 | fn from_v1(set: &BTreeSet<String>) -> InstallInfo { | |
476 | InstallInfo { | |
477 | version_req: None, | |
478 | bins: set.clone(), | |
479 | features: BTreeSet::new(), | |
480 | all_features: false, | |
481 | no_default_features: false, | |
482 | profile: "release".to_string(), | |
483 | target: None, | |
484 | rustc: None, | |
485 | other: BTreeMap::new(), | |
486 | } | |
487 | } | |
9ecdfe04 EH |
488 | |
489 | /// Determine if this installation is "up to date", or if it needs to be reinstalled. | |
490 | /// | |
491 | /// This does not do Package/Source/Version checking. | |
e4918c45 | 492 | fn is_up_to_date(&self, opts: &CompileOptions, target: &str, exes: &BTreeSet<String>) -> bool { |
85854b18 EH |
493 | self.features == feature_set(&opts.cli_features.features) |
494 | && self.all_features == opts.cli_features.all_features | |
495 | && self.no_default_features == !opts.cli_features.uses_default_features | |
77ee608d | 496 | && self.profile.as_str() == opts.build_config.requested_profile.as_str() |
d47a9545 | 497 | && (self.target.is_none() || self.target.as_deref() == Some(target)) |
9ecdfe04 EH |
498 | && &self.bins == exes |
499 | } | |
e023a667 EH |
500 | } |
501 | ||
502 | /// Determines the root directory where installation is done. | |
bbc4938e | 503 | pub fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult<Filesystem> { |
504 | let config_root = config.get_path("install.root")?; | |
505 | Ok(flag | |
506 | .map(PathBuf::from) | |
507 | .or_else(|| env::var_os("CARGO_INSTALL_ROOT").map(PathBuf::from)) | |
508 | .or_else(move || config_root.map(|v| v.val)) | |
509 | .map(Filesystem::new) | |
510 | .unwrap_or_else(|| config.home().clone())) | |
511 | } | |
512 | ||
e023a667 | 513 | /// Determines the `PathSource` from a `SourceId`. |
e97fef02 | 514 | pub fn path_source(source_id: SourceId, config: &Config) -> CargoResult<PathSource<'_>> { |
bbc4938e | 515 | let path = source_id |
516 | .url() | |
517 | .to_file_path() | |
e023a667 | 518 | .map_err(|()| format_err!("path sources must have a valid path"))?; |
bbc4938e | 519 | Ok(PathSource::new(&path, source_id, config)) |
520 | } | |
521 | ||
e023a667 | 522 | /// Gets a Package based on command-line requirements. |
d2b27752 | 523 | pub fn select_dep_pkg<T>( |
137e518d | 524 | source: &mut T, |
d2b27752 | 525 | dep: Dependency, |
bbc4938e | 526 | config: &Config, |
137e518d | 527 | needs_update: bool, |
8a301589 | 528 | ) -> CargoResult<Package> |
bbc4938e | 529 | where |
3c96c054 | 530 | T: Source, |
bbc4938e | 531 | { |
5217280e AC |
532 | // This operation may involve updating some sources or making a few queries |
533 | // which may involve frobbing caches, as a result make sure we synchronize | |
534 | // with other global Cargos | |
535 | let _lock = config.acquire_package_cache_lock()?; | |
536 | ||
137e518d | 537 | if needs_update { |
bbc4938e | 538 | source.update()?; |
539 | } | |
540 | ||
d2b27752 DWH |
541 | let deps = source.query_vec(&dep)?; |
542 | match deps.iter().map(|p| p.package_id()).max() { | |
543 | Some(pkgid) => { | |
544 | let pkg = Box::new(source).download_now(pkgid, config)?; | |
545 | Ok(pkg) | |
bbc4938e | 546 | } |
8bea1b07 | 547 | None => { |
81687e79 | 548 | let is_yanked: bool = if dep.version_req().is_exact() { |
549 | let version: String = dep.version_req().to_string(); | |
550 | PackageId::new(dep.package_name(), &version[1..], source.source_id()) | |
551 | .map_or(false, |pkg_id| source.is_yanked(pkg_id).unwrap_or(false)) | |
3952fdb2 | 552 | } else { |
81687e79 | 553 | false |
554 | }; | |
3952fdb2 | 555 | if is_yanked { |
556 | bail!( | |
557 | "cannot install package `{}`, it has been yanked from {}", | |
558 | dep.package_name(), | |
559 | source.source_id() | |
560 | ) | |
561 | } else { | |
562 | bail!( | |
844cde20 | 563 | "could not find `{}` in {} with version `{}`", |
564 | dep.package_name(), | |
565 | source.source_id(), | |
566 | dep.version_req(), | |
3952fdb2 | 567 | ) |
8bea1b07 | 568 | } |
569 | } | |
d2b27752 DWH |
570 | } |
571 | } | |
572 | ||
573 | pub fn select_pkg<T, F>( | |
574 | source: &mut T, | |
575 | dep: Option<Dependency>, | |
576 | mut list_all: F, | |
577 | config: &Config, | |
578 | ) -> CargoResult<Package> | |
579 | where | |
580 | T: Source, | |
581 | F: FnMut(&mut T) -> CargoResult<Vec<Package>>, | |
582 | { | |
583 | // This operation may involve updating some sources or making a few queries | |
584 | // which may involve frobbing caches, as a result make sure we synchronize | |
585 | // with other global Cargos | |
586 | let _lock = config.acquire_package_cache_lock()?; | |
587 | ||
588 | source.update()?; | |
589 | ||
590 | return if let Some(dep) = dep { | |
591 | select_dep_pkg(source, dep, config, false) | |
592 | } else { | |
593 | let candidates = list_all(source)?; | |
594 | let binaries = candidates | |
595 | .iter() | |
596 | .filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0); | |
597 | let examples = candidates | |
598 | .iter() | |
599 | .filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0); | |
600 | let pkg = match one(binaries, |v| multi_err("binaries", v))? { | |
601 | Some(p) => p, | |
602 | None => match one(examples, |v| multi_err("examples", v))? { | |
e023a667 | 603 | Some(p) => p, |
d2b27752 DWH |
604 | None => bail!( |
605 | "no packages found with binaries or \ | |
606 | examples" | |
607 | ), | |
608 | }, | |
609 | }; | |
610 | Ok(pkg.clone()) | |
611 | }; | |
612 | ||
613 | fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String { | |
614 | pkgs.sort_unstable_by_key(|a| a.name()); | |
615 | format!( | |
616 | "multiple packages with {} found: {}", | |
617 | kind, | |
618 | pkgs.iter() | |
619 | .map(|p| p.name().as_str()) | |
620 | .collect::<Vec<_>>() | |
621 | .join(", ") | |
622 | ) | |
bbc4938e | 623 | } |
624 | } | |
625 | ||
e023a667 EH |
626 | /// Get one element from the iterator. |
627 | /// Returns None if none left. | |
628 | /// Returns error if there is more than one item in the iterator. | |
629 | fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>> | |
bbc4938e | 630 | where |
631 | I: Iterator, | |
632 | F: FnOnce(Vec<I::Item>) -> String, | |
633 | { | |
634 | match (i.next(), i.next()) { | |
635 | (Some(i1), Some(i2)) => { | |
636 | let mut v = vec![i1, i2]; | |
637 | v.extend(i); | |
e023a667 | 638 | Err(format_err!("{}", f(v))) |
bbc4938e | 639 | } |
640 | (Some(i), None) => Ok(Some(i)), | |
641 | (None, _) => Ok(None), | |
642 | } | |
643 | } | |
644 | ||
85854b18 EH |
645 | /// Helper to convert features to a BTreeSet. |
646 | fn feature_set(features: &Rc<BTreeSet<FeatureValue>>) -> BTreeSet<String> { | |
647 | features.iter().map(|s| s.to_string()).collect() | |
bbc4938e | 648 | } |
649 | ||
e023a667 EH |
650 | /// Helper to get the executable names from a filter. |
651 | pub fn exe_names(pkg: &Package, filter: &ops::CompileFilter) -> BTreeSet<String> { | |
652 | let to_exe = |name| format!("{}{}", name, env::consts::EXE_SUFFIX); | |
653 | match filter { | |
654 | CompileFilter::Default { .. } => pkg | |
655 | .targets() | |
656 | .iter() | |
657 | .filter(|t| t.is_bin()) | |
658 | .map(|t| to_exe(t.name())) | |
659 | .collect(), | |
5a59b809 EH |
660 | CompileFilter::Only { |
661 | all_targets: true, .. | |
662 | } => pkg | |
663 | .targets() | |
664 | .iter() | |
665 | .filter(|target| target.is_executable()) | |
666 | .map(|target| to_exe(target.name())) | |
667 | .collect(), | |
e023a667 EH |
668 | CompileFilter::Only { |
669 | ref bins, | |
670 | ref examples, | |
671 | .. | |
672 | } => { | |
673 | let all_bins: Vec<String> = bins.try_collect().unwrap_or_else(|| { | |
674 | pkg.targets() | |
675 | .iter() | |
676 | .filter(|t| t.is_bin()) | |
677 | .map(|t| t.name().to_string()) | |
678 | .collect() | |
679 | }); | |
680 | let all_examples: Vec<String> = examples.try_collect().unwrap_or_else(|| { | |
681 | pkg.targets() | |
682 | .iter() | |
683 | .filter(|t| t.is_exe_example()) | |
684 | .map(|t| t.name().to_string()) | |
685 | .collect() | |
686 | }); | |
687 | ||
688 | all_bins | |
689 | .iter() | |
690 | .chain(all_examples.iter()) | |
691 | .map(|name| to_exe(name)) | |
692 | .collect() | |
693 | } | |
694 | } | |
bbc4938e | 695 | } |