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