use std::sync::Arc;
use async_trait::async_trait;
use crate::result::Result;
use crate::system::ManagerPackageOptions;
pub mod apk;
pub mod apt;
#[cfg(unix)]
pub mod brew;
pub mod dnf;
pub mod flatpak;
pub mod mas;
pub mod pacman;
pub mod plugin;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PackageRequest {
pub name: String,
pub version: Option<String>,
pub tap_url: Option<String>,
}
impl std::fmt::Display for PackageRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.version {
Some(v) => write!(f, "{}@{}", self.name, v),
None => write!(f, "{}", self.name),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PackageState {
Installed {
version: String,
},
#[cfg(unix)]
InstalledAutoUpdates {
version: String,
},
Missing,
#[cfg_attr(windows, allow(dead_code))]
NeedsRepair {
installed: String,
},
VersionMismatch {
installed: String,
},
#[cfg(unix)]
Unavailable {
reason: String,
},
}
impl PackageState {
pub fn is_installed(&self) -> bool {
match self {
Self::Installed { .. } => true,
#[cfg(unix)]
Self::InstalledAutoUpdates { .. } => true,
_ => false,
}
}
pub fn auto_updates(&self) -> bool {
match self {
#[cfg(unix)]
Self::InstalledAutoUpdates { .. } => true,
_ => false,
}
}
#[cfg(unix)]
pub fn unavailable(reason: impl Into<String>) -> Self {
Self::Unavailable {
reason: reason.into(),
}
}
pub fn is_unavailable(&self) -> bool {
#[cfg(unix)]
if matches!(self, Self::Unavailable { .. }) {
return true;
}
false
}
pub fn unavailable_reason(&self) -> Option<&str> {
#[cfg(unix)]
if let Self::Unavailable { reason } = self {
return Some(reason);
}
None
}
}
#[derive(Debug, Clone)]
pub struct PackageStatus {
pub request: PackageRequest,
pub state: PackageState,
}
#[derive(Debug, Default)]
pub struct InstallOpts {
pub dry_run: bool,
pub update: bool,
}
#[async_trait(?Send)]
pub trait SystemPackageManager: Send + Sync {
fn name(&self) -> &str;
fn is_available(&self) -> bool;
fn unavailable_reason(&self) -> String;
async fn unavailable_reason_async(&self) -> Option<String> {
(!self.is_available()).then(|| self.unavailable_reason())
}
async fn installed(&self, pkgs: &[PackageRequest]) -> Result<Vec<PackageStatus>>;
async fn available(&self, names: &[String]) -> Result<Vec<bool>> {
Ok(vec![true; names.len()])
}
async fn install(&self, pkgs: &[PackageRequest], opts: &InstallOpts) -> Result<()>;
async fn install_with_options(
&self,
pkgs: &[PackageRequest],
opts: &InstallOpts,
_manager_options: &ManagerPackageOptions,
) -> Result<()> {
self.install(pkgs, opts).await
}
async fn upgrade(&self, pkgs: &[PackageRequest], opts: &InstallOpts) -> Result<()> {
self.install(pkgs, opts).await
}
fn supports_version_pins(&self) -> bool {
true
}
#[allow(dead_code)] fn is_plugin(&self) -> bool {
false
}
}
pub fn builtin_managers() -> Vec<Arc<dyn SystemPackageManager>> {
vec![
Arc::new(apk::ApkManager::new()),
Arc::new(apt::AptManager::new()),
#[cfg(unix)]
Arc::new(brew::BrewManager::new()),
#[cfg(unix)]
Arc::new(brew::BrewCaskManager::new()),
Arc::new(dnf::DnfManager::new()),
Arc::new(flatpak::FlatpakManager::new()),
Arc::new(flatpak::FlatpakManager::new_user()),
Arc::new(mas::MasManager::new()),
Arc::new(pacman::PacmanManager::new()),
]
}
pub fn is_builtin_manager_name(name: &str) -> bool {
builtin_managers()
.iter()
.any(|manager| manager.name() == name)
}
pub fn all_managers() -> Vec<Arc<dyn SystemPackageManager>> {
let mut managers = builtin_managers();
let builtins = managers
.iter()
.map(|manager| manager.name().to_string())
.collect::<std::collections::HashSet<_>>();
let Some(plugins) = crate::toolset::install_state::try_list_plugins() else {
return managers;
};
for (name, plugin_type) in plugins.iter() {
if *plugin_type != crate::plugins::PluginType::Package {
continue;
}
if builtins.contains(name) {
warn!(
"package plugin '{name}' collides with a built-in package manager; ignoring plugin"
);
continue;
}
match plugin::PackagePluginManager::new(name.clone()) {
Ok(manager) => managers.push(Arc::new(manager)),
Err(err) => warn!("failed to load package plugin '{name}': {err:#}"),
}
}
managers
}