use crate::core::PackageId;
use crate::core::{PackageIdSpec, PackageIdSpecQuery, SourceId};
use crate::ops::common_for_install_and_uninstall::*;
use crate::sources::PathSource;
use crate::util::errors::CargoResult;
use crate::util::Filesystem;
use crate::util::GlobalContext;
use anyhow::bail;
use std::collections::BTreeSet;
use std::env;
pub fn uninstall(
root: Option<&str>,
specs: Vec<&str>,
bins: &[String],
gctx: &GlobalContext,
) -> 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, gctx)?;
let scheduled_error = if specs.len() == 1 {
uninstall_one(&root, specs[0], bins, gctx)?;
false
} else if specs.is_empty() {
uninstall_cwd(&root, bins, gctx)?;
false
} else {
let mut succeeded = vec![];
let mut failed = vec![];
for spec in specs {
let root = root.clone();
match uninstall_one(&root, spec, bins, gctx) {
Ok(()) => succeeded.push(spec),
Err(e) => {
crate::display_error(&e, &mut gctx.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() {
gctx.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],
gctx: &GlobalContext,
) -> CargoResult<()> {
let tracker = InstallTracker::load(gctx, 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, gctx)
}
fn uninstall_cwd(root: &Filesystem, bins: &[String], gctx: &GlobalContext) -> CargoResult<()> {
let tracker = InstallTracker::load(gctx, root)?;
let source_id = SourceId::for_path(gctx.cwd())?;
let mut src = path_source(source_id, gctx)?;
let pkg = select_pkg(
&mut src,
None,
|path: &mut PathSource<'_>| path.root_package().map(|p| vec![p]),
gctx,
None,
)?;
let pkgid = pkg.package_id();
uninstall_pkgid(root, tracker, pkgid, bins, gctx)
}
fn uninstall_pkgid(
root: &Filesystem,
mut tracker: InstallTracker,
pkgid: PackageId,
bins: &[String],
gctx: &GlobalContext,
) -> CargoResult<()> {
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)
}
}
let to_remove = {
if bins.is_empty() {
installed
} else {
bins
}
};
for bin in to_remove {
let bin_path = dst.join(&bin);
gctx.shell().status("Removing", bin_path.display())?;
tracker.remove_bin_then_save(pkgid, &bin, &bin_path)?;
}
Ok(())
}