//! This crate provides an AUR helper with a (hopefully) //! simple and easy to understand API. As it stands, this //! crate will absolutely hijack your input/output streams. //#[macro_use] //extern crate display_derive; extern crate failure; extern crate flate2; extern crate itertools; #[macro_use] extern crate log; extern crate pkg_utils; extern crate raur; extern crate raur_ext; extern crate reqwest; extern crate serde; #[macro_use] extern crate serde_derive; extern crate solvent; extern crate split_iter; extern crate tar; extern crate toml; extern crate version_compare; mod config; mod dependency; mod util; use std::rc::Rc; use std::path::Path; use failure::err_msg; use itertools::{Itertools, process_results}; use pkg_utils::Alpm; use split_iter::Splittable; use config::Config; use dependency::{ ActionType::*, package::Package, PkgAction, resolve_dependencies }; use util::{exists_path, Pacman, Result, xdg_dir}; pub use dependency::ActionType; pub use util::InstallReason; //pub type FnOnDoActions = Fn(pkgs: Iterator) -> bool; //pub type Action = (ActionType, String); /// `Aurum` contains all of the configuration details for the crate, /// including configuration files, output callbacks, etc. pub struct Aurum { config: Config, explicit_deps: Vec<(ActionType, String)>, before_actions: Option) -> bool> } ///TODO: Provide a way to pipe subprocess std streams impl Aurum { /// Create a new instance of Aurum. This entails a number of things: /// - Getting or creating a configuration /// - Verifying that configuration /// - Getting package info about packages already in the current configuration pub fn new(config_file: Option>) -> Result { let config_file: Option<_> = match config_file { Some(config_file) => { let filename = config_file.as_ref().into(); exists_path(filename) .or_else(|| { warn!("Configuration file {:?} does not exist, Using default configuration", config_file.as_ref()); None }) } None => { let mut filename = xdg_dir("XDG_CONFIG_HOME", ".config"); filename.push("/aurum.toml"); exists_path(filename) } }; Ok(Aurum { config: Config::new(config_file)?, explicit_deps: Vec::new(), before_actions: None }) } /// The main way to interact with Aurum. Add a dependency to the top /// level of the dependency tree. pub fn action(&mut self, action: (ActionType, String)) { self.explicit_deps.push(action); } /// Helper function over self.action. pub fn actions(&mut self, mut actions: Vec<(ActionType, String)>) { self.explicit_deps.append(&mut actions); } /// `handler` is called by `self.commit` after dependency resolution occurs. /// The boolean returned by `handler` is used to determine whether or not the /// actions added to this instance of `Aurum` should be applied. pub fn on_do_actions(&mut self, handler: fn(&Vec<(ActionType, &str)>) -> bool) { self.before_actions = Some(handler); } /// All the things happen here. It might take a little time. pub fn commit(mut self) -> Result<()> { info!("loading packages..."); let dbs = Alpm::new("/var/lib/pacman")?; let dbs = Rc::new(dbs); // This little step can increase the speed of dep resolution if self.explicit_deps.len() > 1 { debug!("Prepopulating cache with listed pkgs: {:?}", self.explicit_deps); let explicit_names = self.explicit_deps.iter() .map(|(_, pkgname)| pkgname ) // Update has an empty name .filter(|name| !name.is_empty() ) .cloned() .collect_vec(); self.config.cache.add(&explicit_names)?; } info!("resolving dependencies..."); let mut actions = { let config = &self.config; let explicit_deps = self.explicit_deps.drain(..) .map(|(action, pkgname)| { let pkg = Package::new(pkgname, dbs.clone(), config); pkg.map(|pkg| (action, pkg) ) }); let explicit_deps = process_results(explicit_deps, |iter| { iter .map(|(action, pkg)| { PkgAction { action: action, package: pkg } }) .collect_vec() })?; resolve_dependencies(&explicit_deps, &self.config, dbs.clone())? }; let mut actions = process_results(actions.drain(..), |iter| iter.collect::>() )?; /* use solvent::SolventError::*; match err { CycleDetected => { // I don't understand the behavior of the positioning/whatever of this error, // and I don't know if my code will even produce a dependency cycle ever. let msg = err_msg("Dependency cycle detected. Please inform aurum developers of your situation"); return Err(msg); }, NoSuchNode => { return Err(err_msg("Missing some node")); } } */ debug!("actions to complete: {:?}", actions); if let Some(func) = self.before_actions { let vec = actions.iter() .map(|action| (action.action.clone(), action.package.name()) ) .collect_vec(); if !(func(&vec)) { // Idk about this error message return Err(err_msg("process terminated by user")); } } let mut like_actions: Vec = Vec::new(); itertools::partition(actions.iter_mut(), |action| action.action.is_install_bin() ); for action in actions.drain(..) { // If we're not empty and action is different if !like_actions.is_empty() && (like_actions[0].action != action.action) { self.do_actions(&mut like_actions)?; } like_actions.push(action); } if !like_actions.is_empty() { self.do_actions(&mut like_actions)?; } Ok(()) } /// Consumes a list of actions, completing them as it consumes them. /// This function assumes that all arguments passed to it have the same /// action and that the list is not empty. fn do_actions(&mut self, actions: &mut Vec) -> Result<()> { match &actions[0].action { Fetch => { let pkgs: Vec = actions.drain(..) .map(|action| action.package.name().to_string() ) .collect(); self.config.fetch(pkgs)?; }, Build => for action in actions.drain(..) { self.config.build(action.package.name())?; }, Install(_) => { let (explicit, dependency) = actions.drain(..) .split(|action| match &action.action { Install(reason) => match reason { InstallReason::Explicit => true, InstallReason::Dependency => false, }, _ => true // assume explicit is safer than dependency }); let explicit: Vec = explicit .map(|action| action.package.name().to_string() ) .collect(); let dependency: Vec = dependency .map(|action| action.package.name().to_string() ) .collect(); // This order _shouldn't_ matter because of the way the dep solver batches // all the pkgs. self.config.install(&explicit, InstallReason::Explicit)?; self.config.install(&dependency, InstallReason::Dependency)?; }, InstallBin => { let pkgs: Vec = actions.drain(..) .map(|action| action.package.name().to_string() ) .collect(); Pacman::sync_pkgs(&pkgs)?; }, Update(_) => {} // Update doesn't actually do anything, just pulls deps } Ok(()) } } #[cfg(test)] mod test { use {Aurum, ActionType::*}; #[test] //#[ignore] fn test_new_api() { let test_config = format!("{}/tests/aurum.toml", env!("CARGO_MANIFEST_DIR")); let mut aurum = Aurum::new(Some(test_config)).unwrap(); aurum.actions(vec![ (Build, "pithos-git".to_string()) ]); aurum.commit().unwrap(); } #[test] fn desired_api() { let test_config = format!("{}/tests/aurum.toml", env!("CARGO_MANIFEST_DIR")); let _aurum = Aurum::new(Some(test_config)).unwrap(); } }