#
cargo/ops/
cargo_uninstall.rs

    1use crate::core::PackageId;
    2use crate::core::{PackageIdSpec, PackageIdSpecQuery, SourceId};
    3use crate::ops::common_for_install_and_uninstall::*;
    4use crate::sources::PathSource;
    5use crate::util::Filesystem;
    6use crate::util::GlobalContext;
    7use crate::util::errors::CargoResult;
    8use anyhow::bail;
    9use std::collections::BTreeSet;
    10use std::env;
    11
    12pub fn uninstall(
    13    root: Option<&str>,
    14    specs: Vec<&str>,
    15    bins: &[String],
    16    gctx: &GlobalContext,
    17) -> CargoResult<()> {
    18    if specs.len() > 1 && !bins.is_empty() {
    19        bail!(
    20            "A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant."
    21        );
    22    }
    23
    24    let root = resolve_root(root, gctx)?;
    25    let scheduled_error = if specs.len() == 1 {
    26        uninstall_one(&root, specs[0], bins, gctx)?;
    27        false
    28    } else if specs.is_empty() {
    29        uninstall_cwd(&root, bins, gctx)?;
    30        false
    31    } else {
    32        let mut succeeded = vec![];
    33        let mut failed = vec![];
    34        for spec in specs {
    35            let root = root.clone();
    36            match uninstall_one(&root, spec, bins, gctx) {
    37                Ok(()) => succeeded.push(spec),
    38                Err(e) => {
    39                    crate::display_error(&e, &mut gctx.shell());
    40                    failed.push(spec)
    41                }
    42            }
    43        }
    44
    45        let mut summary = vec![];
    46        if !succeeded.is_empty() {
    47            summary.push(format!(
    48                "Successfully uninstalled {}!",
    49                succeeded.join(", ")
    50            ));
    51        }
    52        if !failed.is_empty() {
    53            summary.push(format!(
    54                "Failed to uninstall {} (see error(s) above).",
    55                failed.join(", ")
    56            ));
    57        }
    58
    59        if !succeeded.is_empty() || !failed.is_empty() {
    60            gctx.shell().status("Summary", summary.join(" "))?;
    61        }
    62
    63        !failed.is_empty()
    64    };
    65
    66    if scheduled_error {
    67        bail!("some packages failed to uninstall");
    68    }
    69
    70    Ok(())
    71}
    72
    73pub fn uninstall_one(
    74    root: &Filesystem,
    75    spec: &str,
    76    bins: &[String],
    77    gctx: &GlobalContext,
    78) -> CargoResult<()> {
    79    let tracker = InstallTracker::load(gctx, root)?;
    80    let all_pkgs = tracker.all_installed_bins().map(|(pkg_id, _set)| *pkg_id);
    81    let pkgid = PackageIdSpec::query_str(spec, all_pkgs)?;
    82    uninstall_pkgid(root, tracker, pkgid, bins, gctx)
    83}
    84
    85fn uninstall_cwd(root: &Filesystem, bins: &[String], gctx: &GlobalContext) -> CargoResult<()> {
    86    let tracker = InstallTracker::load(gctx, root)?;
    87    let source_id = SourceId::for_path(gctx.cwd())?;
    88    let mut src = path_source(source_id, gctx)?;
    89    let pkg = select_pkg(
    90        &mut src,
    91        None,
    92        |path: &mut PathSource<'_>| path.root_package().map(|p| vec![p]),
    93        gctx,
    94        None,
    95    )?;
    96    let pkgid = pkg.package_id();
    97    uninstall_pkgid(root, tracker, pkgid, bins, gctx)
    98}
    99
    100fn uninstall_pkgid(
    101    root: &Filesystem,
    102    mut tracker: InstallTracker,
    103    pkgid: PackageId,
    104    bins: &[String],
    105    gctx: &GlobalContext,
    106) -> CargoResult<()> {
    107    let installed = match tracker.installed_bins(pkgid) {
    108        Some(bins) => bins.clone(),
    109        None => bail!("package `{}` is not installed", pkgid),
    110    };
    111
    112    let dst = root.join("bin").into_path_unlocked();
    113    for bin in &installed {
    114        let bin = dst.join(bin);
    115        if !bin.exists() {
    116            bail!(
    117                "corrupt metadata, `{}` does not exist when it should",
    118                bin.display()
    119            )
    120        }
    121    }
    122
    123    let bins = bins
    124        .iter()
    125        .map(|s| {
    126            if s.ends_with(env::consts::EXE_SUFFIX) {
    127                s.to_string()
    128            } else {
    129                format!("{}{}", s, env::consts::EXE_SUFFIX)
    130            }
    131        })
    132        .collect::<BTreeSet<_>>();
    133
    134    for bin in bins.iter() {
    135        if !installed.contains(bin) {
    136            bail!("binary `{}` not installed as part of `{}`", bin, pkgid)
    137        }
    138    }
    139
    140    let to_remove = { if bins.is_empty() { installed } else { bins } };
    141
    142    for bin in to_remove {
    143        let bin_path = dst.join(&bin);
    144        gctx.shell().status("Removing", bin_path.display())?;
    145        tracker.remove_bin_then_save(pkgid, &bin, &bin_path)?;
    146    }
    147
    148    Ok(())
    149}