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(())
}