cargo/ops/
common_for_install_and_uninstall.rs

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