cargo/ops/
cargo_uninstall.rs1use 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}