diff --git a/src/config.rs b/src/config.rs index 0c598acf..51a7ac28 100644 --- a/src/config.rs +++ b/src/config.rs @@ -180,10 +180,20 @@ pub struct Brew { greedy_cask: Option, } +#[derive(Debug, Deserialize, Clone, Copy)] +pub enum ArchPackageManager { + Autodetect, + Trizen, + Paru, + Yay, + Pacman, +} + #[derive(Deserialize, Default, Debug)] #[serde(deny_unknown_fields)] pub struct Linux { yay_arguments: Option, + arch_package_manager: Option, trizen_arguments: Option, dnf_arguments: Option, apt_arguments: Option, @@ -630,6 +640,16 @@ impl Config { .unwrap_or("") } + /// Extra yay arguments + #[allow(dead_code)] + pub fn arch_package_manager(&self) -> ArchPackageManager { + self.config_file + .linux + .as_ref() + .and_then(|s| s.arch_package_manager) + .unwrap_or(ArchPackageManager::Autodetect) + } + /// Extra yay arguments #[allow(dead_code)] pub fn yay_arguments(&self) -> &str { diff --git a/src/steps/os/archlinux.rs b/src/steps/os/archlinux.rs new file mode 100644 index 00000000..84fd7a9b --- /dev/null +++ b/src/steps/os/archlinux.rs @@ -0,0 +1,173 @@ +use crate::config; +use crate::execution_context::ExecutionContext; +use crate::utils::which; +use anyhow::Result; +use std::env::var_os; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn get_execution_path() -> OsString { + let mut path = OsString::from("/usr/bin:"); + path.push(var_os("PATH").unwrap()); + path +} + +pub trait ArchPackageManager { + fn upgrade(&self, ctx: &ExecutionContext) -> Result<()>; +} + +pub struct YayParu { + executable: PathBuf, + pacman: PathBuf, +} + +impl ArchPackageManager for YayParu { + fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { + Command::new(&self.executable) + .arg("-Pw") + .spawn() + .and_then(|mut p| p.wait()) + .ok(); + + let mut command = ctx.run_type().execute(&self.executable); + + command + .arg("--pacman") + .arg(&self.pacman) + .arg("-Syu") + .args(ctx.config().yay_arguments().split_whitespace()) + .env("PATH", get_execution_path()); + + if ctx.config().yes() { + command.arg("--noconfirm"); + } + command.check_run()?; + + if ctx.config().cleanup() { + let mut command = ctx.run_type().execute(&self.executable); + command.arg("--pacman").arg(&self.pacman).arg("-Scc"); + if ctx.config().yes() { + command.arg("--noconfirm"); + } + command.check_run()?; + } + + Ok(()) + } +} + +impl YayParu { + fn get(exec_name: &str, pacman: &Path) -> Option { + Some(Self { + executable: which(exec_name)?, + pacman: pacman.to_owned(), + }) + } +} + +pub struct Trizen { + executable: PathBuf, +} + +impl ArchPackageManager for Trizen { + fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { + let mut command = ctx.run_type().execute(&self.executable); + + command + .arg("-Syu") + .args(ctx.config().trizen_arguments().split_whitespace()) + .env("PATH", get_execution_path()); + + if ctx.config().yes() { + command.arg("--noconfirm"); + } + command.check_run()?; + + if ctx.config().cleanup() { + let mut command = ctx.run_type().execute(&self.executable); + command.arg("-Sc"); + if ctx.config().yes() { + command.arg("--noconfirm"); + } + command.check_run()?; + } + + Ok(()) + } +} + +impl Trizen { + fn get() -> Option { + Some(Self { + executable: which("trizen")?, + }) + } +} + +pub struct Pacman { + sudo: PathBuf, + executable: PathBuf, +} + +impl ArchPackageManager for Pacman { + fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { + let mut command = ctx.run_type().execute(&self.sudo); + command + .arg(&self.executable) + .arg("-Syu") + .env("PATH", get_execution_path()); + if ctx.config().yes() { + command.arg("--noconfirm"); + } + command.check_run()?; + + if ctx.config().cleanup() { + let mut command = ctx.run_type().execute(&self.sudo); + command.arg(&self.executable).arg("-Scc"); + if ctx.config().yes() { + command.arg("--noconfirm"); + } + command.check_run()?; + } + + Ok(()) + } +} + +impl Pacman { + pub fn get(ctx: &ExecutionContext) -> Option { + Some(Self { + executable: which("powerpill").unwrap_or_else(|| PathBuf::from("/usr/bin/pacman")), + sudo: ctx.sudo().to_owned()?, + }) + } +} + +fn box_pacakge_manager(package_manager: P) -> Box { + Box::new(package_manager) as Box +} + +pub fn get_arch_package_manager(ctx: &ExecutionContext) -> Option> { + let pacman = which("powerpill").unwrap_or_else(|| PathBuf::from("/usr/bin/pacman")); + + match ctx.config().arch_package_manager() { + config::ArchPackageManager::Autodetect => YayParu::get("paru", &pacman) + .map(box_pacakge_manager) + .or_else(|| YayParu::get("yay", &pacman).map(box_pacakge_manager)) + .or_else(|| { + Trizen::get() + .map(box_pacakge_manager) + .or_else(|| Pacman::get(ctx).map(box_pacakge_manager)) + }), + config::ArchPackageManager::Trizen => Trizen::get().map(box_pacakge_manager), + config::ArchPackageManager::Paru => YayParu::get("paru", &pacman).map(box_pacakge_manager), + config::ArchPackageManager::Yay => YayParu::get("yay", &pacman).map(box_pacakge_manager), + config::ArchPackageManager::Pacman => Pacman::get(ctx).map(box_pacakge_manager), + } +} + +pub fn upgrade_arch_linux(ctx: &ExecutionContext) -> Result<()> { + let package_manager = get_arch_package_manager(ctx).unwrap(); + package_manager.upgrade(ctx) +} diff --git a/src/steps/os/linux.rs b/src/steps/os/linux.rs index d51ed6c1..2f797b0c 100644 --- a/src/steps/os/linux.rs +++ b/src/steps/os/linux.rs @@ -1,14 +1,13 @@ use crate::error::{SkipStep, TopgradeError}; use crate::execution_context::ExecutionContext; use crate::executor::{CommandExt, RunType}; +use crate::steps::os::archlinux; use crate::terminal::{print_separator, print_warning}; use crate::utils::{require, require_option, which, PathExt}; use anyhow::Result; use ini::Ini; use log::debug; use serde::Deserialize; -use std::env::var_os; -use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::process::Command; use walkdir::WalkDir; @@ -95,7 +94,7 @@ impl Distribution { match self { Distribution::Alpine => upgrade_alpine_linux(ctx), - Distribution::Arch => upgrade_arch_linux(ctx), + Distribution::Arch => archlinux::upgrade_arch_linux(ctx), Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx), Distribution::ClearLinux => upgrade_clearlinux(ctx), Distribution::Debian => upgrade_debian(ctx), @@ -155,97 +154,6 @@ fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> { ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").check_run() } -fn upgrade_arch_linux(ctx: &ExecutionContext) -> Result<()> { - let pacman = which("powerpill").unwrap_or_else(|| PathBuf::from("/usr/bin/pacman")); - let yes = ctx.config().yes(); - let sudo = ctx.sudo(); - let run_type = ctx.run_type(); - let cleanup = ctx.config().cleanup(); - - let path = { - let mut path = OsString::from("/usr/bin:"); - path.push(var_os("PATH").unwrap()); - path - }; - debug!("Running Arch update with path: {:?}", path); - - let yay = which("yay"); - if let Some(yay) = &yay { - run_type - .execute(&yay) - .arg("-Pw") - .spawn() - .and_then(|mut p| p.wait()) - .ok(); - } - - if let Some(yay) = yay.or_else(|| which("paru")) { - let mut command = run_type.execute(&yay); - - command - .arg("--pacman") - .arg(&pacman) - .arg("-Syu") - .args(ctx.config().yay_arguments().split_whitespace()) - .env("PATH", path); - - if yes { - command.arg("--noconfirm"); - } - command.check_run()?; - - if cleanup { - let mut command = run_type.execute(&yay); - command.arg("--pacman").arg(&pacman).arg("-Scc"); - if yes { - command.arg("--noconfirm"); - } - command.check_run()?; - } - } else if let Some(trizen) = which("trizen") { - let mut command = run_type.execute(&trizen); - - command - .arg("-Syu") - .args(ctx.config().trizen_arguments().split_whitespace()) - .env("PATH", path); - - if yes { - command.arg("--noconfirm"); - } - command.check_run()?; - - if cleanup { - let mut command = run_type.execute(&trizen); - command.arg("-Sc"); - if yes { - command.arg("--noconfirm"); - } - command.check_run()?; - } - } else if let Some(sudo) = &sudo { - let mut command = run_type.execute(&sudo); - command.arg(&pacman).arg("-Syu").env("PATH", path); - if yes { - command.arg("--noconfirm"); - } - command.check_run()?; - - if cleanup { - let mut command = run_type.execute(&sudo); - command.arg(&pacman).arg("-Scc"); - if yes { - command.arg("--noconfirm"); - } - command.check_run()?; - } - } else { - print_warning("Neither sudo nor yay detected. Skipping system upgrade"); - } - - Ok(()) -} - fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> { let _ = if let Some(ostree) = Path::new("/usr/bin/rpm-ostree").if_exists() { if ctx.config().rpm_ostree() { @@ -623,6 +531,7 @@ mod tests { expected_distribution ); } + #[test] fn test_arch_linux() { test_template(include_str!("os_release/arch"), Distribution::Arch); diff --git a/src/steps/os/mod.rs b/src/steps/os/mod.rs index 63d0ef28..fc7a5a78 100644 --- a/src/steps/os/mod.rs +++ b/src/steps/os/mod.rs @@ -1,5 +1,7 @@ #[cfg(target_os = "android")] pub mod android; +#[cfg(target_os = "linux")] +mod archlinux; #[cfg(target_os = "dragonfly")] pub mod dragonfly; #[cfg(target_os = "freebsd")]