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}