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}