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}