use std::sync::Arc;
use console::style;
use eyre::{Result, bail, eyre};
use itertools::Itertools;
use crate::backend::Backend;
use crate::cli::args::ToolArg;
use crate::config::Config;
use crate::toolset::{ToolRequest, ToolSource, ToolVersion, ToolsetBuilder};
use crate::ui::install_progress::removal_progress;
use crate::ui::multi_progress_report::MultiProgressReport;
use crate::{config, dirs, exit, file};
#[derive(Debug, usage_rs::Args)]
#[usage(
verbatim_doc_comment,
example(
r###"mise uninstall node@18.0.0"###,
help = r###"uninstall a specific version"###
),
example(
r###"mise uninstall node"###,
help = r###"uninstall the current node version (if only one version is installed)"###
),
example(
r###"mise uninstall --all node"###,
help = r###"uninstall every installed version of node"###
)
)]
pub(crate) struct Uninstall {
#[usage(value_name = "INSTALLED_TOOL@VERSION", required_unless = "all")]
installed_tool: Vec<ToolArg>,
#[usage(long, short)]
all: bool,
#[usage(long, short = 'n')]
dry_run: bool,
#[usage(long, verbatim_doc_comment)]
dry_run_code: bool,
}
impl Uninstall {
pub(super) fn is_dry_run(&self) -> bool {
self.dry_run || self.dry_run_code
}
pub(crate) async fn run(self) -> Result<()> {
let config = Config::get().await?;
let tool_versions = if self.installed_tool.is_empty() && self.all {
self.get_all_tool_versions(&config).await?
} else {
self.get_requested_tool_versions(&config).await?
};
let tool_versions = tool_versions
.into_iter()
.unique_by(|(_, tv)| (tv.request.ba().short.clone(), tv.version.clone()))
.collect::<Vec<_>>();
if !self.all && tool_versions.len() > self.installed_tool.len() {
bail!("multiple tools specified, use --all to uninstall all versions");
}
let removed_install_paths = tool_versions
.iter()
.map(|(_, tv)| tv.install_path())
.collect::<Vec<_>>();
let mpr = MultiProgressReport::get();
let mut to_remove = Vec::with_capacity(tool_versions.len());
for (plugin, tv) in tool_versions {
if !plugin.is_version_installed(&config, &tv, true)
&& !file::entry_exists(tv.install_path())
{
warn!("{} is not installed", tv.style());
continue;
}
to_remove.push((plugin, tv));
}
let has_work = !to_remove.is_empty();
let mut progress = if self.is_dry_run() {
None
} else {
removal_progress(
&mpr,
to_remove
.iter()
.map(|(_, tv)| (format!("{}@{}", tv.ba().short, tv.version), tv.style())),
)
};
for (plugin, tv) in to_remove {
let key = format!("{}@{}", tv.ba().short, tv.version);
let tool = progress
.as_ref()
.and_then(|progress| progress.start_tool(&key));
let pr = match &tool {
Some(tool) => tool.reporter(),
None => mpr.add(&tv.style()),
};
let result = plugin
.uninstall_version(&config, &tv, pr.as_ref(), self.is_dry_run())
.await;
if let Some(tool) = &tool {
tool.complete(result.as_ref().err().map(|e| e.to_string()).as_deref());
}
if let Err(err) = result {
error!("{err}");
return Err(eyre!(err).wrap_err(format!("failed to uninstall {tv}")));
}
if self.is_dry_run() {
pr.finish_with_message("uninstalled (dry-run)".into());
} else {
if let Err(err) = crate::tool_purgatory::forget_path(&tv.install_path()) {
warn!("failed to clear tool purgatory entry: {err:#}");
}
if tool.is_none() {
pr.finish_with_message("uninstalled".into());
}
}
}
if let Some(progress) = progress.as_mut() {
progress.finish(vec![]);
}
if self.is_dry_run() {
if self.dry_run_code && has_work {
return Err(exit::request(1));
}
return Ok(());
}
file::touch_dir(&dirs::DATA)?;
let config = Config::reset().await?;
let ts = config.get_toolset().await?;
config::rebuild_shims_and_runtime_symlinks_after_removal(
&config,
ts,
&removed_install_paths,
)
.await?;
Ok(())
}
async fn get_all_tool_versions(
&self,
config: &Arc<Config>,
) -> Result<Vec<(Arc<dyn Backend>, ToolVersion)>> {
let ts = ToolsetBuilder::new().build(config).await?;
let tool_versions = ts
.list_installed_versions(config)
.await?
.into_iter()
.collect::<Vec<_>>();
Ok(tool_versions)
}
async fn get_requested_tool_versions(
&self,
config: &Arc<Config>,
) -> Result<Vec<(Arc<dyn Backend>, ToolVersion)>> {
let runtimes = ToolArg::double_tool_condition(&self.installed_tool)?;
let mut tool_versions = Vec::new();
for ta in runtimes {
let backend = ta.ba.backend()?;
let query = ta.tvr.as_ref().map(|tvr| tvr.version()).unwrap_or_default();
let installed_versions = backend.list_installed_versions();
let exact_match = installed_versions.iter().find(|v| v == &&query);
let matches = match exact_match {
Some(m) => vec![m],
None => installed_versions
.iter()
.filter(|v| v.starts_with(&query))
.collect_vec(),
};
let mut tvs = Vec::new();
if let Some(tvr) = &ta.tvr {
tvs.push((
backend.clone(),
tvr.resolve(config, &Default::default()).await?,
));
}
tvs.extend(
matches
.into_iter()
.map(|v| {
let tvr = ToolRequest::new(backend.ba().clone(), v, ToolSource::Unknown)?;
let tv = ToolVersion::new(tvr, v.into());
Ok((backend.clone(), tv))
})
.collect::<Result<Vec<_>>>()?,
);
if tvs.is_empty() {
warn!(
"no versions found for {}",
style(&backend).blue().for_stderr()
);
}
tool_versions.extend(tvs);
}
Ok(tool_versions)
}
}