cargo/ops/
common_for_install_and_uninstall.rs

1use std::collections::{btree_map, BTreeMap, BTreeSet};
2use std::env;
3use std::io::prelude::*;
4use std::io::SeekFrom;
5use std::path::{Path, PathBuf};
6use std::rc::Rc;
7use std::task::Poll;
8
9use anyhow::{bail, format_err, Context as _};
10use cargo_util::paths;
11use cargo_util_schemas::core::PartialVersion;
12use ops::FilterRule;
13use serde::{Deserialize, Serialize};
14
15use crate::core::compiler::{DirtyReason, Freshness};
16use crate::core::Target;
17use crate::core::{Dependency, FeatureValue, Package, PackageId, SourceId};
18use crate::ops::{self, CompileFilter, CompileOptions};
19use crate::sources::source::QueryKind;
20use crate::sources::source::Source;
21use crate::sources::PathSource;
22use crate::util::cache_lock::CacheLockMode;
23use crate::util::errors::CargoResult;
24use crate::util::GlobalContext;
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!("\
641cannot install package `{name} {ver}`, it requires rustc {msrv} or newer, while the currently active rustc version is {current}{extra}"
642)
643                }
644            }
645            let pkg = Box::new(source).download_now(summary.package_id(), gctx)?;
646            Ok(pkg)
647        }
648        None => {
649            let is_yanked: bool = if dep.version_req().is_exact() {
650                let version: String = dep.version_req().to_string();
651                if let Ok(pkg_id) =
652                    PackageId::try_new(dep.package_name(), &version[1..], source.source_id())
653                {
654                    source.invalidate_cache();
655                    loop {
656                        match source.is_yanked(pkg_id) {
657                            Poll::Ready(Ok(is_yanked)) => break is_yanked,
658                            Poll::Ready(Err(_)) => break false,
659                            Poll::Pending => source.block_until_ready()?,
660                        }
661                    }
662                } else {
663                    false
664                }
665            } else {
666                false
667            };
668            if is_yanked {
669                bail!(
670                    "cannot install package `{}`, it has been yanked from {}",
671                    dep.package_name(),
672                    source.source_id()
673                )
674            } else {
675                bail!(
676                    "could not find `{}` in {} with version `{}`",
677                    dep.package_name(),
678                    source.source_id(),
679                    dep.version_req(),
680                )
681            }
682        }
683    }
684}
685
686pub fn select_pkg<T, F>(
687    source: &mut T,
688    dep: Option<Dependency>,
689    mut list_all: F,
690    gctx: &GlobalContext,
691    current_rust_version: Option<&PartialVersion>,
692) -> CargoResult<Package>
693where
694    T: Source,
695    F: FnMut(&mut T) -> CargoResult<Vec<Package>>,
696{
697    // This operation may involve updating some sources or making a few queries
698    // which may involve frobbing caches, as a result make sure we synchronize
699    // with other global Cargos
700    let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
701
702    source.invalidate_cache();
703
704    return if let Some(dep) = dep {
705        select_dep_pkg(source, dep, gctx, false, current_rust_version)
706    } else {
707        let candidates = list_all(source)?;
708        let binaries = candidates
709            .iter()
710            .filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
711        let examples = candidates
712            .iter()
713            .filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
714        let git_url = source.source_id().url().to_string();
715        let pkg = match one(binaries, |v| multi_err("binaries", &git_url, v))? {
716            Some(p) => p,
717            None => match one(examples, |v| multi_err("examples", &git_url, v))? {
718                Some(p) => p,
719                None => bail!(
720                    "no packages found with binaries or \
721                         examples"
722                ),
723            },
724        };
725        Ok(pkg.clone())
726    };
727
728    fn multi_err(kind: &str, git_url: &str, mut pkgs: Vec<&Package>) -> String {
729        pkgs.sort_unstable_by_key(|a| a.name());
730        let first_pkg = pkgs[0];
731        format!(
732            "multiple packages with {} found: {}. When installing a git repository, \
733            cargo will always search the entire repo for any Cargo.toml.\n\
734            Please specify a package, e.g. `cargo install --git {} {}`.",
735            kind,
736            pkgs.iter()
737                .map(|p| p.name().as_str())
738                .collect::<Vec<_>>()
739                .join(", "),
740            git_url,
741            first_pkg.name()
742        )
743    }
744}
745
746/// Get one element from the iterator.
747/// Returns None if none left.
748/// Returns error if there is more than one item in the iterator.
749fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
750where
751    I: Iterator,
752    F: FnOnce(Vec<I::Item>) -> String,
753{
754    match (i.next(), i.next()) {
755        (Some(i1), Some(i2)) => {
756            let mut v = vec![i1, i2];
757            v.extend(i);
758            Err(format_err!("{}", f(v)))
759        }
760        (Some(i), None) => Ok(Some(i)),
761        (None, _) => Ok(None),
762    }
763}
764
765/// Helper to convert features to a `BTreeSet`.
766fn feature_set(features: &Rc<BTreeSet<FeatureValue>>) -> BTreeSet<String> {
767    features.iter().map(|s| s.to_string()).collect()
768}
769
770/// Helper to get the executable names from a filter.
771pub fn exe_names(pkg: &Package, filter: &ops::CompileFilter) -> BTreeSet<String> {
772    let to_exe = |name| format!("{}{}", name, env::consts::EXE_SUFFIX);
773    match filter {
774        CompileFilter::Default { .. } => pkg
775            .targets()
776            .iter()
777            .filter(|t| t.is_bin())
778            .map(|t| to_exe(t.name()))
779            .collect(),
780        CompileFilter::Only {
781            all_targets: true, ..
782        } => pkg
783            .targets()
784            .iter()
785            .filter(|target| target.is_executable())
786            .map(|target| to_exe(target.name()))
787            .collect(),
788        CompileFilter::Only {
789            ref bins,
790            ref examples,
791            ..
792        } => {
793            let collect = |rule: &_, f: fn(&Target) -> _| match rule {
794                FilterRule::All => pkg
795                    .targets()
796                    .iter()
797                    .filter(|t| f(t))
798                    .map(|t| t.name().into())
799                    .collect(),
800                FilterRule::Just(targets) => targets.clone(),
801            };
802            let all_bins = collect(bins, Target::is_bin);
803            let all_examples = collect(examples, Target::is_exe_example);
804
805            all_bins
806                .iter()
807                .chain(all_examples.iter())
808                .map(|name| to_exe(name))
809                .collect()
810        }
811    }
812}