]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/common_for_install_and_uninstall.rs
Print environment note for json format, too.
[cargo.git] / src / cargo / ops / common_for_install_and_uninstall.rs
CommitLineData
e023a667 1use std::collections::{btree_map, BTreeMap, BTreeSet};
bbc4938e 2use std::env;
3use std::io::prelude::*;
4use std::io::SeekFrom;
5use std::path::{Path, PathBuf};
85854b18 6use std::rc::Rc;
bbc4938e 7
3a18c89a 8use anyhow::{bail, format_err};
bbc4938e 9use serde::{Deserialize, Serialize};
10
e9428cba 11use crate::core::compiler::Freshness;
85854b18 12use crate::core::{Dependency, FeatureValue, Package, PackageId, Source, SourceId};
e023a667 13use crate::ops::{self, CompileFilter, CompileOptions};
bbc4938e 14use crate::sources::PathSource;
15use crate::util::errors::{CargoResult, CargoResultExt};
137e518d 16use crate::util::Config;
e9428cba 17use 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
32pub 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)]
41struct 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)]
59struct 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 87pub 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
92impl 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 317impl 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 365impl 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
474impl 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 503pub 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 514pub 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 523pub 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 529where
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
573pub fn select_pkg<T, F>(
574 source: &mut T,
575 dep: Option<Dependency>,
576 mut list_all: F,
577 config: &Config,
578) -> CargoResult<Package>
579where
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.
629fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
bbc4938e 630where
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.
646fn 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.
651pub 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}