use std::collections::HashMap;
use eyre::{Result, bail};
use crate::config::Settings;
use crate::system::ManagerPackages;
use crate::system::packages::{InstallOpts, PackageState};
use crate::ui::prompt;
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum Action {
Install,
Upgrade,
}
impl Action {
fn verb(self) -> &'static str {
match self {
Action::Install => "install",
Action::Upgrade => "upgrade",
}
}
}
pub(crate) struct DriverOpts {
pub manager: Option<String>,
pub explicit: bool,
pub dry_run: bool,
pub update: bool,
pub yes: bool,
}
pub(crate) async fn run(mgrs: Vec<ManagerPackages>, action: Action, d: &DriverOpts) -> Result<()> {
if let Some(only) = &d.manager
&& !mgrs.iter().any(|mp| mp.manager.name() == only)
{
if let Some(enabled) = &Settings::get().system_packages.managers
&& !enabled.contains(only)
{
bail!(
"manager '{only}' is excluded by the system_packages.managers setting \
(currently: {})",
enabled.join(", ")
);
}
bail!("no packages requested for manager '{only}'");
}
if mgrs.is_empty() {
info!("no system packages configured in [system.packages]");
return Ok(());
}
let opts = InstallOpts {
dry_run: d.dry_run,
update: d.update,
};
for mp in mgrs {
if let Some(only) = &d.manager
&& mp.manager.name() != only
{
continue;
}
let name = mp.manager.name();
if mp.disabled {
if d.manager.is_some() {
bail!("manager '{name}' is excluded by the system_packages.managers setting");
}
debug!("{name}: skipping, excluded by system_packages.managers");
continue;
}
if !mp.manager.is_available() {
if d.manager.is_some() || d.explicit {
bail!(
"{name} is not available: {}",
mp.manager.unavailable_reason()
);
}
debug!("{name}: skipping, {}", mp.manager.unavailable_reason());
continue;
}
let statuses = mp.manager.installed(&mp.requests).await?;
let mut targets: Vec<_> = statuses
.iter()
.filter(|s| match action {
Action::Install => !matches!(s.state, PackageState::Installed { .. }),
Action::Upgrade => !matches!(s.state, PackageState::Missing),
})
.map(|s| s.request.clone())
.collect();
let skipped = statuses.len() - targets.len();
if action == Action::Upgrade && skipped > 0 {
warn!("{name}: {skipped} package(s) not installed — run `mise system install` first");
}
if !mp.manager.supports_version_pins() {
targets.retain(|r| {
if r.version.is_some() {
warn!(
"{name}: cannot {} pinned version '{r}', skipping",
action.verb()
);
false
} else {
true
}
});
}
if action == Action::Install && skipped > 0 {
info!("{name}: {skipped} package(s) already installed");
}
if targets.is_empty() {
continue;
}
let list = targets.iter().map(|r| r.to_string()).collect::<Vec<_>>();
if !d.dry_run && !d.yes && console::user_attended_stderr() {
let msg = format!("{name}: {} {}?", action.verb(), list.join(", "));
if !prompt::confirm(msg)? {
info!("{name}: skipped");
continue;
}
}
match action {
Action::Install => {
mp.manager.install(&targets, &opts).await?;
if !d.dry_run {
info!("{name}: installed {}", list.join(", "));
}
}
Action::Upgrade => {
let prior: HashMap<String, String> = statuses
.iter()
.filter_map(|s| match &s.state {
PackageState::Installed { version }
| PackageState::VersionMismatch { installed: version } => {
Some((s.request.name.clone(), version.clone()))
}
PackageState::Missing => None,
})
.collect();
mp.manager.upgrade(&targets, &opts).await?;
if !d.dry_run {
let after = mp.manager.installed(&targets).await?;
let changed: Vec<String> = after
.iter()
.filter_map(|s| match &s.state {
PackageState::Installed { version }
| PackageState::VersionMismatch { installed: version } => {
let old = prior.get(&s.request.name)?;
(old != version)
.then(|| format!("{} {old} -> {version}", s.request.name))
}
PackageState::Missing => None,
})
.collect();
if changed.is_empty() {
info!("{name}: already up to date");
} else {
info!("{name}: upgraded {}", changed.join(", "));
}
}
}
}
}
Ok(())
}