use crate::actions::{Actions, AurPackage, DepMissing, Missing, RepoPackage, Unneeded}; use crate::base::Base; use crate::cb::{Group, GroupCB, IsDevelCb, ProviderCB}; use crate::pkgbuild::PkgbuildRepo; use crate::satisfies::{Satisfies, satisfies_provide}; use crate::{AurBase, Error, Pkgbuild, PkgbuildPackages}; use std::collections::HashSet; use alpm::{Alpm, Dep, Depend, Version}; use alpm_utils::{AsTarg, DbListExt, Targ}; use bitflags::bitflags; use log::Level::Debug; use log::{debug, log_enabled}; use raur::{ArcPackage, Cache, Raur, SearchBy}; // TODO: pkgbuild repo will not bundle pkg specific deps, which means a package from a srcinfo // already in build may not actually already be fully satisfied. check for this and if not push it // as a new pkgbase enum RepoSource { Repo, Pkgbuild, Aur, Unspecified, Missing, } #[derive(Default)] struct Targets<'t, 'a> { repo: Vec<(Targ<'t>, &'a alpm::Package)>, pkgbuild: Vec>, aur: Vec<&'t str>, } bitflags! { /// Config options for Handle. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Flags: u32 { /// Do not resolve dependencies. const NO_DEPS = 1 << 2; /// Do not enforse version constraints on dependencies. const NO_DEP_VERSION = 1 << 3; /// Solve provides for targets. const TARGET_PROVIDES = 1 << 4; /// Solve provides for non targets. const NON_TARGET_PROVIDES = 1 << 5; /// Solve provides for missing packages. const MISSING_PROVIDES = 1 << 6; /// Solve provides in all instances. const PROVIDES = 1 << 7; /// Calculate which packages are only needed to build the packages. const CALCULATE_MAKE = 1 << 8; /// Solve checkdepends. const CHECK_DEPENDS = 1 << 10; /// Ignore targets that are up to date. const NEEDED = 1 << 11; /// Search PKGBUILD repos for upgrades and targets const PKGBUILDS = 1 << 12; /// Search aur for targets. const AUR = 1 << 13; /// Search alpm repos for targets. const REPO = 1 << 14; /// when fetching updates, also include packages that are older than locally installed. const ENABLE_DOWNGRADE = 1 << 15; /// Pull in pkgbuild dependencies even if they are already satisfied. const RESOLVE_SATISFIED_PKGBUILDS = 1 << 16; } } impl Flags { /// Create a new Flags with the default configuration pub fn new() -> Self { Flags::CALCULATE_MAKE | Flags::TARGET_PROVIDES | Flags::MISSING_PROVIDES | Flags::CHECK_DEPENDS | Flags::AUR | Flags::REPO | Flags::PKGBUILDS } /// Create a new Flags with repo targets disabled pub fn aur_only() -> Self { Flags::new() & !Flags::REPO } } impl Default for Flags { fn default() -> Self { Self::new() } } #[derive(Debug)] #[allow(dead_code)] enum AurOrPkgbuild<'a> { Aur(&'a raur::Package), Pkgbuild(&'a str, &'a srcinfo::Srcinfo, &'a srcinfo::Package), } impl<'a> AurOrPkgbuild<'a> { fn pkgbase(&self) -> &str { match self { AurOrPkgbuild::Aur(pkg) => &pkg.package_base, AurOrPkgbuild::Pkgbuild(_, base, _) => &base.base.pkgbase, } } fn depends(&self, arch: &str, check_depends: bool) -> Vec<&str> { match self { AurOrPkgbuild::Aur(pkg) => { let check = if check_depends { Some(&pkg.check_depends) } else { None }; pkg.make_depends .iter() .chain(check.into_iter().flatten()) .chain(&pkg.depends) .map(|s| s.as_str()) .collect() } AurOrPkgbuild::Pkgbuild(_, src, pkg) => { let base = &src.base; let check = if check_depends { Some(base.checkdepends.arch(arch)) } else { None }; base.makedepends .arch(arch) .chain(check.into_iter().flatten()) .chain(src.pkg.depends.arch(arch)) .chain(pkg.depends.arch(arch)) .collect() } } } } /// Resolver is the main type for resolving dependencies /// /// Given a list of targets of either repo or AUR packages it will resolve the dependencies needed /// to install them. /// /// This resolver assumes all the repo packages will be installed first, then each base is built /// and installed together. /// /// aur-depends will try to solve dependnecies using the minimum ammount of AUR RPC requests. /// /// Resolving is done via the AUR RPC. No packages are downloaded. /// /// # Example /// /// ```no_run /// # use aur_depends::Error; /// # #[tokio::test] /// # async fn run() -> Result<(), Error> { /// use std::collections::HashSet; /// use alpm::Alpm; /// use raur::Handle; /// /// use aur_depends::{Flags, Resolver}; /// /// let alpm = Alpm::new("/", "/var/lib/pacman")?; /// let raur = Handle::default(); /// let mut cache = HashSet::new(); /// let resolver = Resolver::new(&alpm, Vec::new(), &mut cache, &raur, Flags::aur()); /// let actions = resolver.resolve_targets(&["discord-canary", "spotify"]).await?; /// /// for install in &actions.install { /// println!("install: {}", install.pkg.name()) /// } /// /// for build in actions.iter_build_pkgs() { /// println!("build: {}", build.pkg.name) /// } /// /// # Ok (()) /// # } /// ``` #[derive(Debug)] pub struct Resolver<'a, 'b, H = raur::Handle> { pub(crate) alpm: &'a Alpm, pub(crate) repos: Vec>, pub(crate) resolved: HashSet, pub(crate) cache: &'b mut Cache, pub(crate) stack: Vec, pub(crate) raur: &'b H, pub(crate) actions: Actions<'a>, pub(crate) seen: HashSet, pub(crate) seen_target: HashSet, pub(crate) flags: Flags, pub(crate) aur_namespace: Option, pub(crate) provider_callback: ProviderCB, pub(crate) group_callback: GroupCB<'a>, pub(crate) is_devel: IsDevelCb, } impl<'a, 'b, E: std::error::Error + Sync + Send + 'static, H: Raur + Sync> Resolver<'a, 'b, H> { /// Create a new Resolver pub fn new(alpm: &'a Alpm, cache: &'b mut Cache, raur: &'b H, flags: Flags) -> Self { let actions = Actions { alpm, missing: Vec::new(), unneeded: Vec::new(), build: Vec::new(), install: Vec::new(), }; Resolver { alpm, repos: Vec::new(), resolved: HashSet::new(), cache, stack: Vec::new(), actions, raur, flags, seen: HashSet::new(), seen_target: HashSet::new(), aur_namespace: None, provider_callback: Default::default(), group_callback: Default::default(), is_devel: Default::default(), } } /// If enabled, causes `aur/foo` to mean from the AUR, instead of a repo named `aur`. pub fn aur_namespace(mut self, enable: bool) -> Self { if enable { self.aur_namespace = Some("aur".to_string()); } self } /// Causes `/foo` to mean from the AUR, instead of a repo named ``. pub fn custom_aur_namespace(mut self, name: Option) -> Self { self.aur_namespace = name; self } /// Set the pkgbuild repos to use. pub fn pkgbuild_repos(mut self, repos: Vec>) -> Self { self.repos = repos; self } /// Getter for the aur cache pub fn get_cache(&self) -> &Cache { self.cache } /// Mut getter for the aur cache pub fn get_cache_mut(&mut self) -> &mut Cache { self.cache } /// Resolve a list of targets. pub async fn resolve_targets(self, pkgs: &[T]) -> Result, Error> { self.resolve(pkgs, &[], true).await } /// Resolve a list of dependencies. pub async fn resolve_depends>( self, deps: &[T], make_deps: &[T], ) -> Result, Error> { self.resolve(deps, make_deps, false).await } // parse part of target and figure out where target is fn where_is_target(&self, targ: Targ) -> RepoSource { if let Some(repo) = targ.repo { if self.alpm.syncdbs().into_iter().any(|db| db.name() == repo) { RepoSource::Repo } else if self.repos.iter().any(|r| r.name == repo) { RepoSource::Pkgbuild } else if Some(repo) == self.aur_namespace.as_deref() { RepoSource::Aur } else { RepoSource::Missing } } else { RepoSource::Unspecified } } fn split_targets<'t, T: AsTarg>( &mut self, deps: &'t [T], make_deps: &'t [T], is_target: bool, ) -> Targets<'t, 'a> { let mut targets = Targets::default(); let use_repo = self.flags.contains(Flags::REPO) || !is_target; let use_pkgbuild = self.flags.contains(Flags::PKGBUILDS) || !is_target; let use_aur = self.flags.contains(Flags::AUR) || !is_target; 'deps: for targ in deps.iter().chain(make_deps) { let targ = targ.as_targ(); // TODO // Not handle repo/pkg for !is_target let dep = Depend::new(targ.to_string()); if !is_target && self.assume_installed(&dep) { continue; } let source = self.where_is_target(targ); if matches!(source, RepoSource::Repo | RepoSource::Unspecified) && use_repo { if let Some(alpm_pkg) = self.find_repo_target_satisfier(targ) { targets.repo.push((targ, alpm_pkg)); continue; } if is_target { let groups = self .alpm .syncdbs() .iter() .filter(|db| targ.repo.is_none() || targ.repo.unwrap() == db.name()) .filter_map(|db| db.group(targ.pkg).map(|group| Group { db, group }).ok()) .collect::>(); if !groups.is_empty() { if let Some(f) = self.group_callback.get() { for alpm_pkg in f(&groups) { targets.repo.push((targ, alpm_pkg)); } } else { for group in groups { for alpm_pkg in group.group.packages() { targets.repo.push((targ, alpm_pkg)); } } } continue; } } } if matches!(source, RepoSource::Pkgbuild | RepoSource::Unspecified) && use_pkgbuild { for repo in &self.repos { if targ.repo.is_some() && targ.repo != Some(repo.name) { continue; } for base in &repo.pkgs { if let Some(_satisfier) = base.which_satisfies_dep( &Depend::new(targ.pkg), self.flags.contains(Flags::NO_DEP_VERSION), ) { targets.pkgbuild.push(targ); continue 'deps; } } } } if matches!(source, RepoSource::Aur | RepoSource::Unspecified) && use_aur { targets.aur.push(targ.pkg); continue; } self.actions.missing.push(Missing { dep: targ.to_string(), stack: Vec::new(), }); } targets } async fn resolve( mut self, deps: &[T], make_deps: &[T], is_target: bool, ) -> Result, Error> { let targets = self.split_targets(deps, make_deps, is_target); let make = make_deps .iter() .map(|t| t.as_targ().pkg) .collect::>(); debug!("aur targets are {:?}", targets.aur); debug!("pkgbuild targets are {:?}", targets.pkgbuild); self.cache_aur_pkgs_recursive(&targets.aur, &targets.pkgbuild, true) .await?; self.resolved.clear(); debug!("Caching done, building tree"); debug!("cache: {:#?}", self.cache); for (pkg, alpm_pkg) in targets.repo { self.resolve_repo_target(pkg, alpm_pkg, &make, is_target) } for &pkg in &targets.pkgbuild { self.resolve_pkgbuild_target(pkg, &make, is_target, &targets.aur)?; } for &aur_pkg in &targets.aur { self.resolve_aur_target(aur_pkg, &make, is_target, &targets.aur)?; } if self.flags.contains(Flags::CALCULATE_MAKE) { self.calculate_make(); } Ok(self.actions) } fn resolve_aur_target( &mut self, aur_pkg: &str, make: &HashSet<&str>, is_target: bool, targs: &[&str], ) -> Result<(), Error> { let dep = Depend::new(aur_pkg); let localdb = self.alpm.localdb(); self.seen_target.insert(aur_pkg.to_string()); if self.should_skip_aur_pkg(&dep, is_target) { return Ok(()); } let pkg = if let Some(pkg) = self.select_satisfier_aur_cache(&dep, is_target) { pkg.clone() } else { self.actions.missing.push(Missing { dep: dep.to_string(), stack: self.stack.clone(), }); return Ok(()); }; if self.flags.contains(Flags::NEEDED) || !is_target { let is_devel = self.is_devel.get().map(|f| f(aur_pkg)).unwrap_or(false); if !is_devel && let Ok(local) = localdb.pkg(&*pkg.name) && local.version() >= Version::new(&*pkg.version) { let unneeded = Unneeded::new(aur_pkg.to_string(), local.version().to_string()); self.actions.unneeded.push(unneeded); return Ok(()); } } let is_make = make.contains(&aur_pkg); self.stack .push(DepMissing::new(pkg.name.to_string(), aur_pkg.to_string())); self.resolve_aur_pkg_deps(targs, AurOrPkgbuild::Aur(&pkg), is_make)?; self.stack.pop().unwrap(); if self.actions.iter_aur_pkgs().any(|p| p.pkg.name == pkg.name) { return Ok(()); } if self .actions .iter_pkgbuilds() .any(|p| p.1.pkg.pkgname == pkg.name) { return Ok(()); } let p = AurPackage { pkg: pkg.clone(), make: false, target: is_target, }; self.push_aur_build(&pkg.package_base, p); Ok(()) } fn resolve_repo_target( &mut self, pkg: Targ, alpm_pkg: &'a alpm::Package, make: &HashSet<&str>, is_target: bool, ) { let localdb = self.alpm.localdb(); if !is_target && localdb.pkgs().find_satisfier(pkg.pkg).is_some() { return; } if self.flags.contains(Flags::NEEDED) && let Ok(local) = localdb.pkg(alpm_pkg.name()) && local.version() >= alpm_pkg.version() { let unneeded = Unneeded::new(pkg.to_string(), local.version().to_string()); self.actions.unneeded.push(unneeded); return; } let is_make = make.contains(&pkg.pkg); self.stack.push(DepMissing::new( alpm_pkg.name().to_string(), pkg.pkg.to_string(), )); self.resolve_repo_pkg(alpm_pkg, is_target, is_make); self.stack.pop().unwrap(); } fn resolve_pkgbuild_target( &mut self, pkgbuild: Targ, make: &HashSet<&str>, is_target: bool, targs: &[&str], ) -> Result<(), Error> { let dep = Depend::new(pkgbuild.pkg); let localdb = self.alpm.localdb(); if self.should_skip_aur_pkg(&dep, is_target) { return Ok(()); } let (repo, base, pkg) = if let Some((repo, base, pkg)) = self.find_pkgbuild_repo_dep(pkgbuild.repo, &dep) { (repo, base, pkg) } else { self.actions.missing.push(Missing { dep: dep.to_string(), stack: self.stack.clone(), }); return Ok(()); }; if self.flags.contains(Flags::NEEDED) || !is_target { let is_devel = self .is_devel .get() .map(|f| f(pkgbuild.pkg)) .unwrap_or(false); if !is_devel && let Ok(local) = localdb.pkg(&*pkg.pkgname) && local.version() >= Version::new(base.version()) { let unneeded = Unneeded::new(pkgbuild.to_string(), local.version().to_string()); self.actions.unneeded.push(unneeded); return Ok(()); } } let base = base.clone(); let pkg = pkg.clone(); let repo = repo.to_string(); let is_make = make.contains(&pkgbuild.pkg); self.stack.push(DepMissing::new( pkg.pkgname.to_string(), pkgbuild.pkg.to_string(), )); self.resolve_aur_pkg_deps(targs, AurOrPkgbuild::Pkgbuild(&repo, &base, &pkg), is_make)?; self.stack.pop().unwrap(); if self .actions .iter_aur_pkgs() .any(|p| p.pkg.name == pkg.pkgname) { return Ok(()); } if self .actions .iter_pkgbuilds() .any(|p| p.1.pkg.pkgname == pkg.pkgname) { return Ok(()); } let p = Pkgbuild { pkg: pkg.clone(), make: false, target: is_target, }; self.push_pkgbuild_build(repo.to_string(), base, p); Ok(()) } fn find_satisfier_aur_cache(&self, dep: &Dep) -> Option<&ArcPackage> { if let Some(pkg) = self .cache .iter() .filter(|pkg| self.alpm.localdb().pkg(pkg.name.as_str()).is_ok()) .find(|pkg| pkg.satisfies_dep(dep, self.flags.contains(Flags::NO_DEP_VERSION))) { return Some(pkg); } if let Some(pkg) = self.cache.get(dep.name()) && pkg.satisfies_dep(dep, self.flags.contains(Flags::NO_DEP_VERSION)) { return Some(pkg); } self.cache .iter() .find(|pkg| pkg.satisfies_dep(dep, self.flags.contains(Flags::NO_DEP_VERSION))) } /// Expected behaviour /// pull in a list of all matches, if one is installed, default to it. /// unless we are looking for a target, then always show all options. fn select_satisfier_aur_cache(&self, dep: &Dep, target: bool) -> Option<&ArcPackage> { debug!("select satisfier: {}", dep); if let Some(f) = self.provider_callback.get() { let mut pkgs = self .cache .iter() .filter(|pkg| pkg.satisfies_dep(dep, self.flags.contains(Flags::NO_DEP_VERSION))) .map(|pkg| pkg.name.as_str()) .collect::>(); debug!("satisfiers for '{:?}': {:?})", dep.to_string(), pkgs); if !target { if let Some(pkg) = pkgs.iter().find(|&&p| self.alpm.localdb().pkg(p).is_ok()) { debug!("picked from cache: {}", pkg); return self.cache.get(*pkg); } if let Some(pkg) = pkgs.iter().find(|&&p| p == dep.name()) { debug!("picked from cache: {}", pkg); return self.cache.get(*pkg); } } if pkgs.len() == 1 { return self.cache.get(pkgs[0]); } else if pkgs.is_empty() { return None; } if !target { for &pkg in &pkgs { if self.alpm.localdb().pkg(pkg).is_ok() { return self.cache.get(pkg); } } } if let Some(true_pkg) = pkgs.iter().position(|pkg| *pkg == dep.name()) { pkgs.swap(true_pkg, 0); pkgs[1..].sort_unstable(); } else { pkgs.sort_unstable(); } let choice = f(dep.to_string().as_str(), &pkgs); debug!("choice was: {}={}", choice, pkgs[choice]); self.cache.get(pkgs[choice]) } else { debug!("no provider callback"); self.find_satisfier_aur_cache(dep) } } fn resolve_aur_pkg_deps( &mut self, targs: &[&str], pkg: AurOrPkgbuild, make: bool, ) -> Result<(), Error> { debug!("resolve pkgbuild repo pkg deps: {}", pkg.pkgbase()); let nover = self.flags.contains(Flags::NO_DEP_VERSION); let check = self.flags.contains(Flags::CHECK_DEPENDS); if !self.flags.contains(Flags::NO_DEPS) { for dep_str in pkg.depends(self.arch(), check) { debug!("depend: {}", dep_str); let dep = Depend::new(dep_str.to_string()); if self.assume_installed(&dep) || self.satisfied_build(&dep) || self.resolved.contains(&dep.to_string()) || self.satisfied_install(&dep) { continue; } let same_pkgbuild = matches!(pkg, AurOrPkgbuild::Pkgbuild(_, base, _) if base .which_satisfies_dep(&dep, nover) .is_some() && !self.pkgbuild_dep_in_base(&dep.to_string(), base, self.arch(), check)); let is_aur_targ = !same_pkgbuild && self.dep_is_aur_targ(targs, &dep); self.resolved.insert(dep.to_string()); if !is_aur_targ && !same_pkgbuild { if !self.flags.contains(Flags::RESOLVE_SATISFIED_PKGBUILDS) && self.satisfied_local(&dep) { continue; } let depstr = dep.to_string(); if let Some(pkg) = self.find_repo_satisfier(&depstr) { if !self.satisfied_local(&dep) { self.stack .push(DepMissing::new(pkg.name().to_string(), depstr)); self.resolve_repo_pkg(pkg, false, true); self.stack.pop().unwrap(); } continue; } } if !same_pkgbuild && self.should_skip_aur_pkg(&dep, is_aur_targ) { continue; } if let Some((repo, base, pkg)) = self.find_pkgbuild_repo_dep(None, &dep) { let repo = repo.to_string(); let base = base.clone(); let pkg = pkg.clone(); self.stack .push(DepMissing::new(pkg.pkgname.to_string(), dep.to_string())); self.resolve_aur_pkg_deps( targs, AurOrPkgbuild::Pkgbuild(&repo, &base, &pkg), true, )?; self.stack.pop(); let pkg = Pkgbuild { pkg, make, target: false, }; self.push_pkgbuild_build(repo.to_string(), base, pkg); continue; } let sat_pkg = if let Some(pkg) = self.select_satisfier_aur_cache(&dep, false) { pkg.clone() } else { debug!("failed to find '{}' in pkgbuild repo or aur cache", dep,); if log_enabled!(Debug) { debug!( "at time of failure pkgcache is: {:?}\n", self.cache.iter().map(|p| &p.name).collect::>() ); debug!("stack is: {:?}", self.stack); } self.actions.missing.push(Missing { dep: dep.to_string(), stack: self.stack.clone(), }); continue; }; self.stack .push(DepMissing::new(sat_pkg.name.to_string(), dep.to_string())); self.resolve_aur_pkg_deps(targs, AurOrPkgbuild::Aur(&sat_pkg), true)?; self.stack.pop(); let p = AurPackage { pkg: sat_pkg.clone(), make, target: false, }; self.push_aur_build(&sat_pkg.package_base, p); } } Ok(()) } fn find_pkgbuild_repo_dep( &self, repo_targ: Option<&str>, dep: &Depend, ) -> Option<(&str, &srcinfo::Srcinfo, &srcinfo::Package)> { for repo in &self.repos { if repo_targ.is_some() && Some(repo.name) != repo_targ { continue; } for base in &repo.pkgs { if let Some(pkg) = base.which_satisfies_dep(dep, self.flags.contains(Flags::NO_DEP_VERSION)) { return Some((repo.name, base, base.pkg(pkg).unwrap())); } } } None } fn should_skip_aur_pkg(&self, dep: &Depend, is_target: bool) -> bool { if is_target { return false; } if self.flags.contains(Flags::RESOLVE_SATISFIED_PKGBUILDS) { return false; } if self.assume_installed(dep) { return true; } if self.satisfied_local(dep) { return true; } false } fn resolve_repo_pkg(&mut self, pkg: &'a alpm::Package, target: bool, make: bool) { if !self.seen.insert(pkg.name().to_string()) { return; } if !self.flags.contains(Flags::NO_DEPS) { for dep in pkg.depends() { if self.satisfied_install(dep) || self.satisfied_local(dep) || self.assume_installed(dep) { continue; } if let Some(pkg) = self.find_repo_satisfier(dep.to_string()) { self.stack .push(DepMissing::new(pkg.name().to_string(), dep.to_string())); self.resolve_repo_pkg(pkg, false, true); self.stack.pop().unwrap(); } else { self.actions.missing.push(Missing { dep: dep.to_string(), stack: self.stack.clone(), }); } } } debug!("pushing to install: {}", pkg.name()); self.actions.install.push(RepoPackage { pkg, make, target }); } async fn cache_aur_pkgs>( &mut self, pkgs: &[S], target: bool, ) -> Result, Error> { let mut pkgs_nover = pkgs .iter() .map(|p| p.as_ref().split(is_ver_char).next().unwrap()) .collect::>(); pkgs_nover.sort_unstable(); pkgs_nover.dedup(); if (self.flags.contains(Flags::PROVIDES)) || (target && self.flags.contains(Flags::TARGET_PROVIDES)) { self.cache_provides(&pkgs_nover).await } else { let mut info = self .raur .cache_info(self.cache, &pkgs_nover) .await .map_err(|e| Error::Raur(Box::new(e)))?; if (self.flags.contains(Flags::PROVIDES)) || (self.flags.contains(Flags::MISSING_PROVIDES)) { let missing = pkgs .iter() .map(|pkg| Depend::new(pkg.as_ref())) .filter(|dep| { !info.iter().any(|info| { info.satisfies_dep(dep, self.flags.contains(Flags::NO_DEP_VERSION)) }) }) .map(|dep| dep.to_string()) .collect::>(); if !missing.is_empty() { debug!("attempting to find provides for missing: {:?}", missing); info.extend(self.cache_provides(&missing).await?); } } if log_enabled!(Debug) { debug!( "provides resolved {:?} found {:?}", pkgs.iter().map(AsRef::as_ref).collect::>(), info.iter().map(|p| p.name.clone()).collect::>() ); } Ok(info) } } async fn cache_provides>( &mut self, pkgs: &[S], ) -> Result, Error> { let mut to_info = pkgs .iter() .map(|s| s.as_ref().to_string()) .collect::>(); debug!("cache args: {:?}\n", to_info); for pkg in pkgs { let pkg = pkg.as_ref().split(is_ver_char).next().unwrap(); // Optimization, may break with alpm repos disabled // for example, trying to resolve "pacman" with aur only should pull in // "pacman-git". But because pacman is installed locally, this optimization // causes us to not cache "pacman-git" and end up with missing. // // TODO: maybe check for local && not sync if self.alpm.localdb().pkg(pkg).is_ok() { continue; } to_info.extend( self.raur .search_by(pkg, SearchBy::Provides) .await .unwrap_or_else(|e| { debug!("provide search '{}' failed: {}", pkg, e); Vec::new() }) .into_iter() .map(|p| p.name), ); } to_info.sort(); to_info.dedup(); debug!("trying to cache {:?}\n", to_info); let mut ret = self .raur .cache_info(self.cache, &to_info) .await .map_err(|e| Error::Raur(Box::new(e)))?; ret.retain(|pkg| { pkgs.iter().any(|dep| { pkg.satisfies_dep( &Depend::new(dep.as_ref()), self.flags.contains(Flags::NO_DEP_VERSION), ) }) }); Ok(ret) } async fn cache_aur_pkgs_recursive>( &mut self, pkgs: &[S], pkgbuilds: &[Targ<'_>], target: bool, ) -> Result<(), Error> { let mut new_pkgs = self .cache_aur_pkgs_recursive2(pkgs, pkgbuilds, target) .await?; while !new_pkgs.is_empty() { let (pkgbuild_pkgs, pkgs): (Vec<_>, Vec<_>) = new_pkgs.into_iter().partition(|p| { self.find_pkgbuild_repo_dep(None, &Depend::new(p.as_str())) .is_some() }); let pkgbuild_pkgs = pkgbuild_pkgs .iter() .map(|p| Targ::new(None, p)) .collect::>(); new_pkgs = self .cache_aur_pkgs_recursive2(&pkgs, &pkgbuild_pkgs, false) .await?; } Ok(()) } fn find_aur_deps_of_pkgbuild(&mut self, targ: Targ<'_>) -> Vec { let mut ret = Vec::new(); if self.flags.contains(Flags::NO_DEPS) { return Vec::new(); } let mut new_resolved = HashSet::new(); let mut pkgbuilds = Vec::new(); let pkg = Depend::new(targ.pkg); if let Some((repo, base, pkg)) = self.find_pkgbuild_repo_dep(targ.repo, &pkg) { let pkgbuild = AurOrPkgbuild::Pkgbuild(repo, base, pkg); let deps = pkgbuild.depends(self.arch(), self.flags.contains(Flags::CHECK_DEPENDS)); for pkg in deps { let dep = Depend::new(pkg); if self.resolved.contains(&dep.to_string()) { continue; } if self.find_repo_satisfier_silent(pkg).is_some() { continue; } if !self.flags.contains(Flags::RESOLVE_SATISFIED_PKGBUILDS) { if self.satisfied_local(&dep) { continue; } } else if self.assume_installed(&dep) { continue; } new_resolved.insert(dep.to_string()); if self.find_pkgbuild_repo_dep(None, &dep).is_some() { pkgbuilds.push(dep.to_depend()); continue; } ret.push(pkg.to_string()); } } self.resolved.extend(new_resolved); for dep in pkgbuilds { ret.extend(self.find_aur_deps_of_pkgbuild(Targ::new(None, &dep.to_string()))); } ret } async fn cache_aur_pkgs_recursive2>( &mut self, pkgs: &[S], pkgbuild_pkgs: &[Targ<'_>], target: bool, ) -> Result, Error> { let pkgs = pkgbuild_pkgs .iter() .flat_map(|p| self.find_aur_deps_of_pkgbuild(*p)) .chain(pkgs.iter().map(|p| p.as_ref().to_string())) .collect::>(); if pkgs.is_empty() { return Ok(Vec::new()); } if log_enabled!(Debug) { debug!( "cache_aur_pkgs_recursive {:?}", pkgs.iter().collect::>() ) } let pkgs = self.cache_aur_pkgs(&pkgs, target).await?; if self.flags.contains(Flags::NO_DEPS) { return Ok(Vec::new()); } let mut new_pkgs = Vec::new(); for pkg in pkgs { let check = if self.flags.contains(Flags::CHECK_DEPENDS) { Some(&pkg.check_depends) } else { None }; let depends = pkg .depends .iter() .chain(&pkg.make_depends) .chain(check.into_iter().flatten()); for pkg in depends { let dep = Depend::new(pkg.as_str()); if self.resolved.contains(&dep.to_string()) { continue; } if self.find_repo_satisfier_silent(pkg).is_some() { continue; } if !self.flags.contains(Flags::RESOLVE_SATISFIED_PKGBUILDS) { if self.satisfied_local(&dep) { continue; } } else if self.assume_installed(&dep) { continue; } self.resolved.insert(dep.to_string()); new_pkgs.push(pkg.clone()); } } Ok(new_pkgs) } fn satisfied_build(&self, target: &Dep) -> bool { for build in &self.actions.build { match build { Base::Aur(pkgs) => { if pkgs.pkgs.iter().any(|b| { b.pkg .satisfies_dep(target, self.flags.contains(Flags::NO_DEP_VERSION)) }) { return true; } } Base::Pkgbuild(pkgs) => { if pkgs.pkgs.iter().any(|pkg| { (&*pkgs.srcinfo, &pkg.pkg) .satisfies_dep(target, self.flags.contains(Flags::NO_DEP_VERSION)) }) { return true; } } } } false } fn satisfied_install(&self, target: &Dep) -> bool { self.actions.install.iter().any(|install| { install .pkg .satisfies_dep(target, self.flags.contains(Flags::NO_DEP_VERSION)) }) } fn satisfied_local(&self, target: &Dep) -> bool { if let Ok(pkg) = self.alpm.localdb().pkg(target.name()) && pkg.satisfies_dep(target, self.flags.contains(Flags::NO_DEP_VERSION)) { return true; } if self.flags.contains(Flags::NO_DEP_VERSION) { let ret = self.alpm.localdb().pkgs().find_satisfier(target.name()); ret.is_some() } else { let ret = self .alpm .localdb() .pkgs() .find_satisfier(target.to_string()); ret.is_some() } } fn find_repo_target_satisfier(&self, mut target: Targ) -> Option<&'a alpm::Package> { if self.flags.contains(Flags::NO_DEP_VERSION) { target = Targ { repo: target.repo, pkg: target .pkg .split_once(is_ver_char) .map_or(target.pkg, |x| x.0), }; } self.alpm.syncdbs().find_target_satisfier(target) } fn find_repo_satisfier>(&self, target: S) -> Option<&'a alpm::Package> { let mut target = target.as_ref(); if self.flags.contains(Flags::NO_DEP_VERSION) { target = target.split_once(is_ver_char).map_or(target, |x| x.0) } self.alpm.syncdbs().find_satisfier(target) } fn find_repo_satisfier_silent>(&self, target: S) -> Option<&'a alpm::Package> { let cb = self.alpm.take_raw_question_cb(); let pkg = self.find_repo_satisfier(target); self.alpm.set_raw_question_cb(cb); pkg } fn dep_is_aur_targ(&self, targs: &[&str], dep: &Dep) -> bool { if let Some(pkg) = self.find_satisfier_aur_cache(dep) { for &targ in targs { if self.seen_target.contains(targ) { continue; } if pkg.satisfies_dep( &Depend::new(targ), self.flags.contains(Flags::NO_DEP_VERSION), ) { return true; } } } false } fn pkgbuild_dep_in_base( &self, dep: &str, base: &srcinfo::Srcinfo, arch: &str, check_depends: bool, ) -> bool { let check = if check_depends { &*base.base.checkdepends } else { &[] }; // TODO consider runtime deps? // TODO doing this would involve pulling in these deps at the start of resolution base.base .makedepends .iter() .chain(check) .filter(|v| v.supports(arch)) .flat_map(|d| d.values()) .any(|d| d == dep) } fn push_aur_build(&mut self, pkgbase: &str, pkg: AurPackage) { debug!("pushing to build: {}", pkg.pkg.name); let mut build = true; if let Some(Base::Aur(base)) = self.actions.build.last_mut() && base.package_base() == pkgbase { base.pkgs.push(pkg); return; } for base in self.actions.build.iter_mut() { if let Base::Aur(pkgs) = base && pkgs.pkgs[0].pkg.package_base == pkgbase { build = false; break; } } self.actions.build.push(Base::Aur(AurBase { pkgs: vec![pkg], build, })); } // TODO: multiple packages may have same pkgbase fn push_pkgbuild_build(&mut self, repo: String, base: srcinfo::Srcinfo, pkg: Pkgbuild) { debug!("pushing to build: {}", pkg.pkg.pkgname); let mut b = true; if let Some(Base::Pkgbuild(b)) = self.actions.build.last_mut() && b.package_base() == base.base.pkgbase { b.pkgs.push(pkg); return; } for build in self.actions.build.iter_mut() { if let Base::Pkgbuild(pkgs) = build && pkgs.srcinfo.base.pkgbase == base.base.pkgbase { b = false; break; } } self.actions.build.push(Base::Pkgbuild(PkgbuildPackages { repo, srcinfo: Box::new(base), pkgs: vec![pkg], build: b, })); } pub(crate) fn find_pkgbuild( &self, name: &str, ) -> Option<(&'a str, &'a srcinfo::Srcinfo, &'a srcinfo::Package)> { for repo in &self.repos { for &srcinfo in &repo.pkgs { for pkg in &srcinfo.pkgs { if pkg.pkgname == name { return Some((repo.name, srcinfo, pkg)); } } } } None } pub(crate) fn is_pkgbuild(&self, name: &str) -> bool { self.find_pkgbuild(name).is_some() } fn calculate_make(&mut self) { let mut runtime = Vec::new(); let mut run = true; let no_dep_ver = self.flags.contains(Flags::NO_DEP_VERSION); let arch = self.arch().to_string(); self.actions .install .iter() .filter(|p| !p.make) .for_each(|p| runtime.extend(p.pkg.depends().iter().map(|d| d.to_depend()))); self.actions .iter_aur_pkgs() .filter(|p| !p.make) .for_each(|p| runtime.extend(p.pkg.depends.iter().map(|d| Depend::new(d.as_str())))); self.actions .iter_pkgbuilds() .filter(|p| !p.1.make) .for_each(|p| runtime.extend(p.1.pkg.depends.arch(&arch).map(Depend::new))); runtime.sort_unstable_by_key(|a| a.to_string()); runtime.dedup_by_key(|a| a.to_string()); while run { run = false; for pkg in &mut self.actions.install { if !pkg.make { continue; } let satisfied = runtime .iter() .any(|dep| pkg.pkg.satisfies_dep(dep, no_dep_ver)); if satisfied { pkg.make = false; run = true; runtime.extend(pkg.pkg.depends().iter().map(|d| d.to_depend())); } } for base in &mut self.actions.build { match base { Base::Aur(base) => { for pkg in &mut base.pkgs { if !pkg.make { continue; } let satisfied = runtime .iter() .any(|dep| pkg.pkg.satisfies_dep(dep, no_dep_ver)); if satisfied { pkg.make = false; run = true; runtime.extend( pkg.pkg.depends.iter().map(|d| Depend::new(d.as_str())), ); } } } Base::Pkgbuild(pkgs) => { for pkg in &mut pkgs.pkgs { if !pkg.make { continue; } let satisfied = runtime.iter().any(|dep| { (&*pkgs.srcinfo, &pkg.pkg).satisfies_dep(dep, no_dep_ver) }); if satisfied { pkg.make = false; run = true; runtime.extend(pkg.pkg.depends.arch(&arch).map(Depend::new)); } } } } } } } fn assume_installed(&self, dep: &Dep) -> bool { let nover = self.flags.contains(Flags::NO_DEP_VERSION); self.alpm .assume_installed() .iter() .any(|assume| satisfies_provide(dep, assume, nover)) } fn arch(&self) -> &str { // Assume first alpm arch is our makepkg carch because we can't parse the makepkg config // there's always one configured realisticly. If there's not this resolves to empty string // and then the call to ArchVecs::arch will just give us the any fields and this still works // even if it's not ideal. // TODO make the user explicitly configure an arch and pawn the problem off. self.alpm.architectures().first().unwrap_or_default() } } fn is_ver_char(c: char) -> bool { matches!(c, '<' | '=' | '>') } #[cfg(test)] mod tests { use super::*; use crate::Conflict; use crate::tests::*; use alpm::SigLevel; use simplelog::{ColorChoice, ConfigBuilder, LevelFilter, TermLogger, TerminalMode}; struct TestActions { build: Vec, install: Vec, missing: Vec>, make: usize, duplicates: Vec, targets: Vec, } fn _init_logger() { let _ = TermLogger::init( LevelFilter::Trace, ConfigBuilder::new() .add_filter_allow_str("aur_depends") .build(), TerminalMode::Stderr, ColorChoice::Never, ); } fn alpm() -> Alpm { //let handle = Alpm::new("/", "/var/lib/pacman/").unwrap(); let mut handle = Alpm::new("/", "tests/db").unwrap(); handle.register_syncdb("core", SigLevel::NONE).unwrap(); handle.register_syncdb("extra", SigLevel::NONE).unwrap(); handle.register_syncdb("community", SigLevel::NONE).unwrap(); handle.register_syncdb("multilib", SigLevel::NONE).unwrap(); handle .add_assume_installed(&Depend::new("assume-dep1")) .unwrap(); handle.add_assume_installed(&Depend::new("i3-wm")).unwrap(); handle } async fn resolve(pkgs: &[&str], flags: Flags) -> TestActions { //_init_logger(); let raur = raur(); let alpm = alpm(); let mut cache = HashSet::new(); let srcinfo = srcinfo::Srcinfo::from_path("tests/srcinfo/custom.SRCINFO").unwrap(); let srcinfo = vec![&srcinfo]; let repo = vec![PkgbuildRepo { name: "my_repo", pkgs: srcinfo, }]; let handle = Resolver::new(&alpm, &mut cache, &raur, flags) .pkgbuild_repos(repo) .aur_namespace(true) .provider_callback(|_, pkgs| { debug!("provider choice: {:?}", pkgs); pkgs.iter() .position(|pkg| *pkg == "yay-bin") .unwrap_or_default() }); let actions = handle.resolve_targets(pkgs).await.unwrap(); let mut build = actions .iter_aur_pkgs() .map(|p| p.pkg.name.clone()) .collect::>(); build.extend(actions.iter_pkgbuilds().map(|p| p.1.pkg.pkgname.clone())); let mut install = actions .install .iter() .map(|b| b.pkg.name().to_string()) .collect::>(); build.sort(); install.sort(); let make = actions.install.iter().filter(|i| i.make).count() + actions.iter_aur_pkgs().filter(|i| i.make).count() + actions.iter_pkgbuilds().filter(|i| i.1.make).count(); let mut targets = actions .iter_aur_pkgs() .filter(|pkg| pkg.target) .map(|pkg| pkg.pkg.name.to_string()) .collect::>(); targets.extend( actions .iter_pkgbuilds() .filter(|pkg| pkg.1.target) .map(|pkg| pkg.1.pkg.pkgname.to_string()), ); TestActions { duplicates: actions.duplicate_targets(), install, build, missing: actions .missing .into_iter() .map(|m| { m.stack .into_iter() .map(|s| s.pkg) .chain(Some(m.dep)) .collect() }) .collect(), make, targets, } } #[tokio::test] async fn test_yay() { let TestActions { install, build, make, .. } = resolve(&["yay"], Flags::new()).await; assert_eq!(build, vec!["yay-bin"]); assert_eq!( install, vec!["git", "go", "perl-error", "perl-mailtools", "perl-timedate"] ); assert_eq!(make, 1); } #[tokio::test] async fn test_yay_needed() { let TestActions { install, build, .. } = resolve(&["yay"], Flags::new() | Flags::NEEDED).await; assert_eq!(build, vec!["yay-bin"]); assert_eq!( install, vec!["git", "go", "perl-error", "perl-mailtools", "perl-timedate"] ); } #[tokio::test] async fn test_yay_no_deps() { let TestActions { install, build, .. } = resolve(&["yay"], Flags::new() | Flags::NO_DEPS).await; assert_eq!(build, vec!["yay-bin"]); assert_eq!(install, Vec::::new()); } #[tokio::test] async fn test_aur_yay_no_deps() { let TestActions { install, build, .. } = resolve(&["aur/yay"], Flags::new() | Flags::NO_DEPS).await; assert_eq!(build, vec!["yay-bin"]); assert_eq!(install, Vec::::new()); } #[tokio::test] async fn test_core_yay_no_deps() { let TestActions { install, build, .. } = resolve(&["core/yay"], Flags::new() | Flags::NO_DEPS).await; assert_eq!(build, Vec::::new()); assert_eq!(install, Vec::::new()); } #[tokio::test] async fn test_core_glibc_no_deps() { let TestActions { install, build, .. } = resolve(&["core/glibc"], Flags::new() | Flags::NO_DEPS).await; assert_eq!(build, Vec::::new()); assert_eq!(install, vec!["glibc"]); } #[tokio::test] async fn test_aur_glibc_no_deps() { let TestActions { install, build, .. } = resolve(&["core/yay"], Flags::new() | Flags::NO_DEPS).await; assert_eq!(build, Vec::::new()); assert_eq!(install, Vec::::new()); } #[tokio::test] async fn test_extra_glibc_no_deps() { let TestActions { install, build, .. } = resolve(&["core/yay"], Flags::new() | Flags::NO_DEPS).await; assert_eq!(build, Vec::::new()); assert_eq!(install, Vec::::new()); } #[tokio::test] async fn test_yay_no_provides() { let TestActions { install, build, .. } = resolve(&["yay"], Flags::new() & !Flags::TARGET_PROVIDES).await; assert_eq!(build, vec!["yay"]); assert_eq!( install, vec!["git", "go", "perl-error", "perl-mailtools", "perl-timedate"] ); } #[tokio::test] async fn test_make_only() { let TestActions { make, .. } = resolve( &["ros-melodic-desktop-full"], Flags::new() & !Flags::TARGET_PROVIDES & !Flags::MISSING_PROVIDES, ) .await; assert_eq!(make, 40); } #[tokio::test] async fn test_cache_only() { let raur = raur(); let alpm = alpm(); let mut cache = HashSet::new(); let mut handle = Resolver::new(&alpm, &mut cache, &raur, Flags::new()); handle .cache_aur_pkgs_recursive(&["ros-melodic-desktop-full"], &[], true) .await .unwrap(); } #[tokio::test] async fn test_pacaur() { let TestActions { install, build, .. } = resolve(&["pacaur"], Flags::new()).await; assert_eq!(build, vec!["auracle-git", "pacaur"]); assert_eq!( install, vec![ "git", "jq", "libnsl", "meson", "ninja", "oniguruma", "perl-error", "perl-mailtools", "perl-timedate", "python", "python-appdirs", "python-packaging", "python-pyparsing", "python-setuptools", "python-six" ] ); } #[tokio::test] async fn test_wants_pacaur() { let TestActions { build, .. } = resolve(&["wants-pacaur"], Flags::new()).await; assert_eq!(build, vec!["wants-pacaur"]); } #[tokio::test] async fn test_wants_pacaur_force_deps() { let TestActions { build, .. } = resolve( &["wants-pacaur"], Flags::new() | Flags::RESOLVE_SATISFIED_PKGBUILDS, ) .await; assert_eq!(build, vec!["auracle-git", "pacaur", "wants-pacaur"]); } #[tokio::test] async fn test_pkgbuild() { let TestActions { install, build, .. } = resolve(&["custom"], Flags::new()).await; assert_eq!(build, vec!["c1", "c2", "c3", "custom"]); assert_eq!(install, vec!["libedit", "llvm-libs", "rust"]); } #[tokio::test] async fn test_assume() { let TestActions { install, build, .. } = resolve(&["assume-test"], Flags::new()).await; assert_eq!(build, vec!["assume-dep2", "assume-test"]); assert_eq!(install, vec!["libev"]); } #[tokio::test] async fn test_pacaur_needed() { let TestActions { install, build, .. } = resolve(&["pacaur"], Flags::new() | Flags::NEEDED).await; assert_eq!(build, Vec::::new()); assert_eq!(install, Vec::::new()); } #[tokio::test] async fn test_many() { let TestActions { install, build, .. } = resolve( &["yay", "pacaur", "pacman", "glibc", "0ad", "spotify"], Flags::new() | Flags::NO_DEPS, ) .await; assert_eq!(build, vec!["pacaur", "spotify", "yay-bin"]); assert_eq!(install, vec!["0ad", "glibc", "pacman"]); } #[tokio::test] async fn test_many_needed() { let TestActions { install, build, .. } = resolve( &["yay", "pacaur", "pacman", "glibc", "0ad", "spotify"], Flags::new() | Flags::NO_DEPS | Flags::NEEDED, ) .await; assert_eq!(build, vec!["spotify", "yay-bin"]); assert_eq!(install, vec!["0ad", "glibc"]); } #[tokio::test] async fn test_override_dep_via_target() { let TestActions { install, build, .. } = resolve(&["perl-mailtools"], Flags::new()).await; assert_eq!(install, ["perl-mailtools", "perl-timedate"]); assert!(build.is_empty()); let TestActions { install, build, .. } = resolve(&["perl-mailtools-git"], Flags::new()).await; assert_eq!(install, ["perl-timedate"]); assert_eq!(build, ["perl-mailtools-git"]); let TestActions { install, build, .. } = resolve(&["perl-timedate-git"], Flags::new()).await; assert!(install.is_empty()); assert_eq!(build, ["perl-timedate-git"]); let TestActions { install, build, .. } = resolve(&["perl-timedate-git", "perl-mailtools-git"], Flags::new()).await; assert!(install.is_empty()); assert_eq!(build, ["perl-mailtools-git", "perl-timedate-git"]); let TestActions { install, build, .. } = resolve(&["perl-mailtools-git", "perl-timedate-git"], Flags::new()).await; assert!(install.is_empty()); assert_eq!(build, ["perl-mailtools-git", "perl-timedate-git"]); } #[tokio::test] async fn test_a() { let TestActions { missing, .. } = resolve(&["a"], Flags::new()).await; assert_eq!(missing, vec![vec!["a", "b>1"]]); } #[tokio::test] async fn test_a_no_ver() { let TestActions { build, .. } = resolve(&["a"], Flags::new() | Flags::NO_DEP_VERSION).await; assert_eq!(build, vec!["a", "b"]); } #[tokio::test] async fn test_discord() { let TestActions { make, install, build, .. } = resolve(&["discord-canary"], Flags::new()).await; println!("{build:?}"); println!("{install:?}"); assert_eq!(build.len(), 3); assert_eq!(install.len(), 89 + 13); assert_eq!(make, 9); } #[tokio::test] async fn test_aur_only() { let TestActions { build, install, .. } = resolve(&["xterm", "yay"], Flags::aur_only() | Flags::NO_DEPS).await; assert_eq!(build, vec!["xterm", "yay-bin"]); assert_eq!(install, Vec::::new()); let TestActions { install, .. } = resolve(&["pacman"], Flags::aur_only() | Flags::NO_DEPS).await; assert_eq!(install, Vec::::new()); //1assert_eq!(build, vec!["pacman-git"]); } #[tokio::test] async fn test_repo_only() { let TestActions { build, install, .. } = resolve( &["xterm", "yay"], (Flags::new() | Flags::NO_DEPS) & !Flags::AUR, ) .await; assert_eq!(install, vec!["xterm"]); assert_eq!(build, Vec::::new()); let TestActions { install, build, .. } = resolve(&["pacman"], (Flags::new() | Flags::NO_DEPS) & !Flags::AUR).await; assert_eq!(install, vec!["pacman"]); assert_eq!(build, Vec::::new()); } #[tokio::test] async fn test_dups() { let TestActions { build, install, duplicates, .. } = resolve(&["extra/xterm", "aur/xterm"], Flags::new()).await; println!("{install:#?}"); println!("{build:#?}"); assert_eq!(duplicates.len(), 1); } #[tokio::test] async fn test_inner_conflicts() { let alpm = alpm(); let raur = raur(); let mut cache = HashSet::new(); let handle = Resolver::new(&alpm, &mut cache, &raur, Flags::new()); let actions = handle .resolve_targets(&["yay", "yay-git", "yay-bin"]) .await .unwrap(); let mut conflict1 = Conflict::new("yay".into()); conflict1.push("yay-git".into(), &Depend::new("yay")); conflict1.push("yay-bin".into(), &Depend::new("yay")); let mut conflict2 = Conflict::new("yay-bin".into()); conflict2.push("yay-git".into(), &Depend::new("yay")); let mut conflict3 = Conflict::new("yay-git".into()); conflict3.push("yay-bin".into(), &Depend::new("yay")); assert_eq!( actions.calculate_inner_conflicts(true), vec![conflict1, conflict2, conflict3] ); } #[tokio::test] async fn test_conflicts() { let alpm = alpm(); let raur = raur(); let mut cache = HashSet::new(); let handle = Resolver::new(&alpm, &mut cache, &raur, Flags::new()); let actions = handle.resolve_targets(&["pacman-git"]).await.unwrap(); let mut conflict = Conflict::new("pacman-git".into()); conflict.push("pacman".into(), &Depend::new("pacman")); assert_eq!(actions.calculate_conflicts(true), vec![conflict]); } #[tokio::test] async fn test_aur_updates() { let alpm = alpm(); let raur = raur(); let mut cache = HashSet::new(); let mut handle = Resolver::new(&alpm, &mut cache, &raur, Flags::new()); let pkgs = handle.updates(None).await.unwrap().aur_updates; let pkgs = pkgs .iter() .map(|p| p.remote.name.as_str()) .collect::>(); assert_eq!(pkgs, vec!["version_newer"]); } #[tokio::test] async fn test_aur_updates_enable_downgrade() { let alpm = alpm(); let raur = raur(); let mut cache = HashSet::new(); let mut handle = Resolver::new( &alpm, &mut cache, &raur, Flags::new() | Flags::ENABLE_DOWNGRADE, ); let pkgs = handle.updates(None).await.unwrap().aur_updates; let pkgs = pkgs .iter() .map(|p| p.remote.name.as_str()) .collect::>(); assert_eq!(pkgs, vec!["pacaur", "version_newer", "version_older"]); } #[tokio::test] async fn test_aur_updates_with_local_repos() { let alpm = Alpm::new("/", "tests/db").unwrap(); alpm.register_syncdb("core", SigLevel::NONE).unwrap(); alpm.register_syncdb("extra", SigLevel::NONE).unwrap(); alpm.register_syncdb("community", SigLevel::NONE).unwrap(); alpm.register_syncdb("multilib", SigLevel::NONE).unwrap(); alpm.register_syncdb("local-repo", SigLevel::NONE).unwrap(); let raur = raur(); let mut cache = HashSet::new(); let mut handle = Resolver::new( &alpm, &mut cache, &raur, Flags::new() | Flags::ENABLE_DOWNGRADE, ); let pkgs = handle .updates(Some(&["local-repo"])) .await .unwrap() .aur_updates; let mut pkgs = pkgs .iter() .map(|p| p.remote.name.as_str()) .collect::>(); pkgs.sort(); assert_eq!(pkgs, vec!["pacaur", "version_newer", "version_older"]); } #[tokio::test] async fn test_aur_updates_excludes_official_in_local_repo() { let alpm = Alpm::new("/", "tests/db").unwrap(); alpm.register_syncdb("core", SigLevel::NONE).unwrap(); alpm.register_syncdb("extra", SigLevel::NONE).unwrap(); alpm.register_syncdb("community", SigLevel::NONE).unwrap(); alpm.register_syncdb("multilib", SigLevel::NONE).unwrap(); alpm.register_syncdb("local-repo-with-official", SigLevel::NONE) .unwrap(); let mut mock_raur = crate::tests::MockRaur::new(); mock_raur.pkg("version_newer").version("100-1"); mock_raur.pkg("glibc").version("999-1"); let mut cache = HashSet::new(); let mut handle = Resolver::new(&alpm, &mut cache, &mock_raur, Flags::new()); let pkgs = handle .updates(Some(&["local-repo-with-official"])) .await .unwrap() .aur_updates; let pkgs = pkgs .iter() .map(|p| p.remote.name.as_str()) .collect::>(); assert_eq!(pkgs, vec!["version_newer"]); } #[tokio::test] async fn test_repo_nover() { let TestActions { install, .. } = resolve(&["repo_version_test"], Flags::new()).await; assert_eq!(install, Vec::::new()); let TestActions { install, .. } = resolve(&["repo_version_test"], Flags::new() | Flags::NO_DEP_VERSION).await; assert_eq!(install, vec!["pacman-contrib"]); } #[tokio::test] async fn test_satisfied_versioned_repo_dep() { let TestActions { missing, .. } = resolve(&["satisfied_versioned_repo_dep"], Flags::new()).await; assert_eq!( missing, vec![vec!["satisfied_versioned_repo_dep", "pacman>100"]] ); let TestActions { missing, .. } = resolve( &["satisfied_versioned_repo_dep"], Flags::new() | Flags::NO_DEP_VERSION, ) .await; assert_eq!(missing, Vec::>::new()); } #[tokio::test] async fn test_satisfied_versioned_repo_dep_nover() { let TestActions { build, install, .. } = resolve( &["satisfied_versioned_repo_dep"], Flags::new() | Flags::NO_DEP_VERSION, ) .await; assert_eq!(build, vec!["satisfied_versioned_repo_dep"]); assert!(install.is_empty()); let TestActions { missing, .. } = resolve( &["satisfied_versioned_repo_dep"], Flags::new() | Flags::NO_DEP_VERSION, ) .await; assert_eq!(missing, Vec::>::new()); } #[tokio::test] async fn test_cyclic() { let TestActions { .. } = resolve(&["cyclic"], Flags::new()).await; } #[tokio::test] async fn test_cyclic2() { let TestActions { .. } = resolve(&["systemd-git"], Flags::new()).await; } #[tokio::test] async fn test_resolve_targets() { let raur = raur(); //let raur = raur::Handle::default(); let alpm = alpm(); let mut cache = HashSet::new(); let flags = Flags::new() & !Flags::TARGET_PROVIDES & !Flags::MISSING_PROVIDES; let handle = Resolver::new(&alpm, &mut cache, &raur, flags).provider_callback(|_, pkgs| { println!("provider choice: {pkgs:?}"); 0 }); let actions = handle .resolve_targets(&["ros-melodic-desktop-full"]) .await //.resolve_targets(&["yay", "yay-bin", "yay-git"]) //.resolve_targets(&["yay", "pikaur", "pacman", "glibc", "0ad", "spotify"]) //.resolve_targets(&["0ad"]) //.resolve_targets(&["linux-pf"]) //.resolve_targets(&["ros-melodic-desktop-full", "yay"]) //.resolve_targets(&["ignition-common"]) .unwrap(); actions .iter_aur_pkgs() .for_each(|p| println!("b {}", p.pkg.name)); actions .iter_pkgbuilds() .for_each(|p| println!("c {}", p.1.pkg.pkgname)); actions .install .iter() .for_each(|p| println!("i {}", p.pkg.name())); actions .missing .iter() .for_each(|m| println!("missing {m:?}")); actions.calculate_conflicts(true).iter().for_each(|c| { println!("c {}: ", c.pkg); c.conflicting .iter() .for_each(|c| println!(" {} ({:?})", c.pkg, c.conflict)) }); actions .calculate_inner_conflicts(true) .iter() .for_each(|c| { println!("c {}: ", c.pkg); c.conflicting .iter() .for_each(|c| println!(" {} ({:?})", c.pkg, c.conflict)) }); actions .unneeded .iter() .for_each(|p| println!("u {}", p.name)); actions .duplicate_targets() .iter() .for_each(|p| println!("d {p}")); println!( "build: {}", actions.iter_aur_pkgs().count() + actions.iter_pkgbuilds().count() ); println!("install: {}", actions.install.len()); } #[tokio::test] async fn test_target_flags() { let TestActions { targets, .. } = resolve(&["discord-canary"], Flags::new()).await; assert_eq!(targets, vec!["discord-canary"]); } }