#
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}