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