#
cargo/ops/
cargo_install.rs

    1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
    2use std::path::{Path, PathBuf};
    3use std::sync::Arc;
    4use std::{env, fs};
    5
    6use crate::core::compiler::{CompileKind, DefaultExecutor, Executor, UnitOutput};
    7use crate::core::{Dependency, Edition, Package, PackageId, SourceId, Target, Workspace};
    8use crate::ops::{common_for_install_and_uninstall::*, FilterRule};
    9use crate::ops::{CompileFilter, Packages};
    10use crate::sources::source::Source;
    11use crate::sources::{GitSource, PathSource, SourceConfigMap};
    12use crate::util::context::FeatureUnification;
    13use crate::util::errors::CargoResult;
    14use crate::util::{Filesystem, GlobalContext, Rustc};
    15use crate::{drop_println, ops};
    16
    17use anyhow::{bail, Context as _};
    18use cargo_util::paths;
    19use cargo_util_schemas::core::PartialVersion;
    20use itertools::Itertools;
    21use semver::VersionReq;
    22use tempfile::Builder as TempFileBuilder;
    23
    24struct Transaction {
    25    bins: Vec<PathBuf>,
    26}
    27
    28impl Transaction {
    29    fn success(mut self) {
    30        self.bins.clear();
    31    }
    32}
    33
    34impl Drop for Transaction {
    35    fn drop(&mut self) {
    36        for bin in self.bins.iter() {
    37            let _ = paths::remove_file(bin);
    38        }
    39    }
    40}
    41
    42struct InstallablePackage<'gctx> {
    43    gctx: &'gctx GlobalContext,
    44    opts: ops::CompileOptions,
    45    root: Filesystem,
    46    source_id: SourceId,
    47    vers: Option<VersionReq>,
    48    force: bool,
    49    no_track: bool,
    50    pkg: Package,
    51    ws: Workspace<'gctx>,
    52    rustc: Rustc,
    53    target: String,
    54}
    55
    56impl<'gctx> InstallablePackage<'gctx> {
    57    // Returns pkg to install. None if pkg is already installed
    58    pub fn new(
    59        gctx: &'gctx GlobalContext,
    60        root: Filesystem,
    61        map: SourceConfigMap<'_>,
    62        krate: Option<&str>,
    63        source_id: SourceId,
    64        from_cwd: bool,
    65        vers: Option<&VersionReq>,
    66        original_opts: &ops::CompileOptions,
    67        force: bool,
    68        no_track: bool,
    69        needs_update_if_source_is_index: bool,
    70        current_rust_version: Option<&PartialVersion>,
    71        lockfile_path: Option<&Path>,
    72    ) -> CargoResult<Option<Self>> {
    73        if let Some(name) = krate {
    74            if name == "." {
    75                bail!(
    76                    "To install the binaries for the package in current working \
    77                     directory use `cargo install --path .`. \n\
    78                     Use `cargo build` if you want to simply build the package."
    79                )
    80            }
    81        }
    82
    83        let dst = root.join("bin").into_path_unlocked();
    84        let pkg = {
    85            let dep = {
    86                if let Some(krate) = krate {
    87                    let vers = if let Some(vers) = vers {
    88                        Some(vers.to_string())
    89                    } else if source_id.is_registry() {
    90                        // Avoid pre-release versions from crate.io
    91                        // unless explicitly asked for
    92                        Some(String::from("*"))
    93                    } else {
    94                        None
    95                    };
    96                    Some(Dependency::parse(krate, vers.as_deref(), source_id)?)
    97                } else {
    98                    None
    99                }
    100            };
    101
    102            if source_id.is_git() {
    103                let mut source = GitSource::new(source_id, gctx)?;
    104                select_pkg(
    105                    &mut source,
    106                    dep,
    107                    |git: &mut GitSource<'_>| git.read_packages(),
    108                    gctx,
    109                    current_rust_version,
    110                )?
    111            } else if source_id.is_path() {
    112                let mut src = path_source(source_id, gctx)?;
    113                if !src.path().is_dir() {
    114                    bail!(
    115                        "`{}` is not a directory. \
    116                     --path must point to a directory containing a Cargo.toml file.",
    117                        src.path().display()
    118                    )
    119                }
    120                if !src.path().join("Cargo.toml").exists() {
    121                    if from_cwd {
    122                        bail!(
    123                            "`{}` is not a crate root; specify a crate to \
    124                         install from crates.io, or use --path or --git to \
    125                         specify an alternate source",
    126                            src.path().display()
    127                        );
    128                    } else if src.path().join("cargo.toml").exists() {
    129                        bail!(
    130                            "`{}` does not contain a Cargo.toml file, but found cargo.toml please try to rename it to Cargo.toml. \
    131                     --path must point to a directory containing a Cargo.toml file.",
    132                            src.path().display()
    133                        )
    134                    } else {
    135                        bail!(
    136                            "`{}` does not contain a Cargo.toml file. \
    137                     --path must point to a directory containing a Cargo.toml file.",
    138                            src.path().display()
    139                        )
    140                    }
    141                }
    142                select_pkg(
    143                    &mut src,
    144                    dep,
    145                    |path: &mut PathSource<'_>| path.root_package().map(|p| vec![p]),
    146                    gctx,
    147                    current_rust_version,
    148                )?
    149            } else if let Some(dep) = dep {
    150                let mut source = map.load(source_id, &HashSet::new())?;
    151                if let Ok(Some(pkg)) = installed_exact_package(
    152                    dep.clone(),
    153                    &mut source,
    154                    gctx,
    155                    original_opts,
    156                    &root,
    157                    &dst,
    158                    force,
    159                    lockfile_path,
    160                ) {
    161                    let msg = format!(
    162                        "package `{}` is already installed, use --force to override",
    163                        pkg
    164                    );
    165                    gctx.shell().status("Ignored", &msg)?;
    166                    return Ok(None);
    167                }
    168                select_dep_pkg(
    169                    &mut source,
    170                    dep,
    171                    gctx,
    172                    needs_update_if_source_is_index,
    173                    current_rust_version,
    174                )?
    175            } else {
    176                bail!(
    177                    "must specify a crate to install from \
    178                         crates.io, or use --path or --git to \
    179                         specify alternate source"
    180                )
    181            }
    182        };
    183
    184        let (ws, rustc, target) = make_ws_rustc_target(
    185            gctx,
    186            &original_opts,
    187            &source_id,
    188            pkg.clone(),
    189            lockfile_path.clone(),
    190        )?;
    191
    192        if !gctx.lock_update_allowed() {
    193            // When --lockfile-path is set, check that passed lock file exists
    194            // (unlike the usual flag behavior, lockfile won't be created as we imply --locked)
    195            if let Some(requested_lockfile_path) = ws.requested_lockfile_path() {
    196                if !requested_lockfile_path.is_file() {
    197                    bail!(
    198                        "no Cargo.lock file found in the requested path {}",
    199                        requested_lockfile_path.display()
    200                    );
    201                }
    202            // If we're installing in --locked mode and there's no `Cargo.lock` published
    203            // ie. the bin was published before https://github.com/rust-lang/cargo/pull/7026
    204            } else if !ws.root().join("Cargo.lock").exists() {
    205                gctx.shell().warn(format!(
    206                    "no Cargo.lock file published in {}",
    207                    pkg.to_string()
    208                ))?;
    209            }
    210        }
    211        let pkg = if source_id.is_git() {
    212            // Don't use ws.current() in order to keep the package source as a git source so that
    213            // install tracking uses the correct source.
    214            pkg
    215        } else {
    216            ws.current()?.clone()
    217        };
    218
    219        // When we build this package, we want to build the *specified* package only,
    220        // and avoid building e.g. workspace default-members instead. Do so by constructing
    221        // specialized compile options specific to the identified package.
    222        // See test `path_install_workspace_root_despite_default_members`.
    223        let mut opts = original_opts.clone();
    224        // For cargo install tracking, we retain the source git url in `pkg`, but for the build spec
    225        // we need to unconditionally use `ws.current()` to correctly address the path where we
    226        // locally cloned that repo.
    227        let pkgidspec = ws.current()?.package_id().to_spec();
    228        opts.spec = Packages::Packages(vec![pkgidspec.to_string()]);
    229
    230        if from_cwd {
    231            if pkg.manifest().edition() == Edition::Edition2015 {
    232                gctx.shell().warn(
    233                    "Using `cargo install` to install the binaries from the \
    234                     package in current working directory is deprecated, \
    235                     use `cargo install --path .` instead. \
    236                     Use `cargo build` if you want to simply build the package.",
    237                )?
    238            } else {
    239                bail!(
    240                    "Using `cargo install` to install the binaries from the \
    241                     package in current working directory is no longer supported, \
    242                     use `cargo install --path .` instead. \
    243                     Use `cargo build` if you want to simply build the package."
    244                )
    245            }
    246        };
    247
    248        // For bare `cargo install` (no `--bin` or `--example`), check if there is
    249        // *something* to install. Explicit `--bin` or `--example` flags will be
    250        // checked at the start of `compile_ws`.
    251        if !opts.filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) {
    252            bail!(
    253                "there is nothing to install in `{}`, because it has no binaries\n\
    254                 `cargo install` is only for installing programs, and can't be used with libraries.\n\
    255                 To use a library crate, add it as a dependency to a Cargo project with `cargo add`.",
    256                pkg,
    257            );
    258        }
    259
    260        let ip = InstallablePackage {
    261            gctx,
    262            opts,
    263            root,
    264            source_id,
    265            vers: vers.cloned(),
    266            force,
    267            no_track,
    268            pkg,
    269            ws,
    270            rustc,
    271            target,
    272        };
    273
    274        // WARNING: no_track does not perform locking, so there is no protection
    275        // of concurrent installs.
    276        if no_track {
    277            // Check for conflicts.
    278            ip.no_track_duplicates(&dst)?;
    279        } else if is_installed(
    280            &ip.pkg, gctx, &ip.opts, &ip.rustc, &ip.target, &ip.root, &dst, force,
    281        )? {
    282            let msg = format!(
    283                "package `{}` is already installed, use --force to override",
    284                ip.pkg
    285            );
    286            gctx.shell().status("Ignored", &msg)?;
    287            return Ok(None);
    288        }
    289
    290        Ok(Some(ip))
    291    }
    292
    293    fn no_track_duplicates(&self, dst: &Path) -> CargoResult<BTreeMap<String, Option<PackageId>>> {
    294        // Helper for --no-track flag to make sure it doesn't overwrite anything.
    295        let duplicates: BTreeMap<String, Option<PackageId>> =
    296            exe_names(&self.pkg, &self.opts.filter)
    297                .into_iter()
    298                .filter(|name| dst.join(name).exists())
    299                .map(|name| (name, None))
    300                .collect();
    301        if !self.force && !duplicates.is_empty() {
    302            let mut msg: Vec<String> = duplicates
    303                .iter()
    304                .map(|(name, _)| {
    305                    format!(
    306                        "binary `{}` already exists in destination `{}`",
    307                        name,
    308                        dst.join(name).to_string_lossy()
    309                    )
    310                })
    311                .collect();
    312            msg.push("Add --force to overwrite".to_string());
    313            bail!("{}", msg.join("\n"));
    314        }
    315        Ok(duplicates)
    316    }
    317
    318    fn install_one(mut self, dry_run: bool) -> CargoResult<bool> {
    319        self.gctx.shell().status("Installing", &self.pkg)?;
    320
    321        let dst = self.root.join("bin").into_path_unlocked();
    322
    323        let mut td_opt = None;
    324        let mut needs_cleanup = false;
    325        if !self.source_id.is_path() {
    326            let target_dir = if let Some(dir) = self.gctx.target_dir()? {
    327                dir
    328            } else if let Ok(td) = TempFileBuilder::new().prefix("cargo-install").tempdir() {
    329                let p = td.path().to_owned();
    330                td_opt = Some(td);
    331                Filesystem::new(p)
    332            } else {
    333                needs_cleanup = true;
    334                Filesystem::new(self.gctx.cwd().join("target-install"))
    335            };
    336            self.ws.set_target_dir(target_dir);
    337        }
    338
    339        self.check_yanked_install()?;
    340
    341        let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
    342        self.opts.build_config.dry_run = dry_run;
    343        let compile = ops::compile_ws(&self.ws, &self.opts, &exec).with_context(|| {
    344            if let Some(td) = td_opt.take() {
    345                // preserve the temporary directory, so the user can inspect it
    346                drop(td.into_path());
    347            }
    348
    349            format!(
    350                "failed to compile `{}`, intermediate artifacts can be \
    351                 found at `{}`.\nTo reuse those artifacts with a future \
    352                 compilation, set the environment variable \
    353                 `CARGO_TARGET_DIR` to that path.",
    354                self.pkg,
    355                self.ws.target_dir().display()
    356            )
    357        })?;
    358        let mut binaries: Vec<(&str, &Path)> = compile
    359            .binaries
    360            .iter()
    361            .map(|UnitOutput { path, .. }| {
    362                let name = path.file_name().unwrap();
    363                if let Some(s) = name.to_str() {
    364                    Ok((s, path.as_ref()))
    365                } else {
    366                    bail!("Binary `{:?}` name can't be serialized into string", name)
    367                }
    368            })
    369            .collect::<CargoResult<_>>()?;
    370        if binaries.is_empty() {
    371            // Cargo already warns the user if they use a target specifier that matches nothing,
    372            // but we want to error if the user asked for a _particular_ binary to be installed,
    373            // and we didn't end up installing it.
    374            //
    375            // NOTE: This _should_ be impossible to hit since --bin=does_not_exist will fail on
    376            // target selection, and --bin=requires_a without --features=a will fail with "target
    377            // .. requires the features ..". But rather than assume that's the case, we define the
    378            // behavior for this fallback case as well.
    379            if let CompileFilter::Only { bins, examples, .. } = &self.opts.filter {
    380                let mut any_specific = false;
    381                if let FilterRule::Just(ref v) = bins {
    382                    if !v.is_empty() {
    383                        any_specific = true;
    384                    }
    385                }
    386                if let FilterRule::Just(ref v) = examples {
    387                    if !v.is_empty() {
    388                        any_specific = true;
    389                    }
    390                }
    391                if any_specific {
    392                    bail!("no binaries are available for install using the selected features");
    393                }
    394            }
    395
    396            // If there _are_ binaries available, but none were selected given the current set of
    397            // features, let the user know.
    398            //
    399            // Note that we know at this point that _if_ bins or examples is set to `::Just`,
    400            // they're `::Just([])`, which is `FilterRule::none()`.
    401            let binaries: Vec<_> = self
    402                .pkg
    403                .targets()
    404                .iter()
    405                .filter(|t| t.is_executable())
    406                .collect();
    407            if !binaries.is_empty() {
    408                self.gctx
    409                    .shell()
    410                    .warn(make_warning_about_missing_features(&binaries))?;
    411            }
    412
    413            return Ok(false);
    414        }
    415        // This is primarily to make testing easier.
    416        binaries.sort_unstable();
    417
    418        let (tracker, duplicates) = if self.no_track {
    419            (None, self.no_track_duplicates(&dst)?)
    420        } else {
    421            let tracker = InstallTracker::load(self.gctx, &self.root)?;
    422            let (_freshness, duplicates) = tracker.check_upgrade(
    423                &dst,
    424                &self.pkg,
    425                self.force,
    426                &self.opts,
    427                &self.target,
    428                &self.rustc.verbose_version,
    429            )?;
    430            (Some(tracker), duplicates)
    431        };
    432
    433        paths::create_dir_all(&dst)?;
    434
    435        // Copy all binaries to a temporary directory under `dst` first, catching
    436        // some failure modes (e.g., out of space) before touching the existing
    437        // binaries. This directory will get cleaned up via RAII.
    438        let staging_dir = TempFileBuilder::new()
    439            .prefix("cargo-install")
    440            .tempdir_in(&dst)?;
    441        if !dry_run {
    442            for &(bin, src) in binaries.iter() {
    443                let dst = staging_dir.path().join(bin);
    444                // Try to move if `target_dir` is transient.
    445                if !self.source_id.is_path() && fs::rename(src, &dst).is_ok() {
    446                    continue;
    447                }
    448                paths::copy(src, &dst)?;
    449            }
    450        }
    451
    452        let (to_replace, to_install): (Vec<&str>, Vec<&str>) = binaries
    453            .iter()
    454            .map(|&(bin, _)| bin)
    455            .partition(|&bin| duplicates.contains_key(bin));
    456
    457        let mut installed = Transaction { bins: Vec::new() };
    458        let mut successful_bins = BTreeSet::new();
    459
    460        // Move the temporary copies into `dst` starting with new binaries.
    461        for bin in to_install.iter() {
    462            let src = staging_dir.path().join(bin);
    463            let dst = dst.join(bin);
    464            self.gctx.shell().status("Installing", dst.display())?;
    465            if !dry_run {
    466                fs::rename(&src, &dst).with_context(|| {
    467                    format!("failed to move `{}` to `{}`", src.display(), dst.display())
    468                })?;
    469                installed.bins.push(dst);
    470                successful_bins.insert(bin.to_string());
    471            }
    472        }
    473
    474        // Repeat for binaries which replace existing ones but don't pop the error
    475        // up until after updating metadata.
    476        let replace_result = {
    477            let mut try_install = || -> CargoResult<()> {
    478                for &bin in to_replace.iter() {
    479                    let src = staging_dir.path().join(bin);
    480                    let dst = dst.join(bin);
    481                    self.gctx.shell().status("Replacing", dst.display())?;
    482                    if !dry_run {
    483                        fs::rename(&src, &dst).with_context(|| {
    484                            format!("failed to move `{}` to `{}`", src.display(), dst.display())
    485                        })?;
    486                        successful_bins.insert(bin.to_string());
    487                    }
    488                }
    489                Ok(())
    490            };
    491            try_install()
    492        };
    493
    494        if let Some(mut tracker) = tracker {
    495            tracker.mark_installed(
    496                &self.pkg,
    497                &successful_bins,
    498                self.vers.map(|s| s.to_string()),
    499                &self.opts,
    500                &self.target,
    501                &self.rustc.verbose_version,
    502            );
    503
    504            if let Err(e) = remove_orphaned_bins(
    505                &self.ws,
    506                &mut tracker,
    507                &duplicates,
    508                &self.pkg,
    509                &dst,
    510                dry_run,
    511            ) {
    512                // Don't hard error on remove.
    513                self.gctx
    514                    .shell()
    515                    .warn(format!("failed to remove orphan: {:?}", e))?;
    516            }
    517
    518            match tracker.save() {
    519                Err(err) => replace_result.with_context(|| err)?,
    520                Ok(_) => replace_result?,
    521            }
    522        }
    523
    524        // Reaching here means all actions have succeeded. Clean up.
    525        installed.success();
    526        if needs_cleanup {
    527            // Don't bother grabbing a lock as we're going to blow it all away
    528            // anyway.
    529            let target_dir = self.ws.target_dir().into_path_unlocked();
    530            paths::remove_dir_all(&target_dir)?;
    531        }
    532
    533        // Helper for creating status messages.
    534        fn executables<T: AsRef<str>>(mut names: impl Iterator<Item = T> + Clone) -> String {
    535            if names.clone().count() == 1 {
    536                format!("(executable `{}`)", names.next().unwrap().as_ref())
    537            } else {
    538                format!(
    539                    "(executables {})",
    540                    names
    541                        .map(|b| format!("`{}`", b.as_ref()))
    542                        .collect::<Vec<_>>()
    543                        .join(", ")
    544                )
    545            }
    546        }
    547
    548        if dry_run {
    549            self.gctx.shell().warn("aborting install due to dry run")?;
    550            Ok(true)
    551        } else if duplicates.is_empty() {
    552            self.gctx.shell().status(
    553                "Installed",
    554                format!(
    555                    "package `{}` {}",
    556                    self.pkg,
    557                    executables(successful_bins.iter())
    558                ),
    559            )?;
    560            Ok(true)
    561        } else {
    562            if !to_install.is_empty() {
    563                self.gctx.shell().status(
    564                    "Installed",
    565                    format!("package `{}` {}", self.pkg, executables(to_install.iter())),
    566                )?;
    567            }
    568            // Invert the duplicate map.
    569            let mut pkg_map = BTreeMap::new();
    570            for (bin_name, opt_pkg_id) in &duplicates {
    571                let key =
    572                    opt_pkg_id.map_or_else(|| "unknown".to_string(), |pkg_id| pkg_id.to_string());
    573                pkg_map.entry(key).or_insert_with(Vec::new).push(bin_name);
    574            }
    575            for (pkg_descr, bin_names) in &pkg_map {
    576                self.gctx.shell().status(
    577                    "Replaced",
    578                    format!(
    579                        "package `{}` with `{}` {}",
    580                        pkg_descr,
    581                        self.pkg,
    582                        executables(bin_names.iter())
    583                    ),
    584                )?;
    585            }
    586            Ok(true)
    587        }
    588    }
    589
    590    fn check_yanked_install(&self) -> CargoResult<()> {
    591        if self.ws.ignore_lock() || !self.ws.root().join("Cargo.lock").exists() {
    592            return Ok(());
    593        }
    594        // It would be best if `source` could be passed in here to avoid a
    595        // duplicate "Updating", but since `source` is taken by value, then it
    596        // wouldn't be available for `compile_ws`.
    597        let dry_run = false;
    598        let (pkg_set, resolve) = ops::resolve_ws(&self.ws, dry_run)?;
    599        ops::check_yanked(
    600            self.ws.gctx(),
    601            &pkg_set,
    602            &resolve,
    603            "consider running without --locked",
    604        )
    605    }
    606}
    607
    608fn make_warning_about_missing_features(binaries: &[&Target]) -> String {
    609    let max_targets_listed = 7;
    610    let target_features_message = binaries
    611        .iter()
    612        .take(max_targets_listed)
    613        .map(|b| {
    614            let name = b.description_named();
    615            let features = b
    616                .required_features()
    617                .unwrap_or(&Vec::new())
    618                .iter()
    619                .map(|f| format!("`{f}`"))
    620                .join(", ");
    621            format!("  {name} requires the features: {features}")
    622        })
    623        .join("\n");
    624
    625    let additional_bins_message = if binaries.len() > max_targets_listed {
    626        format!(
    627            "\n{} more targets also requires features not enabled. See them in the Cargo.toml file.",
    628            binaries.len() - max_targets_listed
    629        )
    630    } else {
    631        "".into()
    632    };
    633
    634    let example_features = binaries[0]
    635        .required_features()
    636        .map(|f| f.join(" "))
    637        .unwrap_or_default();
    638
    639    format!(
    640        "\
    641none of the package's binaries are available for install using the selected features
    642{target_features_message}{additional_bins_message}
    643Consider enabling some of the needed features by passing, e.g., `--features=\"{example_features}\"`"
    644    )
    645}
    646
    647pub fn install(
    648    gctx: &GlobalContext,
    649    root: Option<&str>,
    650    krates: Vec<(String, Option<VersionReq>)>,
    651    source_id: SourceId,
    652    from_cwd: bool,
    653    opts: &ops::CompileOptions,
    654    force: bool,
    655    no_track: bool,
    656    dry_run: bool,
    657    lockfile_path: Option<&Path>,
    658) -> CargoResult<()> {
    659    let root = resolve_root(root, gctx)?;
    660    let dst = root.join("bin").into_path_unlocked();
    661    let map = SourceConfigMap::new(gctx)?;
    662
    663    let current_rust_version = if opts.honor_rust_version.unwrap_or(true) {
    664        let rustc = gctx.load_global_rustc(None)?;
    665        Some(rustc.version.clone().into())
    666    } else {
    667        None
    668    };
    669
    670    let (installed_anything, scheduled_error) = if krates.len() <= 1 {
    671        let (krate, vers) = krates
    672            .iter()
    673            .next()
    674            .map(|(k, v)| (Some(k.as_str()), v.as_ref()))
    675            .unwrap_or((None, None));
    676        let installable_pkg = InstallablePackage::new(
    677            gctx,
    678            root,
    679            map,
    680            krate,
    681            source_id,
    682            from_cwd,
    683            vers,
    684            opts,
    685            force,
    686            no_track,
    687            true,
    688            current_rust_version.as_ref(),
    689            lockfile_path,
    690        )?;
    691        let mut installed_anything = true;
    692        if let Some(installable_pkg) = installable_pkg {
    693            installed_anything = installable_pkg.install_one(dry_run)?;
    694        }
    695        (installed_anything, false)
    696    } else {
    697        let mut succeeded = vec![];
    698        let mut failed = vec![];
    699        // "Tracks whether or not the source (such as a registry or git repo) has been updated.
    700        // This is used to avoid updating it multiple times when installing multiple crates.
    701        let mut did_update = false;
    702
    703        let pkgs_to_install: Vec<_> = krates
    704            .iter()
    705            .filter_map(|(krate, vers)| {
    706                let root = root.clone();
    707                let map = map.clone();
    708                match InstallablePackage::new(
    709                    gctx,
    710                    root,
    711                    map,
    712                    Some(krate.as_str()),
    713                    source_id,
    714                    from_cwd,
    715                    vers.as_ref(),
    716                    opts,
    717                    force,
    718                    no_track,
    719                    !did_update,
    720                    current_rust_version.as_ref(),
    721                    lockfile_path,
    722                ) {
    723                    Ok(Some(installable_pkg)) => {
    724                        did_update = true;
    725                        Some((krate, installable_pkg))
    726                    }
    727                    Ok(None) => {
    728                        // Already installed
    729                        succeeded.push(krate.as_str());
    730                        None
    731                    }
    732                    Err(e) => {
    733                        crate::display_error(&e, &mut gctx.shell());
    734                        failed.push(krate.as_str());
    735                        // We assume an update was performed if we got an error.
    736                        did_update = true;
    737                        None
    738                    }
    739                }
    740            })
    741            .collect();
    742
    743        let install_results: Vec<_> = pkgs_to_install
    744            .into_iter()
    745            .map(|(krate, installable_pkg)| (krate, installable_pkg.install_one(dry_run)))
    746            .collect();
    747
    748        for (krate, result) in install_results {
    749            match result {
    750                Ok(installed) => {
    751                    if installed {
    752                        succeeded.push(krate);
    753                    }
    754                }
    755                Err(e) => {
    756                    crate::display_error(&e, &mut gctx.shell());
    757                    failed.push(krate);
    758                }
    759            }
    760        }
    761
    762        let mut summary = vec![];
    763        if !succeeded.is_empty() {
    764            summary.push(format!("Successfully installed {}!", succeeded.join(", ")));
    765        }
    766        if !failed.is_empty() {
    767            summary.push(format!(
    768                "Failed to install {} (see error(s) above).",
    769                failed.join(", ")
    770            ));
    771        }
    772        if !succeeded.is_empty() || !failed.is_empty() {
    773            gctx.shell().status("Summary", summary.join(" "))?;
    774        }
    775
    776        (!succeeded.is_empty(), !failed.is_empty())
    777    };
    778
    779    if installed_anything {
    780        // Print a warning that if this directory isn't in PATH that they won't be
    781        // able to run these commands.
    782        let path = gctx.get_env_os("PATH").unwrap_or_default();
    783        let dst_in_path = env::split_paths(&path).any(|path| path == dst);
    784
    785        if !dst_in_path {
    786            gctx.shell().warn(&format!(
    787                "be sure to add `{}` to your PATH to be \
    788             able to run the installed binaries",
    789                dst.display()
    790            ))?;
    791        }
    792    }
    793
    794    if scheduled_error {
    795        bail!("some crates failed to install");
    796    }
    797
    798    Ok(())
    799}
    800
    801fn is_installed(
    802    pkg: &Package,
    803    gctx: &GlobalContext,
    804    opts: &ops::CompileOptions,
    805    rustc: &Rustc,
    806    target: &str,
    807    root: &Filesystem,
    808    dst: &Path,
    809    force: bool,
    810) -> CargoResult<bool> {
    811    let tracker = InstallTracker::load(gctx, root)?;
    812    let (freshness, _duplicates) =
    813        tracker.check_upgrade(dst, pkg, force, opts, target, &rustc.verbose_version)?;
    814    Ok(freshness.is_fresh())
    815}
    816
    817/// Checks if vers can only be satisfied by exactly one version of a package in a registry, and it's
    818/// already installed. If this is the case, we can skip interacting with a registry to check if
    819/// newer versions may be installable, as no newer version can exist.
    820fn installed_exact_package<T>(
    821    dep: Dependency,
    822    source: &mut T,
    823    gctx: &GlobalContext,
    824    opts: &ops::CompileOptions,
    825    root: &Filesystem,
    826    dst: &Path,
    827    force: bool,
    828    lockfile_path: Option<&Path>,
    829) -> CargoResult<Option<Package>>
    830where
    831    T: Source,
    832{
    833    if !dep.version_req().is_exact() {
    834        // If the version isn't exact, we may need to update the registry and look for a newer
    835        // version - we can't know if the package is installed without doing so.
    836        return Ok(None);
    837    }
    838    // Try getting the package from the registry  without updating it, to avoid a potentially
    839    // expensive network call in the case that the package is already installed.
    840    // If this fails, the caller will possibly do an index update and try again, this is just a
    841    // best-effort check to see if we can avoid hitting the network.
    842    if let Ok(pkg) = select_dep_pkg(source, dep, gctx, false, None) {
    843        let (_ws, rustc, target) =
    844            make_ws_rustc_target(gctx, opts, &source.source_id(), pkg.clone(), lockfile_path)?;
    845        if let Ok(true) = is_installed(&pkg, gctx, opts, &rustc, &target, root, dst, force) {
    846            return Ok(Some(pkg));
    847        }
    848    }
    849    Ok(None)
    850}
    851
    852fn make_ws_rustc_target<'gctx>(
    853    gctx: &'gctx GlobalContext,
    854    opts: &ops::CompileOptions,
    855    source_id: &SourceId,
    856    pkg: Package,
    857    lockfile_path: Option<&Path>,
    858) -> CargoResult<(Workspace<'gctx>, Rustc, String)> {
    859    let mut ws = if source_id.is_git() || source_id.is_path() {
    860        Workspace::new(pkg.manifest_path(), gctx)?
    861    } else {
    862        let mut ws = Workspace::ephemeral(pkg, gctx, None, false)?;
    863        ws.set_resolve_honors_rust_version(Some(false));
    864        ws
    865    };
    866    ws.set_resolve_feature_unification(FeatureUnification::Selected);
    867    ws.set_ignore_lock(gctx.lock_update_allowed());
    868    ws.set_requested_lockfile_path(lockfile_path.map(|p| p.to_path_buf()));
    869    // if --lockfile-path is set, imply --locked
    870    if ws.requested_lockfile_path().is_some() {
    871        ws.set_ignore_lock(false);
    872    }
    873    ws.set_require_optional_deps(false);
    874
    875    let rustc = gctx.load_global_rustc(Some(&ws))?;
    876    let target = match &opts.build_config.single_requested_kind()? {
    877        CompileKind::Host => rustc.host.as_str().to_owned(),
    878        CompileKind::Target(target) => target.short_name().to_owned(),
    879    };
    880
    881    Ok((ws, rustc, target))
    882}
    883
    884/// Display a list of installed binaries.
    885pub fn install_list(dst: Option<&str>, gctx: &GlobalContext) -> CargoResult<()> {
    886    let root = resolve_root(dst, gctx)?;
    887    let tracker = InstallTracker::load(gctx, &root)?;
    888    for (k, v) in tracker.all_installed_bins() {
    889        drop_println!(gctx, "{}:", k);
    890        for bin in v {
    891            drop_println!(gctx, "    {}", bin);
    892        }
    893    }
    894    Ok(())
    895}
    896
    897/// Removes executables that are no longer part of a package that was
    898/// previously installed.
    899fn remove_orphaned_bins(
    900    ws: &Workspace<'_>,
    901    tracker: &mut InstallTracker,
    902    duplicates: &BTreeMap<String, Option<PackageId>>,
    903    pkg: &Package,
    904    dst: &Path,
    905    dry_run: bool,
    906) -> CargoResult<()> {
    907    let filter = ops::CompileFilter::new_all_targets();
    908    let all_self_names = exe_names(pkg, &filter);
    909    let mut to_remove: HashMap<PackageId, BTreeSet<String>> = HashMap::new();
    910    // For each package that we stomped on.
    911    for other_pkg in duplicates.values().flatten() {
    912        // Only for packages with the same name.
    913        if other_pkg.name() == pkg.name() {
    914            // Check what the old package had installed.
    915            if let Some(installed) = tracker.installed_bins(*other_pkg) {
    916                // If the old install has any names that no longer exist,
    917                // add them to the list to remove.
    918                for installed_name in installed {
    919                    if !all_self_names.contains(installed_name.as_str()) {
    920                        to_remove
    921                            .entry(*other_pkg)
    922                            .or_default()
    923                            .insert(installed_name.clone());
    924                    }
    925                }
    926            }
    927        }
    928    }
    929
    930    for (old_pkg, bins) in to_remove {
    931        tracker.remove(old_pkg, &bins);
    932        for bin in bins {
    933            let full_path = dst.join(bin);
    934            if full_path.exists() {
    935                ws.gctx().shell().status(
    936                    "Removing",
    937                    format!(
    938                        "executable `{}` from previous version {}",
    939                        full_path.display(),
    940                        old_pkg
    941                    ),
    942                )?;
    943                if !dry_run {
    944                    paths::remove_file(&full_path)
    945                        .with_context(|| format!("failed to remove {:?}", full_path))?;
    946                }
    947            }
    948        }
    949    }
    950    Ok(())
    951}