use std::sync::Arc;
use indexmap::IndexSet;
use crate::backend::Backend;
use crate::config::settings::SystemDepsMode;
use crate::config::{Config, Settings};
use crate::system::deps::{self, DepStatus, SystemDep};
use crate::toolset::install_options::InstallOptions;
use crate::toolset::tool_request::ToolRequest;
use crate::toolset::tool_version::ToolVersion;
pub(super) type TVTuple = (Arc<dyn Backend>, ToolVersion);
pub(super) fn show_python_install_hint(versions: &[ToolRequest]) {
let num_python = versions
.iter()
.filter(|tr| tr.ba().tool_name == "python")
.count();
if num_python != 1 {
return;
}
hint!(
"python_multi",
"use multiple versions simultaneously with",
"mise use python@3.12 python@3.11"
);
}
pub(super) async fn preflight_system_deps(
config: &Arc<Config>,
versions: &[ToolRequest],
opts: &InstallOptions,
) {
let mode = Settings::get().system_deps;
if mode == SystemDepsMode::Ignore {
return;
}
if let Err(err) = preflight_system_deps_inner(config, versions, opts, mode).await {
warn!("system dependency check failed: {err:#}");
}
}
async fn preflight_system_deps_inner(
config: &Arc<Config>,
versions: &[ToolRequest],
opts: &InstallOptions,
mode: SystemDepsMode,
) -> eyre::Result<()> {
let mut seen_backends = IndexSet::new();
let mut per_tool: Vec<(String, Vec<SystemDep>)> = vec![];
for tr in versions {
if !tr.is_os_supported() {
continue;
}
let Ok(backend) = tr.backend() else { continue };
if !seen_backends.insert(backend.ba().short.clone()) {
continue;
}
let deps = backend.system_dependencies();
if !deps.is_empty() {
per_tool.push((backend.ba().tool_name.clone(), deps));
}
}
if per_tool.is_empty() {
return Ok(());
}
let mut missing_required: Vec<(String, DepStatus)> = vec![];
let mut missing_optional: Vec<(String, DepStatus)> = vec![];
for (tool, deps) in &per_tool {
for status in deps::detect(deps).await {
if status.satisfied {
continue;
}
if status.dep.optional.is_some() {
missing_optional.push((tool.clone(), status));
} else {
missing_required.push((tool.clone(), status));
}
}
}
for (tool, status) in &missing_optional {
let reason = status.dep.optional.as_deref().unwrap_or_default();
info!(
"{tool}: optional system dependency {} is missing ({reason})",
status.dep.label()
);
}
if missing_required.is_empty() {
return Ok(());
}
let effective = match mode {
SystemDepsMode::Prompt if !console::user_attended_stderr() && !opts.yes => {
SystemDepsMode::Warn
}
other => other,
};
report_missing(&missing_required);
let missing_deps: Vec<SystemDep> = missing_required
.iter()
.map(|(_, s)| s.dep.clone())
.collect();
let refs: Vec<&SystemDep> = missing_deps.iter().collect();
let (mut by_mgr, unremediable) = deps::build_requests(&refs);
crate::system::attach_brew_tap_urls(config, &mut by_mgr);
for dep in &unremediable {
warn!(
"no available package manager can install {} — install it manually",
dep.label()
);
}
if effective == SystemDepsMode::Warn {
for line in deps::hint_commands(&refs) {
warn!("install with: {line}");
}
return Ok(());
}
if by_mgr.is_empty() {
return Ok(());
}
let mgrs = crate::system::packages_from_requests(by_mgr)?;
let driver_opts = crate::cli::system::driver::DriverOpts {
manager: None,
explicit: false,
dry_run: opts.dry_run,
update: false,
yes: matches!(mode, SystemDepsMode::Auto) || opts.yes,
};
if let Err(err) = crate::cli::system::driver::run(
mgrs,
crate::cli::system::driver::Action::Install,
&driver_opts,
)
.await
{
warn!("failed to install system dependencies: {err:#}");
return Ok(());
}
if opts.dry_run {
return Ok(());
}
let remediated: Vec<SystemDep> = missing_deps
.iter()
.filter(|d| deps::pick_manager(d).is_some())
.cloned()
.collect();
for status in deps::detect_fresh(&remediated).await {
if !status.satisfied {
warn!(
"{} is still not detected after attempting to install it — the package install \
may have been declined or skipped, or it needs to be linked or added to PATH \
(e.g. `brew link --force`)",
status.dep.label()
);
}
}
Ok(())
}
fn report_missing(missing: &[(String, DepStatus)]) {
warn!("missing system dependencies for tools about to install:");
for (tool, status) in missing {
match (&status.found, &status.reason) {
(Some(found), _) => warn!(" {tool}: {} required, found {found}", status.dep.label()),
(None, Some(reason)) => warn!(" {tool}: {} — {reason}", status.dep.label()),
(None, None) => warn!(" {tool}: {}", status.dep.label()),
}
}
}