Skip to main content

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;
7
8use anyhow::{Context as _, bail, format_err};
9use cargo_util::paths;
10use cargo_util_schemas::core::PartialVersion;
11use cargo_util_terminal::report::Level;
12use ops::FilterRule;
13use serde::{Deserialize, Serialize};
14
15use crate::core::compiler::{DirtyReason, Freshness};
16use crate::core::{Dependency, FeatureValue, Package, PackageId, SourceId};
17use crate::core::{PackageSet, Target};
18use crate::ops::{self, CompileFilter, CompileOptions};
19use crate::sources::PathSource;
20use crate::sources::source::{QueryKind, Source, SourceMap};
21use crate::util::GlobalContext;
22use crate::util::cache_lock::CacheLockMode;
23use crate::util::context::{ConfigRelativePath, Definition};
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 = match gctx.get::<Option<ConfigRelativePath>>("install.root")? {
549        Some(p) => {
550            let resolved = p.resolve_program(gctx);
551            if resolved.is_relative() {
552                let definition = p.value().definition.clone();
553                if matches!(definition, Definition::Path(_)) {
554                    let suggested = format!("{}/", resolved.display());
555                    let notes = [
556                        Level::NOTE.message("a future version of Cargo will treat it as relative to the configuration directory"),
557                        Level::HELP.message(format!("add a trailing slash (`{}`) to adopt the correct behavior and silence this warning", suggested)),
558                        Level::NOTE.message("see more at https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths"),
559                    ];
560                    gctx.shell().print_report(
561                        &[Level::WARNING
562                            .secondary_title(format!(
563                                "the `install.root` value `{}` defined in {} without a trailing slash is deprecated",
564                                resolved.display(),
565                                definition
566                            ))
567                            .elements(notes)],
568                        false,
569                    )?;
570                }
571            }
572            Some(resolved)
573        }
574        None => None,
575    };
576
577    Ok(flag
578        .map(PathBuf::from)
579        .or_else(|| gctx.get_env_os("CARGO_INSTALL_ROOT").map(PathBuf::from))
580        .or_else(|| config_root)
581        .map(Filesystem::new)
582        .unwrap_or_else(|| gctx.home().clone()))
583}
584
585/// Determines the `PathSource` from a `SourceId`.
586pub fn path_source(source_id: SourceId, gctx: &GlobalContext) -> CargoResult<PathSource<'_>> {
587    let path = source_id
588        .url()
589        .to_file_path()
590        .map_err(|()| format_err!("path sources must have a valid path"))?;
591    Ok(PathSource::new(&path, source_id, gctx))
592}
593
594/// Gets a Package based on command-line requirements.
595pub fn select_dep_pkg(
596    source: &mut dyn Source,
597    dep: Dependency,
598    gctx: &GlobalContext,
599    needs_update: bool,
600    current_rust_version: Option<&PartialVersion>,
601) -> CargoResult<Package> {
602    // This operation may involve updating some sources or making a few queries
603    // which may involve frobbing caches, as a result make sure we synchronize
604    // with other global Cargos
605    let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
606
607    if needs_update {
608        source.invalidate_cache();
609    }
610
611    let deps = crate::util::block_on(source.query_vec(&dep, QueryKind::Exact))?;
612    match deps
613        .iter()
614        .map(|s| s.as_summary())
615        .max_by_key(|p| p.package_id())
616    {
617        Some(summary) => {
618            if let (Some(current), Some(msrv)) = (current_rust_version, summary.rust_version()) {
619                if !msrv.is_compatible_with(current) {
620                    let name = summary.name();
621                    let ver = summary.version();
622                    let extra = if dep.source_id().is_registry() {
623                        // Match any version, not just the selected
624                        let msrv_dep =
625                            Dependency::parse(dep.package_name(), None, dep.source_id())?;
626                        let msrv_deps =
627                            crate::util::block_on(source.query_vec(&msrv_dep, QueryKind::Exact))?;
628                        if let Some(alt) = msrv_deps
629                            .iter()
630                            .map(|s| s.as_summary())
631                            .filter(|summary| {
632                                summary
633                                    .rust_version()
634                                    .map(|msrv| msrv.is_compatible_with(current))
635                                    .unwrap_or(true)
636                            })
637                            .max_by_key(|s| s.package_id())
638                        {
639                            if let Some(rust_version) = alt.rust_version() {
640                                format!(
641                                    "\n`{name} {}` supports rustc {rust_version}",
642                                    alt.version()
643                                )
644                            } else {
645                                format!(
646                                    "\n`{name} {}` has an unspecified minimum rustc version",
647                                    alt.version()
648                                )
649                            }
650                        } else {
651                            String::new()
652                        }
653                    } else {
654                        String::new()
655                    };
656                    bail!(
657                        "\
658cannot install package `{name} {ver}`, it requires rustc {msrv} or newer, while the currently active rustc version is {current}{extra}"
659                    )
660                }
661            }
662            // Download the package immediately.
663            let mut sources = SourceMap::new();
664            sources.insert(Box::new(source));
665            let pkg_set = PackageSet::new(&[summary.package_id()], sources, gctx)?;
666            Ok(pkg_set.get_one(summary.package_id())?.clone())
667        }
668        None => {
669            let is_yanked: bool = if dep.version_req().is_exact() {
670                let version: String = dep.version_req().to_string();
671                if let Ok(pkg_id) =
672                    PackageId::try_new(dep.package_name(), &version[1..], source.source_id())
673                {
674                    source.invalidate_cache();
675                    crate::util::block_on(source.is_yanked(pkg_id)).unwrap_or_default()
676                } else {
677                    false
678                }
679            } else {
680                false
681            };
682            if is_yanked {
683                bail!(
684                    "cannot install package `{}`, it has been yanked from {}",
685                    dep.package_name(),
686                    source.source_id()
687                )
688            } else {
689                bail!(
690                    "could not find `{}` in {} with version `{}`",
691                    dep.package_name(),
692                    source.source_id(),
693                    dep.version_req(),
694                )
695            }
696        }
697    }
698}
699
700pub fn select_pkg<T, F>(
701    source: &mut T,
702    dep: Option<Dependency>,
703    mut list_all: F,
704    gctx: &GlobalContext,
705    current_rust_version: Option<&PartialVersion>,
706) -> CargoResult<Package>
707where
708    T: Source,
709    F: FnMut(&mut T) -> CargoResult<Vec<Package>>,
710{
711    // This operation may involve updating some sources or making a few queries
712    // which may involve frobbing caches, as a result make sure we synchronize
713    // with other global Cargos
714    let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
715
716    source.invalidate_cache();
717
718    return if let Some(dep) = dep {
719        select_dep_pkg(source, dep, gctx, false, current_rust_version)
720    } else {
721        let candidates = list_all(source)?;
722        let binaries = candidates
723            .iter()
724            .filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
725        let examples = candidates
726            .iter()
727            .filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
728        let git_url = source.source_id().url().to_string();
729        let pkg = match one(binaries, |v| multi_err("binaries", &git_url, v))? {
730            Some(p) => p,
731            None => match one(examples, |v| multi_err("examples", &git_url, v))? {
732                Some(p) => p,
733                None => bail!(
734                    "no packages found with binaries or \
735                         examples"
736                ),
737            },
738        };
739        Ok(pkg.clone())
740    };
741
742    fn multi_err(kind: &str, git_url: &str, mut pkgs: Vec<&Package>) -> String {
743        pkgs.sort_unstable_by_key(|a| a.name());
744        let first_pkg = pkgs[0];
745        format!(
746            "multiple packages with {} found: {}. When installing a git repository, \
747            cargo will always search the entire repo for any Cargo.toml.\n\
748            Please specify a package, e.g. `cargo install --git {} {}`.",
749            kind,
750            pkgs.iter()
751                .map(|p| p.name().as_str())
752                .collect::<Vec<_>>()
753                .join(", "),
754            git_url,
755            first_pkg.name()
756        )
757    }
758}
759
760/// Get one element from the iterator.
761/// Returns None if none left.
762/// Returns error if there is more than one item in the iterator.
763fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
764where
765    I: Iterator,
766    F: FnOnce(Vec<I::Item>) -> String,
767{
768    match (i.next(), i.next()) {
769        (Some(i1), Some(i2)) => {
770            let mut v = vec![i1, i2];
771            v.extend(i);
772            Err(format_err!("{}", f(v)))
773        }
774        (Some(i), None) => Ok(Some(i)),
775        (None, _) => Ok(None),
776    }
777}
778
779/// Helper to convert features to a `BTreeSet`.
780fn feature_set(features: &Rc<BTreeSet<FeatureValue>>) -> BTreeSet<String> {
781    features.iter().map(|s| s.to_string()).collect()
782}
783
784/// Helper to get the executable names from a filter.
785pub fn exe_names(pkg: &Package, filter: &ops::CompileFilter) -> BTreeSet<String> {
786    let to_exe = |name| format!("{}{}", name, env::consts::EXE_SUFFIX);
787    match filter {
788        CompileFilter::Default { .. } => pkg
789            .targets()
790            .iter()
791            .filter(|t| t.is_bin())
792            .map(|t| to_exe(t.name()))
793            .collect(),
794        CompileFilter::Only {
795            all_targets: true, ..
796        } => pkg
797            .targets()
798            .iter()
799            .filter(|target| target.is_executable())
800            .map(|target| to_exe(target.name()))
801            .collect(),
802        CompileFilter::Only { bins, examples, .. } => {
803            let collect = |rule: &_, f: fn(&Target) -> _| match rule {
804                FilterRule::All => pkg
805                    .targets()
806                    .iter()
807                    .filter(|t| f(t))
808                    .map(|t| t.name().into())
809                    .collect(),
810                FilterRule::Just(targets) => targets.clone(),
811            };
812            let all_bins = collect(bins, Target::is_bin);
813            let all_examples = collect(examples, Target::is_exe_example);
814
815            all_bins
816                .iter()
817                .chain(all_examples.iter())
818                .map(|name| to_exe(name))
819                .collect()
820        }
821    }
822}