summaryrefslogtreecommitdiffstats
path: root/src/cargo/ops/cargo_uninstall.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/cargo/ops/cargo_uninstall.rs')
-rw-r--r--src/cargo/ops/cargo_uninstall.rs155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/cargo/ops/cargo_uninstall.rs b/src/cargo/ops/cargo_uninstall.rs
new file mode 100644
index 0000000..3551544
--- /dev/null
+++ b/src/cargo/ops/cargo_uninstall.rs
@@ -0,0 +1,155 @@
+use crate::core::PackageId;
+use crate::core::{PackageIdSpec, SourceId};
+use crate::ops::common_for_install_and_uninstall::*;
+use crate::sources::PathSource;
+use crate::util::errors::CargoResult;
+use crate::util::Config;
+use crate::util::Filesystem;
+use anyhow::bail;
+use cargo_util::paths;
+use std::collections::BTreeSet;
+use std::env;
+
+pub fn uninstall(
+ root: Option<&str>,
+ specs: Vec<&str>,
+ bins: &[String],
+ config: &Config,
+) -> CargoResult<()> {
+ if specs.len() > 1 && !bins.is_empty() {
+ bail!("A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant.");
+ }
+
+ let root = resolve_root(root, config)?;
+ let scheduled_error = if specs.len() == 1 {
+ uninstall_one(&root, specs[0], bins, config)?;
+ false
+ } else if specs.is_empty() {
+ uninstall_cwd(&root, bins, config)?;
+ false
+ } else {
+ let mut succeeded = vec![];
+ let mut failed = vec![];
+ for spec in specs {
+ let root = root.clone();
+ match uninstall_one(&root, spec, bins, config) {
+ Ok(()) => succeeded.push(spec),
+ Err(e) => {
+ crate::display_error(&e, &mut config.shell());
+ failed.push(spec)
+ }
+ }
+ }
+
+ let mut summary = vec![];
+ if !succeeded.is_empty() {
+ summary.push(format!(
+ "Successfully uninstalled {}!",
+ succeeded.join(", ")
+ ));
+ }
+ if !failed.is_empty() {
+ summary.push(format!(
+ "Failed to uninstall {} (see error(s) above).",
+ failed.join(", ")
+ ));
+ }
+
+ if !succeeded.is_empty() || !failed.is_empty() {
+ config.shell().status("Summary", summary.join(" "))?;
+ }
+
+ !failed.is_empty()
+ };
+
+ if scheduled_error {
+ bail!("some packages failed to uninstall");
+ }
+
+ Ok(())
+}
+
+pub fn uninstall_one(
+ root: &Filesystem,
+ spec: &str,
+ bins: &[String],
+ config: &Config,
+) -> CargoResult<()> {
+ let tracker = InstallTracker::load(config, root)?;
+ let all_pkgs = tracker.all_installed_bins().map(|(pkg_id, _set)| *pkg_id);
+ let pkgid = PackageIdSpec::query_str(spec, all_pkgs)?;
+ uninstall_pkgid(root, tracker, pkgid, bins, config)
+}
+
+fn uninstall_cwd(root: &Filesystem, bins: &[String], config: &Config) -> CargoResult<()> {
+ let tracker = InstallTracker::load(config, root)?;
+ let source_id = SourceId::for_path(config.cwd())?;
+ let mut src = path_source(source_id, config)?;
+ let pkg = select_pkg(
+ &mut src,
+ None,
+ |path: &mut PathSource<'_>| path.read_packages(),
+ config,
+ )?;
+ let pkgid = pkg.package_id();
+ uninstall_pkgid(root, tracker, pkgid, bins, config)
+}
+
+fn uninstall_pkgid(
+ root: &Filesystem,
+ mut tracker: InstallTracker,
+ pkgid: PackageId,
+ bins: &[String],
+ config: &Config,
+) -> CargoResult<()> {
+ let mut to_remove = Vec::new();
+ let installed = match tracker.installed_bins(pkgid) {
+ Some(bins) => bins.clone(),
+ None => bail!("package `{}` is not installed", pkgid),
+ };
+
+ let dst = root.join("bin").into_path_unlocked();
+ for bin in &installed {
+ let bin = dst.join(bin);
+ if !bin.exists() {
+ bail!(
+ "corrupt metadata, `{}` does not exist when it should",
+ bin.display()
+ )
+ }
+ }
+
+ let bins = bins
+ .iter()
+ .map(|s| {
+ if s.ends_with(env::consts::EXE_SUFFIX) {
+ s.to_string()
+ } else {
+ format!("{}{}", s, env::consts::EXE_SUFFIX)
+ }
+ })
+ .collect::<BTreeSet<_>>();
+
+ for bin in bins.iter() {
+ if !installed.contains(bin) {
+ bail!("binary `{}` not installed as part of `{}`", bin, pkgid)
+ }
+ }
+
+ if bins.is_empty() {
+ to_remove.extend(installed.iter().map(|b| dst.join(b)));
+ tracker.remove(pkgid, &installed);
+ } else {
+ for bin in bins.iter() {
+ to_remove.push(dst.join(bin));
+ }
+ tracker.remove(pkgid, &bins);
+ }
+ tracker.save()?;
+ for bin in to_remove {
+ config.shell().status("Removing", bin.display())?;
+ paths::remove_file(bin)?;
+ }
+
+ Ok(())
+}