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