diff --git a/Cargo.toml b/Cargo.toml index 6afad75..59615ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ directories = "4.0.1" log = "0.4.17" os-release = "0.1.0" pomsky-macro = "0.10.0" +procfs = "0.16.0" regex = "1.10.2" reqwest = {version = "0.11.21", default-features = false, features = ["default-tls", "blocking"]} serde = {version = "1.0.152", features = ["derive"]} diff --git a/src/impaccable.rs b/src/impaccable.rs index 359e39c..d643e7e 100644 --- a/src/impaccable.rs +++ b/src/impaccable.rs @@ -16,20 +16,23 @@ type Result = std::result::Result; pub type PackageId = String; pub type GroupId = String; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct PackageGroup { - pub members: BTreeSet + pub members: BTreeSet, } impl PackageGroup { pub fn new() -> Self { Self { - members: BTreeSet::new() + ..Default::default() } } pub fn from_members(members: BTreeSet) -> Self { - Self { members } - } + Self { + members, + ..Default::default() + } + } } /// Default data structure to store Package groups diff --git a/src/impaccable/config.rs b/src/impaccable/config.rs index abcff60..9c9922d 100644 --- a/src/impaccable/config.rs +++ b/src/impaccable/config.rs @@ -45,8 +45,8 @@ impl ConfigManager { // verify all groups configured in the config file actually exist in the package configuration for (_target_id, target) in &config.targets { for configured_group in &target.root_groups { - if package_group_names.contains(configured_group){ - return Err(impaccable::Error::GroupNotFound { group: configured_group.to_owned() }); + if !package_group_names.contains(configured_group){ + return Err(impaccable::Error::GroupNotFound { group: configured_group.to_owned(), package_dir: package_config_path }); } } } @@ -55,6 +55,7 @@ impl ConfigManager { } pub fn config(&self) -> &Config { &self.config } + pub fn config_path(&self) -> &Path { &self.config_path } pub fn package_config(&self) -> &PackageConfiguration { &self.package_config } pub fn package_config_mut(&mut self) -> &mut PackageConfiguration { &mut self.package_config } @@ -95,6 +96,7 @@ impl ConfigManager { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { pub package_dir: PathBuf, + pub pacman_wrapper: String, pub targets: BTreeMap } @@ -115,6 +117,7 @@ impl Config { ); Ok(Self { package_dir : "./packages".into(), + pacman_wrapper: "paru".into(), targets, }) } @@ -125,19 +128,12 @@ pub struct TargetConfig { pub root_groups: BTreeSet } -/// Represents the parsed form of the entire package configuration of a system. -/// Files are indexed by their absolute paths. -#[derive(Debug, Default, Clone)] -pub struct PackageConfiguration { - pub files : HashMap -} - #[derive(Debug, Default, Clone)] pub struct PackageFile { pub groups: PackageGroupMap } -impl PackageFile { +impl PackageFile { pub fn new() -> Self { Self { groups: BTreeMap::new() @@ -148,10 +144,24 @@ impl PackageFile { } } +/// Represents the parsed form of the entire package configuration of a system. +/// Files are indexed by their absolute paths. +#[derive(Debug, Default, Clone)] +pub struct PackageConfiguration { + pub files : HashMap, + package_dir : PathBuf +} + impl PackageConfiguration{ + fn new(package_dir: &Path) -> Self { + Self { + package_dir: package_dir.into(), + ..Default::default() + } + } /// Parses a package directory to generate a corresponding `PackageConfiguration` fn parse(package_dir: &Path) -> impaccable::Result { - let mut package_configuration = PackageConfiguration::default(); + let mut package_configuration = PackageConfiguration::new(package_dir); for entry in WalkDir::new(package_dir) .into_iter() @@ -243,7 +253,7 @@ impl PackageConfiguration{ } } let (Some(package_group), Some(file)) = (package_group_ref, file_to_save) else { - return Err(Error::GroupNotFound { group: group_id.clone() }); + return Err(Error::GroupNotFound { group: group_id.clone(), package_dir: self.package_dir.clone() }); }; package_group.extend(packages); self.write_file_to_disk(&file)?; @@ -268,7 +278,7 @@ impl PackageConfiguration{ } let Some(file) = file_to_save else { if !group_found { - return Err(Error::GroupNotFound { group: group_id.to_owned() }); + return Err(Error::GroupNotFound { group: group_id.to_owned(), package_dir: self.package_dir.clone() }); } else { return Err(Error::PackageNotFound { package: package_id.clone() }); } diff --git a/src/impaccable/distro.rs b/src/impaccable/distro.rs index 5ef5f73..46d9908 100644 --- a/src/impaccable/distro.rs +++ b/src/impaccable/distro.rs @@ -1,6 +1,7 @@ -use std::{collections::BTreeMap, fmt::Display}; +use std::{collections::BTreeMap, fmt::Display, io::BufReader, fs::File}; -use anyhow::bail; +use anyhow::{bail, Context}; +use procfs::{self, FromBufRead}; use crate::impaccable::{PackageGroupMap, PackageGroup}; @@ -39,9 +40,9 @@ pub fn generate_configuration(system_config: &SystemConfiguration) -> anyhow::Re let response = reqwest::blocking::get(package_url)?.text()?; println!("{}", response); - let package_group = PackageGroup { - members: response.lines().map(|x| x.to_owned()).collect() - }; + let package_group = PackageGroup::from_members( + response.lines().map(|x| x.to_owned()).collect() + ); group_map.insert(format!("{}-{}", system_config.distro, url_path), package_group); } Ok(group_map) @@ -52,6 +53,26 @@ pub fn generate_configuration(system_config: &SystemConfiguration) -> anyhow::Re } } + +// TODO: use this for proper AMD and intel ucode handling +fn get_cpu_vendor() -> anyhow::Result { + let file = File::open("/proc/cpuinfo")?; + let reader = BufReader::new(file); + let cpuinfo = procfs::CpuInfo::from_buf_read(reader)?; + let vendor_id = cpuinfo.fields.get("vendor_id").context("Failed to retrieve vendor_id")?; + match &vendor_id[..] { + "Amd" => Ok(CpuVendor::Amd), + "Intel" => Ok(CpuVendor::Intel), + // TODO: Improve error reporting + _ => bail!("Unsupported Cpu Vendor") + } +} + +enum CpuVendor { + Amd, + Intel, +} + #[derive(Debug, Clone)] pub struct SystemConfiguration { pub distro: String, diff --git a/src/impaccable/error.rs b/src/impaccable/error.rs index 4868c62..0a6c9ae 100644 --- a/src/impaccable/error.rs +++ b/src/impaccable/error.rs @@ -21,9 +21,10 @@ pub enum Error { PackageFileNotFound { package_file: PathBuf, }, - #[error("Group `{group}` not found")] + #[error("Group `{group}` not found in package dir `{package_dir}`")] GroupNotFound { group: GroupId, + package_dir: PathBuf, }, #[error("Package `{package}` not found")] diff --git a/src/impaccable/pacman.rs b/src/impaccable/pacman.rs index 2aca175..a0c77e8 100644 --- a/src/impaccable/pacman.rs +++ b/src/impaccable/pacman.rs @@ -4,6 +4,10 @@ use anyhow::{Context, bail}; use pomsky_macro::pomsky; use regex::Regex; +pub struct PacmanWrapper { + pub wrapper: String +} + const RE_PACKAGE_REQUIRED_BY: &str = pomsky!( let package_name_char = ['a'-'z' '0'-'9' '@' '.' '_' '+' '-']; "Required By"[s]+": ":(((package_name_char+)' '*)+ | "None") @@ -18,9 +22,13 @@ fn re_package_required_by() -> &'static Regex { } /// Queries what packages are installed on the system -pub fn query_explicitly_installed() -> anyhow::Result> { +pub fn query_installed(explicit: bool) -> anyhow::Result> { + let mut pacman_args = String::from("-Qq"); + if explicit { + pacman_args.push('e') + } let pacman_output_bytes = Command::new("pacman") - .arg("-Qqe") + .arg(&pacman_args) .output() .context("Failed to run pacman -Qqe")? .stdout; @@ -32,22 +40,26 @@ pub fn query_explicitly_installed() -> anyhow::Result> { Ok(installed_set) } -/// Installs the supplied packages. -pub fn install_packages(packages: I) -> anyhow::Result<()> -where - I: IntoIterator, - S: AsRef, -{ - let _pacman_command = Command::new("pacman") - .arg("-S") - .args(packages) - .stdin(Stdio::inherit()) - .status() - .context("Failed to run pacman")?; - Ok(()) +impl PacmanWrapper { + /// Installs the supplied packages. + pub fn install_packages(&self, packages: I) -> anyhow::Result<()> + where + I: IntoIterator, + S: AsRef, + { + let _pacman_command = Command::new(self.wrapper.clone()) + .arg("-S") + .args(packages) + .stdin(Stdio::inherit()) + .status() + .context("Failed to run pacman")?; + Ok(()) + } } + + /// Uninstalls the supplied packages. pub fn uninstall_packages(packages: I) -> anyhow::Result where diff --git a/src/main.rs b/src/main.rs index 755637f..6e0a470 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,13 @@ use clap::Parser; use impaccable::{config::{ConfigManager, ActiveTarget}, pacman, PackageId}; use dialoguer::{Confirm, Editor, theme::ColorfulTheme, Input, FuzzySelect, MultiSelect, Select}; use directories::ProjectDirs; +use serde::de::IntoDeserializer; use std::{path::PathBuf, fs::{self, File}, env, io, collections::BTreeSet}; use std::io::Write; use anyhow::{Context, bail, anyhow}; use cli::{Cli, CliCommand, Target, Groups}; -use crate::impaccable::{pacman::packages_required_by, PackageGroup, GroupId}; +use crate::impaccable::{pacman::{packages_required_by, PacmanWrapper}, GroupId, PackageGroup}; fn main() -> std::result::Result<(), anyhow::Error> { let cli = Cli::parse(); @@ -33,6 +34,8 @@ fn main() -> std::result::Result<(), anyhow::Error> { } }; + println!("active target path: {}", active_target_path.to_string_lossy()); + let mut config_manager : impaccable::config::ConfigManager = { let config_path = { if let Some(cli_config_override) = cli.config { @@ -46,6 +49,8 @@ fn main() -> std::result::Result<(), anyhow::Error> { default_config_path } }; + println!("config path: {}", config_path.to_string_lossy()); + // Parse the config file. If it is not found, offer to create it instead. let config_manager = match ConfigManager::parse(config_path.clone()) { @@ -81,6 +86,7 @@ fn main() -> std::result::Result<(), anyhow::Error> { }; config_manager }; + let mut active_target = match std::fs::read_to_string(&active_target_path) { Ok(s) => ActiveTarget::parse(&s).context(format!("Failed to parse active target at '{}'", &active_target_path.to_string_lossy()))?, @@ -108,6 +114,8 @@ fn main() -> std::result::Result<(), anyhow::Error> { }, }; + println!("active target: {}", active_target.target()); + // The following code handles the different CLI (sub)commands, then exits. match &cli.command { None => {}, @@ -115,13 +123,16 @@ fn main() -> std::result::Result<(), anyhow::Error> { println!("config: {:?}", config_manager.config()); } Some(CliCommand::Sync { remove_untracked }) => { - let pacman_installed = impaccable::pacman::query_explicitly_installed().context("Failed to query installed packages")?; + // TODO: get from config + let pacman_wrapper = PacmanWrapper{ wrapper: "paru".into() }; + + let pacman_installed = impaccable::pacman::query_installed(true).context("Failed to query installed packages")?; let target_config = config_manager.config().targets.get(active_target.target()).ok_or_else(|| anyhow!(impaccable::Error::TargetNotFound(active_target.target().clone())))?; let should_be_installed : BTreeSet<&PackageId> = config_manager.package_config().packages_of_groups(&target_config.root_groups).collect(); let not_installed = should_be_installed.iter().filter(|package| !pacman_installed.contains(**package)); - pacman::install_packages(not_installed).context("Failed to install missing packages")?; + pacman_wrapper.install_packages(not_installed).context("Failed to install missing packages")?; if *remove_untracked { let untracked_packages = pacman_installed.iter().cloned().filter(|package| should_be_installed.contains(package)); @@ -168,7 +179,7 @@ fn main() -> std::result::Result<(), anyhow::Error> { } } Some(CliCommand::Plan { remove_untracked }) => { - let pacman_installed = impaccable::pacman::query_explicitly_installed().context("Failed to query installed packages")?; + let pacman_installed = impaccable::pacman::query_installed(true).context("Failed to query installed packages")?; let target = config_manager.config().targets.get(active_target.target()).context(format!("Failed to find root group {} in config", active_target.target()))?; @@ -230,7 +241,7 @@ fn main() -> std::result::Result<(), anyhow::Error> { } Some(CliCommand::Import) => { - let pacman_installed = impaccable::pacman::query_explicitly_installed().context("Failed to query installed packages")?; + let pacman_installed = impaccable::pacman::query_installed(true).context("Failed to query installed packages")?; let target = config_manager .config()