diff --git a/fuzz/atom/parser/gencorpus.rs b/fuzz/atom/parser/gencorpus.rs index 299835a..344e713 100644 --- a/fuzz/atom/parser/gencorpus.rs +++ b/fuzz/atom/parser/gencorpus.rs @@ -20,7 +20,7 @@ fn main() -> Result<(), Box> { fs::create_dir_all(&corpus_dir)?; - let repo = Repo::new("/var/db/repos/gentoo"); + let repo = Repo::new("/var/db/repos/gentoo").expect("failed to open repo"); let mut atoms = Vec::new(); for category in repo.categories()? { diff --git a/fuzz/atom/vercmp/gencorpus.rs b/fuzz/atom/vercmp/gencorpus.rs index 6f96f4f..e88ada5 100644 --- a/fuzz/atom/vercmp/gencorpus.rs +++ b/fuzz/atom/vercmp/gencorpus.rs @@ -17,7 +17,7 @@ fn main() -> Result<(), Box> { fs::create_dir_all(&corpus_dir)?; - let repo = Repo::new("/var/db/repos/gentoo"); + let repo = Repo::new("/var/db/repos/gentoo").expect("failed to open repo"); let mut versions = Vec::new(); for category in repo.categories()? { diff --git a/src/lib.rs b/src/lib.rs index b7b01a7..66cf2b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,8 @@ pub mod atom; /// ``` /// use gentoo_utils::repo::Repo; /// -/// let repo = Repo::new("/var/db/repos/gentoo"); +/// let repo = Repo::new("/var/db/repos/gentoo") +/// .expect("failed to open repo"); /// /// for result in repo.categories().expect("failed to read categories") { /// let category = result.expect("failed to read category"); diff --git a/src/repo/meson.build b/src/repo/meson.build index c1be7a7..e706ad9 100644 --- a/src/repo/meson.build +++ b/src/repo/meson.build @@ -1,3 +1,4 @@ -sources += files('mod.rs') +sources += files('mod.rs', 'parsers.rs') subdir('ebuild') +subdir('profile') diff --git a/src/repo/mod.rs b/src/repo/mod.rs index eb68839..0443afc 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -11,14 +11,21 @@ use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter, tag}; use crate::{ Parseable, atom::{self, Atom}, - repo::ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri}, + repo::{ + ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri}, + profile::{LineBasedFileExpr, Profile}, + }, useflag::IUseFlag, }; pub mod ebuild; +mod parsers; +pub mod profile; #[derive(Debug, thiserror::Error)] pub enum Error { + #[error("invalid repo: {0}")] + Invalid(String), #[error("io error: {0}")] Io(PathBuf, io::Error), #[error("error while reading directory: {0:?}: {1}")] @@ -27,12 +34,23 @@ pub enum Error { Unicode(PathBuf), #[error("parser error: {0}")] Parser(String), + #[error("profile error: {0}")] + Profile(profile::Error), } +#[derive(Debug, Clone, PartialEq, Eq, Get)] +pub struct Arch(#[get(method = "get", kind = "deref")] String); + #[derive(Debug, Clone, Get)] pub struct Repo { #[get(kind = "deref")] path: PathBuf, + #[get(kind = "deref")] + name: String, + #[get(kind = "deref")] + package_mask: Vec, + #[get(kind = "deref")] + arch_list: Vec, } #[derive(Debug, Clone, Get)] @@ -49,10 +67,39 @@ pub struct Categories(PathBuf, fs::ReadDir); pub struct Ebuilds(PathBuf, fs::ReadDir); impl Repo { - pub fn new>(path: P) -> Self { - Self { + pub fn new>(path: P) -> Result { + let name_path = path.as_ref().join("profiles/repo_name"); + let name = match fs::read_to_string(&name_path) { + Ok(repo_name) => repo_name, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => { + return Err(Error::Invalid("missing repo_name".to_string())); + } + Err(e) => return Err(Error::Io(name_path, e)), + }; + + let package_mask_path = path.as_ref().join("profiles/package.mask"); + let package_mask = + match fs::read_to_string(&package_mask_path).map(|s| read_package_mask(&s)) { + Ok(Ok(package_mask)) => package_mask, + Ok(Err(e)) => return Err(e), + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), + Err(e) => return Err(Error::Io(package_mask_path, e)), + }; + + let arch_list_path = path.as_ref().join("profiles/arch.list"); + let arch_list = match fs::read_to_string(&arch_list_path).map(|s| read_arch_list(&s)) { + Ok(Ok(arch_list)) => arch_list, + Ok(Err(e)) => return Err(e), + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), + Err(e) => return Err(Error::Io(arch_list_path, e)), + }; + + Ok(Self { path: path.as_ref().to_path_buf(), - } + name, + package_mask, + arch_list, + }) } pub fn categories(&self) -> Result { @@ -63,6 +110,10 @@ impl Repo { fs::read_dir(&path).map_err(|e| Error::Io(path, e))?, )) } + + pub fn evaluate_profile>(&self, path: P) -> Result { + Profile::evaluate(self.path.join("profiles").join(path)).map_err(Error::Profile) + } } impl Category { @@ -316,6 +367,34 @@ fn read_idepend(input: &str) -> Option>, Error>> { Some(parse_depends(line)) } +fn read_package_mask(input: &str) -> Result, Error> { + Ok(profile::LineBasedFileExpr::::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(input)) + .map_err(|it| Error::Parser(it.rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(atom) => Some(atom), + }) + .collect()) +} + +fn read_arch_list(input: &str) -> Result, Error> { + Ok(LineBasedFileExpr::::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(input)) + .map_err(|it| Error::Parser(it.rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(arch) => Some(arch), + }) + .collect()) +} + fn parse_depends(line: &str) -> Result>, Error> { Depend::::parser() .separated_by(ascii_whitespace1()) diff --git a/src/repo/parsers.rs b/src/repo/parsers.rs new file mode 100644 index 0000000..ccef666 --- /dev/null +++ b/src/repo/parsers.rs @@ -0,0 +1,20 @@ +use mon::{Parser, ParserIter, ascii_alphanumeric, one_of}; + +use crate::{Parseable, repo::Arch}; + +impl<'a> Parseable<'a, &'a str> for Arch { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let start = ascii_alphanumeric(); + let rest = ascii_alphanumeric() + .or(one_of("-".chars())) + .repeated() + .many(); + + start + .and(rest) + .recognize() + .map(|output: &str| Arch(output.to_string())) + } +} diff --git a/src/repo/profile/make_defaults/meson.build b/src/repo/profile/make_defaults/meson.build new file mode 100644 index 0000000..a7331a8 --- /dev/null +++ b/src/repo/profile/make_defaults/meson.build @@ -0,0 +1 @@ +sources += files('mod.rs', 'parsers.rs') diff --git a/src/repo/profile/make_defaults/mod.rs b/src/repo/profile/make_defaults/mod.rs new file mode 100644 index 0000000..334f165 --- /dev/null +++ b/src/repo/profile/make_defaults/mod.rs @@ -0,0 +1,135 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + repo::profile::{LineBasedFileExpr, Profile}, +}; + +mod parsers; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("parser error: {0}")] + Parser(String), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct Key(String); + +#[derive(Debug, Clone)] +struct Literal(String); + +#[derive(Debug, Clone)] +struct Interpolation(String); + +#[derive(Debug, Clone)] +enum Segment { + Literal(Literal), + Interpolation(Interpolation), +} + +#[derive(Debug, Clone)] +struct Assignment(Key, Vec); + +pub(super) fn evaluate>( + parents: &[Profile], + path: P, +) -> Result, Error> { + let parsed = match fs::read_to_string(path.as_ref().join("make.defaults")) { + Ok(contents) => parse(&contents)?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => HashMap::new(), + Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)), + }; + + let mut vars = interpolate(parents, parsed); + + incremental(parents, &mut vars); + + Ok(vars) +} + +fn incremental(parents: &[Profile], vars: &mut HashMap) { + for key in [ + "USE", + "USE_EXPAND", + "USE_EXPAND_HIDDEN", + "CONFIG_PROTECT", + "CONFIG_PROTECT_MASK", + ] { + let mut accumulated = Vec::new(); + + for parent in parents { + if let Some(values) = parent.make_defaults().get(key) { + accumulated.extend(values.split_ascii_whitespace()); + } + } + + if let Some(values) = vars.get(key) { + accumulated.extend(values.split_ascii_whitespace()); + } + + let mut final_values = Vec::new(); + + for var in accumulated { + if var == "-*" { + final_values.clear(); + } else if let Some(stripped) = var.strip_prefix("-") { + final_values.retain(|v| *v != stripped); + } else { + final_values.push(var); + } + } + + let mut seen = HashSet::new(); + final_values.retain(|v| seen.insert(*v)); + + if !final_values.is_empty() { + vars.insert(key.to_string(), final_values.join(" ")); + } + } +} + +fn interpolate(parents: &[Profile], vars: HashMap>) -> HashMap { + let parent_vars = parents + .iter() + .flat_map(|parent| parent.make_defaults().clone().into_iter()) + .collect::>(); + + vars.into_iter() + .map(|(key, segments)| { + let interpolated = segments + .into_iter() + .map(|segment| match segment { + Segment::Interpolation(i) => parent_vars.get(&i.0).cloned().unwrap_or_default(), + Segment::Literal(literal) => literal.0.trim().to_string(), + }) + .collect::>(); + + let joined = interpolated.join(""); + + (key.0, joined) + }) + .collect::>() +} + +fn parse(contents: &str) -> Result>, Error> { + Ok(LineBasedFileExpr::::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(contents)) + .map_err(|e| Error::Parser(dbg!(e).rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(Assignment(key, value)) => Some((key, value)), + }) + .collect()) +} diff --git a/src/repo/profile/make_defaults/parsers.rs b/src/repo/profile/make_defaults/parsers.rs new file mode 100644 index 0000000..4d27791 --- /dev/null +++ b/src/repo/profile/make_defaults/parsers.rs @@ -0,0 +1,88 @@ +use mon::{Parser, ParserIter, ascii_alpha, ascii_alphanumeric, r#if, one_of, tag}; + +use crate::{ + Parseable, + repo::profile::make_defaults::{Assignment, Interpolation, Key, Literal, Segment}, +}; + +impl<'a> Parseable<'a, &'a str> for Key { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let start = ascii_alpha(); + let rest = ascii_alphanumeric() + .or(one_of("_".chars())) + .repeated() + .many(); + + start + .followed_by(rest) + .recognize() + .map(|output: &str| Key(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Literal { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + r#if(|c: &char| *c != '"') + .and_not(Interpolation::parser()) + .repeated() + .at_least(1) + .recognize() + .map(|output: &str| Literal(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Interpolation { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Key::parser() + .recognize() + .delimited_by(tag("{"), tag("}")) + .preceded_by(tag("$")) + .map(|output: &str| Interpolation(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Segment { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Literal::parser() + .map(Segment::Literal) + .or(Interpolation::parser().map(Segment::Interpolation)) + } +} + +impl<'a> Parseable<'a, &'a str> for Assignment { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Key::parser() + .followed_by(tag("=")) + .and( + Segment::parser() + .repeated() + .many() + .delimited_by(tag("\""), tag("\"")), + ) + .map(|(key, value)| Assignment(key, value)) + } +} + +#[cfg(test)] +mod test { + use mon::input::InputIter; + + use super::*; + + #[test] + fn test_parse_value() { + let it = InputIter::new(r#"KEY="foo ${bar}""#); + + Assignment::parser().check_finished(it).unwrap(); + } +} diff --git a/src/repo/profile/meson.build b/src/repo/profile/meson.build new file mode 100644 index 0000000..c7b7baf --- /dev/null +++ b/src/repo/profile/meson.build @@ -0,0 +1,6 @@ +sources += files('mod.rs', 'parsers.rs') + +subdir('make_defaults') +subdir('package') +subdir('packages') +subdir('useflags') diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs new file mode 100644 index 0000000..37909c7 --- /dev/null +++ b/src/repo/profile/mod.rs @@ -0,0 +1,194 @@ +//! Evaluate profiles: +//! ```rust +//! use gentoo_utils::repo::Repo; +//! +//! let repo = Repo::new("/var/db/repos/gentoo") +//! .expect("failed to open repo"); +//! let profile = repo.evaluate_profile("default/linux/23.0") +//! .expect("failed to evaluate profile"); +//! +//! for (key, value) in profile.make_defaults() { +//! println!("{key} = {value}"); +//! } +//! ``` + +use std::{ + collections::HashMap, + fs::{self, File}, + io::{self, Read}, + path::{Path, PathBuf}, +}; + +use get::Get; +use itertools::Itertools; + +use crate::{Parseable, atom::Atom, repo::ebuild::Eapi, useflag::UseFlag}; + +mod make_defaults; +mod package; +mod package_use; +mod packages; +mod parsers; +mod useflags; + +#[derive(Debug, Clone)] +pub(super) enum LineBasedFileExpr { + Comment, + Expr(T), +} + +#[derive(Debug, Clone)] +enum FlagOperation { + Add(UseFlag), + Remove(UseFlag), +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("error evaluating make.defaults settings: {0}")] + MakeDefaults(#[from] make_defaults::Error), + #[error("error evaluating packages settings: {0}")] + Packages(#[from] packages::Error), + #[error("error evaluating package settings: {0}")] + Package(#[from] package::Error), + #[error("error evaluating package.use settings: {0}")] + PackageUse(#[from] package_use::Error), + #[error("error evaluating use settings: {0}")] + Use(#[from] useflags::Error), + #[error("parser error: {0}")] + Parser(String), +} + +#[derive(Debug, Clone, Get)] +pub struct Profile { + #[get(kind = "deref")] + path: PathBuf, + eapi: Eapi, + deprecated: Option, + #[get(kind = "deref")] + parents: Vec, + make_defaults: HashMap, + #[get(kind = "deref")] + packages: Vec, + #[get(kind = "deref")] + package_mask: Vec, + #[get(kind = "deref")] + package_provided: Vec, + package_use: HashMap>, + package_use_force: HashMap>, + package_use_mask: HashMap>, + package_use_stable_force: HashMap>, + package_use_stable_mask: HashMap>, + #[get(kind = "deref")] + use_force: Vec, + #[get(kind = "deref")] + use_mask: Vec, + #[get(kind = "deref")] + use_stable_force: Vec, + #[get(kind = "deref")] + use_stable_mask: Vec, +} + +impl Profile { + pub(super) fn evaluate>(path: P) -> Result { + let parents_path = path.as_ref().join("parent"); + + let parents = match fs::read_to_string(&parents_path) { + Ok(parents) => parents + .lines() + .map(|line| path.as_ref().join(line)) + .map(Profile::evaluate) + .collect::>()?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), + Err(e) => return Err(Error::Io(parents_path, e)), + }; + + let eapi_path = path.as_ref().join("eapi"); + let eapi = match fs::read_to_string(&eapi_path) + .map(|s| Eapi::parse(s.trim()).map_err(str::to_string)) + { + Ok(Ok(eapi)) => eapi, + Ok(Err(rest)) => return Err(Error::Parser(rest)), + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Eapi::parse("0").unwrap(), + Err(e) => return Err(Error::Io(eapi_path, e)), + }; + + let deprecated_path = path.as_ref().join("deprecated"); + let deprecated = match fs::read_to_string(&deprecated_path) { + Ok(string) => Some(string), + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => None, + Err(e) => return Err(Error::Io(deprecated_path, e)), + }; + + let make_defaults = make_defaults::evaluate(&parents, &path)?; + + let packages = packages::evaluate(&parents, &path)?; + + let package_mask = package::evaluate(&parents, package::Kind::Mask, &path)?; + let package_provided = package::evaluate(&parents, package::Kind::Provided, &path)?; + + let package_use = package_use::evaluate(&parents, package_use::Kind::Use, &path)?; + let package_use_force = package_use::evaluate(&parents, package_use::Kind::Force, &path)?; + let package_use_mask = package_use::evaluate(&parents, package_use::Kind::Mask, &path)?; + let package_use_stable_force = + package_use::evaluate(&parents, package_use::Kind::StableForce, &path)?; + let package_use_stable_mask = + package_use::evaluate(&parents, package_use::Kind::StableMask, &path)?; + + let use_force = useflags::evaluate(&parents, useflags::Kind::Force, &path)?; + let use_mask = useflags::evaluate(&parents, useflags::Kind::Mask, &path)?; + let use_stable_force = useflags::evaluate(&parents, useflags::Kind::StableForce, &path)?; + let use_stable_mask = useflags::evaluate(&parents, useflags::Kind::StableMask, &path)?; + + Ok(Self { + path: path.as_ref().to_path_buf(), + parents, + eapi, + deprecated, + make_defaults, + packages, + package_mask, + package_provided, + package_use, + package_use_force, + package_use_mask, + package_use_stable_force, + package_use_stable_mask, + use_force, + use_mask, + use_stable_force, + use_stable_mask, + }) + } +} + +fn read_config_files>(path: P) -> Result { + let metadata = fs::metadata(&path)?; + + if metadata.is_file() { + fs::read_to_string(&path) + } else if metadata.is_dir() { + let mut buffer = String::new(); + let paths = fs::read_dir(&path)? + .collect::, _>>()? + .into_iter() + .map(|entry| entry.path()) + .filter(|path| path.starts_with(".")) + .sorted() + .collect::>(); + + for path in &paths { + let mut file = File::open(path)?; + + file.read_to_string(&mut buffer)?; + } + + Ok(buffer) + } else { + let path = fs::canonicalize(&path)?; + + read_config_files(path) + } +} diff --git a/src/repo/profile/package/meson.build b/src/repo/profile/package/meson.build new file mode 100644 index 0000000..a7331a8 --- /dev/null +++ b/src/repo/profile/package/meson.build @@ -0,0 +1 @@ +sources += files('mod.rs', 'parsers.rs') diff --git a/src/repo/profile/package/mod.rs b/src/repo/profile/package/mod.rs new file mode 100644 index 0000000..facf9ea --- /dev/null +++ b/src/repo/profile/package/mod.rs @@ -0,0 +1,95 @@ +use std::{ + io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + atom::Atom, + repo::profile::{LineBasedFileExpr, Profile, read_config_files}, +}; + +mod parsers; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("parser error: {0}")] + Parser(String), +} + +#[derive(Debug, Clone, Copy)] +pub(super) enum Kind { + Mask, + Provided, +} + +#[derive(Debug, Clone)] +enum Package { + Add(Atom), + Remove(Atom), +} + +pub(super) fn evaluate>( + parents: &[Profile], + kind: Kind, + path: P, +) -> Result, Error> { + let file_path = match kind { + Kind::Mask => "package.mask", + Kind::Provided => "package.provided", + }; + + let parsed = match read_config_files(path.as_ref().join(file_path)) { + Ok(contents) => parse(&contents)?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), + Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)), + }; + + Ok(inherit(parents, kind, parsed)) +} + +fn inherit(parents: &[Profile], kind: Kind, packages: Vec) -> Vec { + let mut accumulated = Vec::new(); + + for parent in parents { + let source = match kind { + Kind::Mask => parent.package_mask(), + Kind::Provided => parent.package_provided(), + }; + + for package in source { + accumulated.push(package.clone()); + } + } + + for package in packages { + match package { + Package::Add(package) => { + accumulated.push(package); + } + Package::Remove(package) => { + accumulated.retain(|p| *p != package); + } + } + } + + accumulated +} + +fn parse(contents: &str) -> Result, Error> { + Ok(LineBasedFileExpr::::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(contents)) + .map_err(|e| Error::Parser(e.rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(package) => Some(package), + }) + .collect()) +} diff --git a/src/repo/profile/package/parsers.rs b/src/repo/profile/package/parsers.rs new file mode 100644 index 0000000..74d9acc --- /dev/null +++ b/src/repo/profile/package/parsers.rs @@ -0,0 +1,13 @@ +use mon::{Parser, tag}; + +use crate::{Parseable, atom::Atom, repo::profile::package::Package}; + +impl<'a> Parseable<'a, &'a str> for Package { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Atom::parser() + .map(Package::Add) + .or(Atom::parser().preceded_by(tag("-")).map(Package::Remove)) + } +} diff --git a/src/repo/profile/package_use/meson.build b/src/repo/profile/package_use/meson.build new file mode 100644 index 0000000..a7331a8 --- /dev/null +++ b/src/repo/profile/package_use/meson.build @@ -0,0 +1 @@ +sources += files('mod.rs', 'parsers.rs') diff --git a/src/repo/profile/package_use/mod.rs b/src/repo/profile/package_use/mod.rs new file mode 100644 index 0000000..ddad8b3 --- /dev/null +++ b/src/repo/profile/package_use/mod.rs @@ -0,0 +1,125 @@ +use std::{ + collections::HashMap, + io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + atom::Atom, + repo::profile::{FlagOperation, LineBasedFileExpr, Profile, read_config_files}, + useflag::UseFlag, +}; + +mod parsers; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("parser error: {0}")] + Parser(String), +} + +#[derive(Debug, Clone)] +struct Expr(Atom, Vec); + +#[derive(Debug, Clone, Copy)] +pub(super) enum Kind { + Use, + Force, + Mask, + StableForce, + StableMask, +} + +pub(super) fn evaluate>( + parents: &[Profile], + kind: Kind, + path: P, +) -> Result>, Error> { + let file_path = match kind { + Kind::Use => "package.use", + Kind::Force => "package.use.force", + Kind::Mask => "package.use.mask", + Kind::StableForce => "package.use.stable.force", + Kind::StableMask => "package.use.stable.mask", + }; + + let parsed = match read_config_files(path.as_ref().join(file_path)) { + Ok(contents) => parse(&contents)?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => HashMap::new(), + Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)), + }; + + Ok(inherit(parents, kind, parsed)) +} + +fn inherit( + parents: &[Profile], + kind: Kind, + vars: HashMap>, +) -> HashMap> { + let mut accumulated: HashMap> = HashMap::new(); + + for parent in parents { + let source = match kind { + Kind::Use => parent.package_use(), + Kind::Force => parent.package_use_force(), + Kind::Mask => parent.package_use_mask(), + Kind::StableForce => parent.package_use_stable_force(), + Kind::StableMask => parent.package_use_stable_mask(), + }; + + for (atom, flags) in source { + accumulated + .entry(atom.clone()) + .and_modify(|f| f.extend(flags.iter().cloned())) + .or_insert(flags.clone()); + } + } + + for (atom, flags) in vars { + match accumulated.get_mut(&atom) { + Some(accumulated) => { + for flag in flags { + match flag { + FlagOperation::Add(flag) => accumulated.push(flag), + FlagOperation::Remove(flag) => accumulated.retain(|v| *v != flag), + } + } + } + None => { + accumulated.insert( + atom.clone(), + flags + .iter() + .filter_map(|flag| match flag { + FlagOperation::Add(flag) => Some(flag), + FlagOperation::Remove(_) => None, + }) + .cloned() + .collect(), + ); + } + } + } + + accumulated +} + +fn parse(contents: &str) -> Result>, Error> { + Ok(LineBasedFileExpr::::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(contents)) + .map_err(|e| Error::Parser(e.rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(Expr(atom, operations)) => Some((atom, operations)), + }) + .collect()) +} diff --git a/src/repo/profile/package_use/parsers.rs b/src/repo/profile/package_use/parsers.rs new file mode 100644 index 0000000..f7bc801 --- /dev/null +++ b/src/repo/profile/package_use/parsers.rs @@ -0,0 +1,36 @@ +use mon::{Parser, ParserIter, ascii_whitespace, ascii_whitespace1, tag}; + +use crate::{ + Parseable, + atom::Atom, + repo::profile::{FlagOperation, package_use::Expr}, +}; + +impl<'a> Parseable<'a, &'a str> for Expr { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Atom::parser() + .followed_by(ascii_whitespace1()) + .and( + FlagOperation::parser() + .separated_by(ascii_whitespace().and_not(tag("\n")).repeated().at_least(1)) + .at_least(1), + ) + .map(|(atom, operations)| Expr(atom, operations)) + } +} + +#[cfg(test)] +mod test { + use mon::input::InputIter; + + use super::*; + + #[test] + fn test_parse_expr() { + let it = InputIter::new("foo/bar a -b"); + + Expr::parser().check_finished(it).unwrap(); + } +} diff --git a/src/repo/profile/packages/meson.build b/src/repo/profile/packages/meson.build new file mode 100644 index 0000000..a7331a8 --- /dev/null +++ b/src/repo/profile/packages/meson.build @@ -0,0 +1 @@ +sources += files('mod.rs', 'parsers.rs') diff --git a/src/repo/profile/packages/mod.rs b/src/repo/profile/packages/mod.rs new file mode 100644 index 0000000..4fd6559 --- /dev/null +++ b/src/repo/profile/packages/mod.rs @@ -0,0 +1,75 @@ +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + atom::Atom, + repo::profile::{LineBasedFileExpr, Profile}, +}; + +mod parsers; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("parser error: {0}")] + Parser(String), +} + +#[derive(Debug, Clone)] +enum Package { + Add(Atom), + Remove(Atom), +} + +pub(super) fn evaluate>(parents: &[Profile], path: P) -> Result, Error> { + let parsed = match fs::read_to_string(path.as_ref().join("packages")) { + Ok(contents) => parse(&contents)?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), + Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)), + }; + + Ok(inherit(parents, parsed)) +} + +fn inherit(parents: &[Profile], packages: Vec) -> Vec { + let mut accumulated = Vec::new(); + + for parent in parents { + for package in parent.packages() { + accumulated.push(package.clone()); + } + } + + for package in packages { + match package { + Package::Add(package) => { + accumulated.push(package); + } + Package::Remove(package) => { + accumulated.retain(|p| *p != package); + } + } + } + + accumulated +} + +fn parse(contents: &str) -> Result, Error> { + Ok(LineBasedFileExpr::::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(contents)) + .map_err(|e| Error::Parser(e.rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(package) => Some(package), + }) + .collect()) +} diff --git a/src/repo/profile/packages/parsers.rs b/src/repo/profile/packages/parsers.rs new file mode 100644 index 0000000..e5dc727 --- /dev/null +++ b/src/repo/profile/packages/parsers.rs @@ -0,0 +1,14 @@ +use mon::{Parser, tag}; + +use crate::{Parseable, atom::Atom, repo::profile::packages::Package}; + +impl<'a> Parseable<'a, &'a str> for Package { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Atom::parser() + .preceded_by(tag("*")) + .map(Package::Add) + .or(Atom::parser().preceded_by(tag("-*")).map(Package::Remove)) + } +} diff --git a/src/repo/profile/parsers.rs b/src/repo/profile/parsers.rs new file mode 100644 index 0000000..44e767e --- /dev/null +++ b/src/repo/profile/parsers.rs @@ -0,0 +1,35 @@ +use mon::{Parser, ParserIter, any, ascii_whitespace1, tag}; + +use crate::{ + Parseable, + repo::profile::{FlagOperation, LineBasedFileExpr}, + useflag::UseFlag, +}; + +impl<'a, T> Parseable<'a, &'a str> for LineBasedFileExpr +where + T: Parseable<'a, &'a str>, +{ + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let comment = tag("#") + .preceded_by(ascii_whitespace1().opt()) + .followed_by(any().and_not(tag("\n")).repeated().many()) + .map(|_| LineBasedFileExpr::Comment); + let expr = T::parser().map(|expr| LineBasedFileExpr::Expr(expr)); + + comment.or(expr) + } +} + +impl<'a> Parseable<'a, &'a str> for FlagOperation { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + UseFlag::parser() + .preceded_by(tag("-")) + .map(FlagOperation::Remove) + .or(UseFlag::parser().map(FlagOperation::Add)) + } +} diff --git a/src/repo/profile/useflags/meson.build b/src/repo/profile/useflags/meson.build new file mode 100644 index 0000000..1b57f64 --- /dev/null +++ b/src/repo/profile/useflags/meson.build @@ -0,0 +1 @@ +sources += files('mod.rs') diff --git a/src/repo/profile/useflags/mod.rs b/src/repo/profile/useflags/mod.rs new file mode 100644 index 0000000..d7fb96c --- /dev/null +++ b/src/repo/profile/useflags/mod.rs @@ -0,0 +1,94 @@ +use std::{ + io, + path::{Path, PathBuf}, +}; + +use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter}; + +use crate::{ + Parseable, + repo::profile::{FlagOperation, LineBasedFileExpr, Profile, read_config_files}, + useflag::UseFlag, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}: io error: {1}")] + Io(PathBuf, io::Error), + #[error("parser error: {0}")] + Parser(String), +} + +#[derive(Debug, Clone, Copy)] +pub(super) enum Kind { + Force, + Mask, + StableForce, + StableMask, +} + +#[allow(clippy::unnecessary_wraps)] +pub(super) fn evaluate>( + parents: &[Profile], + kind: Kind, + path: P, +) -> Result, Error> { + let file_path = match kind { + Kind::Force => "use.force", + Kind::Mask => "use.mask", + Kind::StableForce => "use.stable.force", + Kind::StableMask => "use.stable.mask", + }; + + let parsed = match read_config_files(path.as_ref().join(file_path)) { + Ok(contents) => parse(&contents)?, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Vec::new(), + Err(e) => return Err(Error::Io(path.as_ref().to_path_buf(), e)), + }; + + Ok(inherit(parents, kind, parsed)) +} + +fn inherit(parents: &[Profile], kind: Kind, operations: Vec) -> Vec { + let mut accumulated = Vec::new(); + + for parent in parents { + let source = match kind { + Kind::Force => parent.use_force(), + Kind::Mask => parent.use_mask(), + Kind::StableForce => parent.use_stable_force(), + Kind::StableMask => parent.use_stable_mask(), + }; + + for flag in source { + accumulated.push(flag.clone()); + } + } + + for operation in operations { + match operation { + FlagOperation::Add(flag) => { + accumulated.push(flag); + } + FlagOperation::Remove(flag) => { + accumulated.retain(|v| *v != flag); + } + } + } + + accumulated +} + +fn parse(contents: &str) -> Result, Error> { + Ok(LineBasedFileExpr::::parser() + .separated_by_with_opt_trailing(ascii_whitespace1()) + .many() + .parse_finished(InputIter::new(contents)) + .map_err(|e| Error::Parser(e.rest().to_string()))? + .into_iter() + .filter_map(|expr| match expr { + LineBasedFileExpr::Comment => None, + LineBasedFileExpr::Expr(flag_operation) => Some(flag_operation), + }) + .collect()) +} diff --git a/tests/meson.build b/tests/meson.build index 8ba06e8..d193910 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -2,6 +2,7 @@ tests = {} subdir('porthole') subdir('repo') +subdir('profile') foreach test, test_args : tests stem = fs.stem(test) @@ -15,5 +16,6 @@ foreach test, test_args : tests link_with: [gentoo_utils], ), args: test_args, + timeout: 0, ) endforeach diff --git a/tests/profile/meson.build b/tests/profile/meson.build new file mode 100644 index 0000000..47ab3bb --- /dev/null +++ b/tests/profile/meson.build @@ -0,0 +1,7 @@ +tests += {meson.current_source_dir() / 'read_all_profiles.rs': []} +tests += { + meson.current_source_dir() / 'read_mock_profile.rs': [ + meson.current_source_dir() / 'mockrepo', + ], +} + diff --git a/tests/profile/mockrepo/profiles/arch.list b/tests/profile/mockrepo/profiles/arch.list new file mode 100644 index 0000000..afe9227 --- /dev/null +++ b/tests/profile/mockrepo/profiles/arch.list @@ -0,0 +1,2 @@ +amd64 +aarch64 \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/base/make.defaults b/tests/profile/mockrepo/profiles/base/make.defaults new file mode 100644 index 0000000..7e93612 --- /dev/null +++ b/tests/profile/mockrepo/profiles/base/make.defaults @@ -0,0 +1 @@ +USE="base" \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/base/use.force b/tests/profile/mockrepo/profiles/base/use.force new file mode 100644 index 0000000..8681f8b --- /dev/null +++ b/tests/profile/mockrepo/profiles/base/use.force @@ -0,0 +1 @@ +base \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/gui/package.use b/tests/profile/mockrepo/profiles/features/emacs/gui/package.use new file mode 100644 index 0000000..71f6be2 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/gui/package.use @@ -0,0 +1 @@ +app-editors/emacs gui \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/gui/parent b/tests/profile/mockrepo/profiles/features/emacs/gui/parent new file mode 100644 index 0000000..a96aa0e --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/gui/parent @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/gui/use.force b/tests/profile/mockrepo/profiles/features/emacs/gui/use.force new file mode 100644 index 0000000..f9242d2 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/gui/use.force @@ -0,0 +1 @@ +gui \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/make.defaults b/tests/profile/mockrepo/profiles/features/emacs/make.defaults new file mode 100644 index 0000000..76bbc0a --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/make.defaults @@ -0,0 +1 @@ +USE="emacs" \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/package.mask b/tests/profile/mockrepo/profiles/features/emacs/package.mask new file mode 100644 index 0000000..11ca885 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/package.mask @@ -0,0 +1 @@ +app-editors/vim \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/package.use b/tests/profile/mockrepo/profiles/features/emacs/package.use new file mode 100644 index 0000000..ca6f712 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/package.use @@ -0,0 +1 @@ +app-editors/emacs default \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/packages b/tests/profile/mockrepo/profiles/features/emacs/packages new file mode 100644 index 0000000..2f4b446 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/packages @@ -0,0 +1 @@ +*app-editors/emacs \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/emacs/use.force b/tests/profile/mockrepo/profiles/features/emacs/use.force new file mode 100644 index 0000000..331d858 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/emacs/use.force @@ -0,0 +1 @@ +default \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/selinux/make.defaults b/tests/profile/mockrepo/profiles/features/selinux/make.defaults new file mode 100644 index 0000000..6abe718 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/selinux/make.defaults @@ -0,0 +1,2 @@ +USE="selinux" +SELINUX_TYPE="sys.subj.portage" \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/selinux/packages b/tests/profile/mockrepo/profiles/features/selinux/packages new file mode 100644 index 0000000..920d332 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/selinux/packages @@ -0,0 +1 @@ +*sec-policy/selinux-base \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/selinux/use.force b/tests/profile/mockrepo/profiles/features/selinux/use.force new file mode 100644 index 0000000..767c305 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/selinux/use.force @@ -0,0 +1 @@ +caps \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/features/selinux/use.mask b/tests/profile/mockrepo/profiles/features/selinux/use.mask new file mode 100644 index 0000000..ce02645 --- /dev/null +++ b/tests/profile/mockrepo/profiles/features/selinux/use.mask @@ -0,0 +1 @@ +jit \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/gentoo-desktop/make.defaults b/tests/profile/mockrepo/profiles/gentoo-desktop/make.defaults new file mode 100644 index 0000000..f767bd0 --- /dev/null +++ b/tests/profile/mockrepo/profiles/gentoo-desktop/make.defaults @@ -0,0 +1 @@ +USE="-base" \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/gentoo-desktop/package.use b/tests/profile/mockrepo/profiles/gentoo-desktop/package.use new file mode 100644 index 0000000..39a006d --- /dev/null +++ b/tests/profile/mockrepo/profiles/gentoo-desktop/package.use @@ -0,0 +1 @@ +app-editors/emacs -default \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/gentoo-desktop/parent b/tests/profile/mockrepo/profiles/gentoo-desktop/parent new file mode 100644 index 0000000..eebec22 --- /dev/null +++ b/tests/profile/mockrepo/profiles/gentoo-desktop/parent @@ -0,0 +1,3 @@ +../base +../features/selinux +../features/emacs/gui \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/gentoo-desktop/use.mask b/tests/profile/mockrepo/profiles/gentoo-desktop/use.mask new file mode 100644 index 0000000..7fff956 --- /dev/null +++ b/tests/profile/mockrepo/profiles/gentoo-desktop/use.mask @@ -0,0 +1 @@ +-jit \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/package.mask b/tests/profile/mockrepo/profiles/package.mask new file mode 100644 index 0000000..11ca885 --- /dev/null +++ b/tests/profile/mockrepo/profiles/package.mask @@ -0,0 +1 @@ +app-editors/vim \ No newline at end of file diff --git a/tests/profile/mockrepo/profiles/repo_name b/tests/profile/mockrepo/profiles/repo_name new file mode 100644 index 0000000..e1c92af --- /dev/null +++ b/tests/profile/mockrepo/profiles/repo_name @@ -0,0 +1 @@ +mockrepo \ No newline at end of file diff --git a/tests/profile/read_all_profiles.rs b/tests/profile/read_all_profiles.rs new file mode 100644 index 0000000..4a214a0 --- /dev/null +++ b/tests/profile/read_all_profiles.rs @@ -0,0 +1,483 @@ +use gentoo_utils::repo::Repo; + +fn main() { + let profiles = [ + "default/linux/alpha/23.0", + "default/linux/alpha/23.0/systemd", + "default/linux/alpha/23.0/desktop", + "default/linux/alpha/23.0/desktop/gnome", + "default/linux/alpha/23.0/desktop/gnome/systemd", + "default/linux/alpha/23.0/split-usr", + "default/linux/alpha/23.0/split-usr/desktop", + "default/linux/alpha/23.0/split-usr/desktop/gnome", + "default/linux/amd64/23.0", + "default/linux/amd64/23.0/systemd", + "default/linux/amd64/23.0/desktop", + "default/linux/amd64/23.0/desktop/systemd", + "default/linux/amd64/23.0/desktop/gnome", + "default/linux/amd64/23.0/desktop/gnome/systemd", + "default/linux/amd64/23.0/desktop/plasma", + "default/linux/amd64/23.0/desktop/plasma/systemd", + "default/linux/amd64/23.0/no-multilib", + "default/linux/amd64/23.0/no-multilib/systemd", + "default/linux/amd64/23.0/no-multilib/hardened", + "default/linux/amd64/23.0/no-multilib/hardened/systemd", + "default/linux/amd64/23.0/no-multilib/hardened/selinux", + "default/linux/amd64/23.0/no-multilib/hardened/selinux/systemd", + "default/linux/amd64/23.0/no-multilib/prefix", + "default/linux/amd64/23.0/no-multilib/prefix/kernel-2.6.32+", + "default/linux/amd64/23.0/no-multilib/prefix/kernel-2.6.16+", + "default/linux/amd64/23.0/no-multilib/prefix/kernel-3.2+", + "default/linux/amd64/23.0/llvm", + "default/linux/amd64/23.0/llvm/systemd", + "default/linux/amd64/23.0/hardened", + "default/linux/amd64/23.0/hardened/systemd", + "default/linux/amd64/23.0/hardened/selinux", + "default/linux/amd64/23.0/hardened/selinux/systemd", + "default/linux/amd64/23.0/split-usr", + "default/linux/amd64/23.0/split-usr/desktop", + "default/linux/amd64/23.0/split-usr/desktop/gnome", + "default/linux/amd64/23.0/split-usr/desktop/plasma", + "default/linux/amd64/23.0/split-usr/no-multilib", + "default/linux/amd64/23.0/split-usr/no-multilib/selinux", + "default/linux/amd64/23.0/split-usr/no-multilib/hardened", + "default/linux/amd64/23.0/split-usr/no-multilib/hardened/selinux", + "default/linux/amd64/23.0/split-usr/no-multilib/prefix", + "default/linux/amd64/23.0/split-usr/no-multilib/prefix/kernel-2.6.32+", + "default/linux/amd64/23.0/split-usr/no-multilib/prefix/kernel-2.6.16+", + "default/linux/amd64/23.0/split-usr/no-multilib/prefix/kernel-3.2+", + "default/linux/amd64/23.0/split-usr/llvm", + "default/linux/amd64/23.0/split-usr/hardened", + "default/linux/amd64/23.0/split-usr/hardened/selinux", + "default/linux/amd64/23.0/x32", + "default/linux/amd64/23.0/x32/systemd", + "default/linux/amd64/23.0/split-usr/x32", + "default/linux/arm/23.0", + "default/linux/arm/23.0/desktop", + "default/linux/arm/23.0/desktop/gnome", + "default/linux/arm/23.0/desktop/gnome/systemd", + "default/linux/arm/23.0/desktop/plasma", + "default/linux/arm/23.0/desktop/plasma/systemd", + "default/linux/arm/23.0/armv4", + "default/linux/arm/23.0/armv4t", + "default/linux/arm/23.0/armv4t/systemd", + "default/linux/arm/23.0/armv5te", + "default/linux/arm/23.0/armv5te/systemd", + "default/linux/arm/23.0/armv6j_sf", + "default/linux/arm/23.0/armv6j_sf/hardened", + "default/linux/arm/23.0/armv6j_sf/hardened/selinux", + "default/linux/arm/23.0/armv6j_sf/systemd", + "default/linux/arm/23.0/armv6j_hf", + "default/linux/arm/23.0/armv6j_hf/hardened", + "default/linux/arm/23.0/armv6j_hf/hardened/selinux", + "default/linux/arm/23.0/armv6j_hf/systemd", + "default/linux/arm/23.0/armv7a_sf", + "default/linux/arm/23.0/armv7a_sf/hardened", + "default/linux/arm/23.0/armv7a_sf/hardened/selinux", + "default/linux/arm/23.0/armv7a_sf/desktop", + "default/linux/arm/23.0/armv7a_sf/desktop/gnome", + "default/linux/arm/23.0/armv7a_sf/desktop/gnome/systemd", + "default/linux/arm/23.0/armv7a_sf/desktop/plasma", + "default/linux/arm/23.0/armv7a_sf/desktop/plasma/systemd", + "default/linux/arm/23.0/armv7a_sf/systemd", + "default/linux/arm/23.0/armv7a_hf", + "default/linux/arm/23.0/armv7a_hf/hardened", + "default/linux/arm/23.0/armv7a_hf/hardened/selinux", + "default/linux/arm/23.0/armv7a_hf/desktop", + "default/linux/arm/23.0/armv7a_hf/desktop/gnome", + "default/linux/arm/23.0/armv7a_hf/desktop/gnome/systemd", + "default/linux/arm/23.0/armv7a_hf/desktop/plasma", + "default/linux/arm/23.0/armv7a_hf/desktop/plasma/systemd", + "default/linux/arm/23.0/armv7a_hf/systemd", + "default/linux/arm/23.0/split-usr", + "default/linux/arm/23.0/split-usr/desktop", + "default/linux/arm/23.0/split-usr/desktop/gnome", + "default/linux/arm/23.0/split-usr/desktop/plasma", + "default/linux/arm/23.0/split-usr/armv4", + "default/linux/arm/23.0/split-usr/armv4t", + "default/linux/arm/23.0/split-usr/armv5te", + "default/linux/arm/23.0/split-usr/armv6j_sf", + "default/linux/arm/23.0/split-usr/armv6j_sf/hardened", + "default/linux/arm/23.0/split-usr/armv6j_sf/hardened/selinux", + "default/linux/arm/23.0/split-usr/armv6j_hf", + "default/linux/arm/23.0/split-usr/armv6j_hf/hardened", + "default/linux/arm/23.0/split-usr/armv6j_hf/hardened/selinux", + "default/linux/arm/23.0/split-usr/armv7a_sf", + "default/linux/arm/23.0/split-usr/armv7a_sf/hardened", + "default/linux/arm/23.0/split-usr/armv7a_sf/hardened/selinux", + "default/linux/arm/23.0/split-usr/armv7a_sf/desktop", + "default/linux/arm/23.0/split-usr/armv7a_sf/desktop/gnome", + "default/linux/arm/23.0/split-usr/armv7a_sf/desktop/plasma", + "default/linux/arm/23.0/split-usr/armv7a_hf", + "default/linux/arm/23.0/split-usr/armv7a_hf/hardened", + "default/linux/arm/23.0/split-usr/armv7a_hf/hardened/selinux", + "default/linux/arm/23.0/split-usr/armv7a_hf/desktop", + "default/linux/arm/23.0/split-usr/armv7a_hf/desktop/gnome", + "default/linux/arm/23.0/split-usr/armv7a_hf/desktop/plasma", + "default/linux/arm64/23.0", + "default/linux/arm64/23.0/hardened", + "default/linux/arm64/23.0/hardened/systemd", + "default/linux/arm64/23.0/hardened/selinux", + "default/linux/arm64/23.0/hardened/selinux/systemd", + "default/linux/arm64/23.0/desktop", + "default/linux/arm64/23.0/desktop/gnome", + "default/linux/arm64/23.0/desktop/gnome/systemd", + "default/linux/arm64/23.0/desktop/plasma", + "default/linux/arm64/23.0/desktop/plasma/systemd", + "default/linux/arm64/23.0/desktop/systemd", + "default/linux/arm64/23.0/systemd", + "default/linux/arm64/23.0/llvm", + "default/linux/arm64/23.0/llvm/systemd", + "default/linux/arm64/23.0/split-usr", + "default/linux/arm64/23.0/split-usr/hardened", + "default/linux/arm64/23.0/split-usr/hardened/selinux", + "default/linux/arm64/23.0/split-usr/desktop", + "default/linux/arm64/23.0/split-usr/desktop/gnome", + "default/linux/arm64/23.0/split-usr/desktop/plasma", + "default/linux/arm64/23.0/split-usr/llvm", + "default/linux/arm64/23.0/big-endian", + "default/linux/arm64/23.0/big-endian/systemd", + "default/linux/arm64/23.0/split-usr/big-endian", + "default/linux/hppa/23.0/hppa1.1", + "default/linux/hppa/23.0/hppa1.1/systemd", + "default/linux/hppa/23.0/hppa1.1/desktop", + "default/linux/hppa/23.0/hppa1.1/desktop/systemd", + "default/linux/hppa/23.0/hppa1.1/split-usr", + "default/linux/hppa/23.0/hppa1.1/split-usr/desktop", + "default/linux/hppa/23.0/hppa2.0", + "default/linux/hppa/23.0/hppa2.0/systemd", + "default/linux/hppa/23.0/hppa2.0/desktop", + "default/linux/hppa/23.0/hppa2.0/desktop/systemd", + "default/linux/hppa/23.0/hppa2.0/split-usr", + "default/linux/hppa/23.0/hppa2.0/split-usr/desktop", + "default/linux/loong/23.0/la64v100/lp64d", + "default/linux/loong/23.0/la64v100/lp64d/llvm", + "default/linux/loong/23.0/la64v100/lp64d/llvm/systemd", + "default/linux/loong/23.0/la64v100/lp64d/desktop", + "default/linux/loong/23.0/la64v100/lp64d/desktop/gnome", + "default/linux/loong/23.0/la64v100/lp64d/desktop/gnome/systemd", + "default/linux/loong/23.0/la64v100/lp64d/desktop/plasma", + "default/linux/loong/23.0/la64v100/lp64d/desktop/plasma/systemd", + "default/linux/loong/23.0/la64v100/lp64d/desktop/systemd", + "default/linux/loong/23.0/la64v100/lp64d/systemd", + "default/linux/loong/23.0/la64v100/split-usr/lp64d", + "default/linux/loong/23.0/la64v100/split-usr/lp64d/desktop", + "default/linux/loong/23.0/la64v100/split-usr/lp64d/desktop/gnome", + "default/linux/loong/23.0/la64v100/split-usr/lp64d/desktop/plasma", + "default/linux/m68k/23.0", + "default/linux/m68k/23.0/systemd", + "default/linux/m68k/23.0/split-usr", + "default/linux/m68k/23.0/time64", + "default/linux/mips/23.0/mipsel/o32_sf", + "default/linux/mips/23.0/mipsel/o32_sf/systemd", + "default/linux/mips/23.0/mipsel/o32", + "default/linux/mips/23.0/mipsel/o32/systemd", + "default/linux/mips/23.0/mipsel/n32", + "default/linux/mips/23.0/mipsel/n32/systemd", + "default/linux/mips/23.0/mipsel/n64", + "default/linux/mips/23.0/mipsel/n64/systemd", + "default/linux/mips/23.0/mipsel/multilib/n32", + "default/linux/mips/23.0/mipsel/multilib/n32/systemd", + "default/linux/mips/23.0/mipsel/multilib/n64", + "default/linux/mips/23.0/mipsel/multilib/n64/systemd", + "default/linux/mips/23.0/o32_sf", + "default/linux/mips/23.0/o32_sf/systemd", + "default/linux/mips/23.0/o32", + "default/linux/mips/23.0/o32/systemd", + "default/linux/mips/23.0/n32", + "default/linux/mips/23.0/n32/systemd", + "default/linux/mips/23.0/n64", + "default/linux/mips/23.0/n64/systemd", + "default/linux/mips/23.0/multilib/n32", + "default/linux/mips/23.0/multilib/n32/systemd", + "default/linux/mips/23.0/multilib/n64", + "default/linux/mips/23.0/multilib/n64/systemd", + "default/linux/mips/23.0/split-usr/mipsel/o32_sf", + "default/linux/mips/23.0/split-usr/mipsel/o32", + "default/linux/mips/23.0/split-usr/mipsel/n32", + "default/linux/mips/23.0/split-usr/mipsel/n64", + "default/linux/mips/23.0/split-usr/mipsel/multilib/n32", + "default/linux/mips/23.0/split-usr/mipsel/multilib/n64", + "default/linux/mips/23.0/split-usr/o32_sf", + "default/linux/mips/23.0/split-usr/o32", + "default/linux/mips/23.0/split-usr/n32", + "default/linux/mips/23.0/split-usr/n64", + "default/linux/mips/23.0/split-usr/multilib/n32", + "default/linux/mips/23.0/split-usr/multilib/n64", + "default/linux/mips/23.0/time64/mipsel/o32_sf", + "default/linux/mips/23.0/time64/mipsel/o32_sf/systemd", + "default/linux/mips/23.0/time64/mipsel/o32", + "default/linux/mips/23.0/time64/mipsel/o32/systemd", + "default/linux/mips/23.0/time64/mipsel/n32", + "default/linux/mips/23.0/time64/mipsel/n32/systemd", + "default/linux/mips/23.0/time64/mipsel/multilib/n32", + "default/linux/mips/23.0/time64/mipsel/multilib/n32/systemd", + "default/linux/mips/23.0/time64/mipsel/multilib/n64", + "default/linux/mips/23.0/time64/mipsel/multilib/n64/systemd", + "default/linux/mips/23.0/time64/o32_sf", + "default/linux/mips/23.0/time64/o32_sf/systemd", + "default/linux/mips/23.0/time64/o32", + "default/linux/mips/23.0/time64/o32/systemd", + "default/linux/mips/23.0/time64/n32", + "default/linux/mips/23.0/time64/n32/systemd", + "default/linux/mips/23.0/time64/multilib/n32", + "default/linux/mips/23.0/time64/multilib/n32/systemd", + "default/linux/mips/23.0/time64/multilib/n64", + "default/linux/mips/23.0/time64/multilib/n64/systemd", + "default/linux/mips/23.0/time64/split-usr/mipsel/o32_sf", + "default/linux/mips/23.0/time64/split-usr/mipsel/o32", + "default/linux/mips/23.0/time64/split-usr/mipsel/n32", + "default/linux/mips/23.0/time64/split-usr/mipsel/multilib/n32", + "default/linux/mips/23.0/time64/split-usr/mipsel/multilib/n64", + "default/linux/mips/23.0/time64/split-usr/o32_sf", + "default/linux/mips/23.0/time64/split-usr/o32", + "default/linux/mips/23.0/time64/split-usr/n32", + "default/linux/mips/23.0/time64/split-usr/multilib/n32", + "default/linux/mips/23.0/time64/split-usr/multilib/n64", + "default/linux/ppc/23.0", + "default/linux/ppc/23.0/desktop", + "default/linux/ppc/23.0/desktop/gnome", + "default/linux/ppc/23.0/desktop/gnome/systemd", + "default/linux/ppc/23.0/systemd", + "default/linux/ppc/23.0/split-usr", + "default/linux/ppc/23.0/split-usr/desktop", + "default/linux/ppc/23.0/split-usr/desktop/gnome", + "default/linux/ppc/23.0/time64", + "default/linux/ppc/23.0/time64/desktop", + "default/linux/ppc/23.0/time64/desktop/gnome", + "default/linux/ppc/23.0/time64/desktop/gnome/systemd", + "default/linux/ppc/23.0/time64/systemd", + "default/linux/ppc/23.0/time64/split-usr", + "default/linux/ppc/23.0/time64/split-usr/desktop", + "default/linux/ppc/23.0/time64/split-usr/desktop/gnome", + "default/linux/ppc64/23.0", + "default/linux/ppc64/23.0/desktop", + "default/linux/ppc64/23.0/desktop/gnome", + "default/linux/ppc64/23.0/desktop/gnome/systemd", + "default/linux/ppc64/23.0/systemd", + "default/linux/ppc64/23.0/split-usr", + "default/linux/ppc64/23.0/split-usr/desktop", + "default/linux/ppc64/23.0/split-usr/desktop/gnome", + "default/linux/ppc64le/23.0", + "default/linux/ppc64le/23.0/desktop", + "default/linux/ppc64le/23.0/desktop/gnome", + "default/linux/ppc64le/23.0/desktop/gnome/systemd", + "default/linux/ppc64le/23.0/desktop/plasma", + "default/linux/ppc64le/23.0/desktop/plasma/systemd", + "default/linux/ppc64le/23.0/desktop/systemd", + "default/linux/ppc64le/23.0/systemd", + "default/linux/ppc64le/23.0/split-usr", + "default/linux/ppc64le/23.0/split-usr/desktop", + "default/linux/ppc64le/23.0/split-usr/desktop/gnome", + "default/linux/ppc64le/23.0/split-usr/desktop/plasma", + "default/linux/riscv/23.0/rv64/lp64d", + "default/linux/riscv/23.0/rv64/lp64d/desktop", + "default/linux/riscv/23.0/rv64/lp64d/desktop/gnome", + "default/linux/riscv/23.0/rv64/lp64d/desktop/gnome/systemd", + "default/linux/riscv/23.0/rv64/lp64d/desktop/plasma", + "default/linux/riscv/23.0/rv64/lp64d/desktop/plasma/systemd", + "default/linux/riscv/23.0/rv64/lp64d/desktop/systemd", + "default/linux/riscv/23.0/rv64/lp64d/systemd", + "default/linux/riscv/23.0/rv64/lp64", + "default/linux/riscv/23.0/rv64/lp64/desktop", + "default/linux/riscv/23.0/rv64/lp64/desktop/gnome", + "default/linux/riscv/23.0/rv64/lp64/desktop/gnome/systemd", + "default/linux/riscv/23.0/rv64/lp64/desktop/plasma", + "default/linux/riscv/23.0/rv64/lp64/desktop/plasma/systemd", + "default/linux/riscv/23.0/rv64/lp64/desktop/systemd", + "default/linux/riscv/23.0/rv64/lp64/systemd", + "default/linux/riscv/23.0/rv64/multilib", + "default/linux/riscv/23.0/rv64/multilib/systemd", + "default/linux/riscv/23.0/rv32/ilp32d", + "default/linux/riscv/23.0/rv32/ilp32d/systemd", + "default/linux/riscv/23.0/rv32/ilp32", + "default/linux/riscv/23.0/rv32/ilp32/systemd", + "default/linux/riscv/23.0/rv64/split-usr/lp64d", + "default/linux/riscv/23.0/rv64/split-usr/lp64d/desktop", + "default/linux/riscv/23.0/rv64/split-usr/lp64d/desktop/gnome", + "default/linux/riscv/23.0/rv64/split-usr/lp64d/desktop/plasma", + "default/linux/riscv/23.0/rv64/split-usr/lp64", + "default/linux/riscv/23.0/rv64/split-usr/lp64/desktop", + "default/linux/riscv/23.0/rv64/split-usr/lp64/desktop/gnome", + "default/linux/riscv/23.0/rv64/split-usr/lp64/desktop/plasma", + "default/linux/riscv/23.0/rv64/split-usr/multilib", + "default/linux/riscv/23.0/rv32/split-usr/ilp32d", + "default/linux/riscv/23.0/rv32/split-usr/ilp32", + "default/linux/s390/23.0", + "default/linux/s390/23.0/systemd", + "default/linux/s390/23.0/split-usr", + "default/linux/s390/23.0/split-usr/s390x", + "default/linux/s390/23.0/s390x", + "default/linux/s390/23.0/s390x/systemd", + "default/linux/s390/23.0/time64", + "default/linux/s390/23.0/time64/systemd", + "default/linux/s390/23.0/time64/split-usr", + "default/linux/sparc/23.0", + "default/linux/sparc/23.0/desktop", + "default/linux/sparc/23.0/systemd", + "default/linux/sparc/23.0/64ul", + "default/linux/sparc/23.0/64ul/desktop", + "default/linux/sparc/23.0/64ul/systemd", + "default/linux/sparc/23.0/split-usr", + "default/linux/sparc/23.0/split-usr/desktop", + "default/linux/sparc/23.0/split-usr/64ul", + "default/linux/sparc/23.0/split-usr/64ul/desktop", + "default/linux/x86/23.0/i686", + "default/linux/x86/23.0/i686/systemd", + "default/linux/x86/23.0/i686/hardened", + "default/linux/x86/23.0/i686/hardened/selinux", + "default/linux/x86/23.0/i686/desktop", + "default/linux/x86/23.0/i686/desktop/gnome", + "default/linux/x86/23.0/i686/desktop/gnome/systemd", + "default/linux/x86/23.0/i686/desktop/plasma", + "default/linux/x86/23.0/i686/desktop/plasma/systemd", + "default/linux/x86/23.0/i686/prefix", + "default/linux/x86/23.0/i686/prefix/kernel-2.6.32+", + "default/linux/x86/23.0/i686/prefix/kernel-2.6.16+", + "default/linux/x86/23.0/i686/prefix/kernel-3.2+", + "default/linux/x86/23.0/i686/split-usr", + "default/linux/x86/23.0/i686/split-usr/hardened", + "default/linux/x86/23.0/i686/split-usr/hardened/selinux", + "default/linux/x86/23.0/i686/split-usr/desktop", + "default/linux/x86/23.0/i686/split-usr/desktop/gnome", + "default/linux/x86/23.0/i686/split-usr/desktop/plasma", + "default/linux/x86/23.0/i686/split-usr/prefix", + "default/linux/x86/23.0/i686/split-usr/prefix/kernel-2.6.32+", + "default/linux/x86/23.0/i686/split-usr/prefix/kernel-2.6.16+", + "default/linux/x86/23.0/i686/split-usr/prefix/kernel-3.2+", + "default/linux/x86/23.0/i686/time64", + "default/linux/x86/23.0/i686/time64/systemd", + "default/linux/x86/23.0/i686/time64/hardened", + "default/linux/x86/23.0/i686/time64/hardened/selinux", + "default/linux/x86/23.0/i686/time64/desktop", + "default/linux/x86/23.0/i686/time64/desktop/gnome", + "default/linux/x86/23.0/i686/time64/desktop/gnome/systemd", + "default/linux/x86/23.0/i686/time64/desktop/plasma", + "default/linux/x86/23.0/i686/time64/desktop/plasma/systemd", + "default/linux/x86/23.0/i686/time64/split-usr", + "default/linux/x86/23.0/i686/time64/split-usr/hardened", + "default/linux/x86/23.0/i686/time64/split-usr/hardened/selinux", + "default/linux/x86/23.0/i686/time64/split-usr/desktop", + "default/linux/x86/23.0/i686/time64/split-usr/desktop/gnome", + "default/linux/x86/23.0/i686/time64/split-usr/desktop/plasma", + "default/linux/x86/23.0/i486", + "default/linux/x86/23.0/i486/systemd", + "default/linux/x86/23.0/i486/hardened", + "default/linux/x86/23.0/i486/hardened/selinux", + "default/linux/x86/23.0/i486/split-usr", + "default/linux/x86/23.0/i486/split-usr/hardened", + "default/linux/x86/23.0/i486/split-usr/hardened/selinux", + "default/linux/x86/23.0/i486/time64", + "default/linux/x86/23.0/i486/time64/systemd", + "default/linux/x86/23.0/i486/time64/hardened", + "default/linux/x86/23.0/i486/time64/hardened/selinux", + "default/linux/x86/23.0/i486/time64/split-usr", + "default/linux/x86/23.0/i486/time64/split-usr/hardened", + "default/linux/x86/23.0/i486/time64/split-usr/hardened/selinux", + "default/linux/amd64/23.0/musl", + "default/linux/amd64/23.0/musl/llvm", + "default/linux/amd64/23.0/musl/hardened", + "default/linux/amd64/23.0/musl/hardened/selinux", + "default/linux/amd64/23.0/split-usr/musl", + "default/linux/amd64/23.0/split-usr/musl/llvm", + "default/linux/amd64/23.0/split-usr/musl/hardened", + "default/linux/amd64/23.0/split-usr/musl/hardened/selinux", + "default/linux/arm/23.0/armv6j_hf/musl", + "default/linux/arm/23.0/armv6j_hf/musl/hardened", + "default/linux/arm/23.0/armv6j_hf/musl/hardened/selinux", + "default/linux/arm/23.0/armv7a_hf/musl", + "default/linux/arm/23.0/armv7a_hf/musl/hardened", + "default/linux/arm/23.0/armv7a_hf/musl/hardened/selinux", + "default/linux/arm/23.0/split-usr/armv6j_hf/musl", + "default/linux/arm/23.0/split-usr/armv6j_hf/musl/hardened", + "default/linux/arm/23.0/split-usr/armv6j_hf/musl/hardened/selinux", + "default/linux/arm/23.0/split-usr/armv7a_hf/musl", + "default/linux/arm/23.0/split-usr/armv7a_hf/musl/hardened", + "default/linux/arm/23.0/split-usr/armv7a_hf/musl/hardened/selinux", + "default/linux/arm64/23.0/musl", + "default/linux/arm64/23.0/musl/llvm", + "default/linux/arm64/23.0/musl/hardened", + "default/linux/arm64/23.0/musl/hardened/selinux", + "default/linux/arm64/23.0/split-usr/musl", + "default/linux/arm64/23.0/split-usr/musl/llvm", + "default/linux/arm64/23.0/split-usr/musl/hardened", + "default/linux/arm64/23.0/split-usr/musl/hardened/selinux", + "default/linux/m68k/23.0/musl", + "default/linux/m68k/23.0/split-usr/musl", + "default/linux/mips/23.0/mipsel/o32/musl", + "default/linux/mips/23.0/mipsel/n64/musl", + "default/linux/mips/23.0/o32/musl", + "default/linux/mips/23.0/n64/musl", + "default/linux/mips/23.0/split-usr/mipsel/o32/musl", + "default/linux/mips/23.0/split-usr/mipsel/n64/musl", + "default/linux/mips/23.0/split-usr/o32/musl", + "default/linux/mips/23.0/split-usr/n64/musl", + "default/linux/ppc/23.0/musl", + "default/linux/ppc/23.0/musl/hardened", + "default/linux/ppc/23.0/split-usr/musl", + "default/linux/ppc/23.0/split-usr/musl/hardened", + "default/linux/ppc64/23.0/musl", + "default/linux/ppc64/23.0/musl/hardened", + "default/linux/ppc64/23.0/split-usr/musl", + "default/linux/ppc64/23.0/split-usr/musl/hardened", + "default/linux/ppc64le/23.0/musl", + "default/linux/ppc64le/23.0/musl/hardened", + "default/linux/ppc64le/23.0/split-usr/musl", + "default/linux/ppc64le/23.0/split-usr/musl/hardened", + "default/linux/riscv/23.0/rv64/lp64d/musl", + "default/linux/riscv/23.0/rv64/lp64/musl", + "default/linux/riscv/23.0/rv64/split-usr/lp64d/musl", + "default/linux/riscv/23.0/rv64/split-usr/lp64/musl", + "default/linux/riscv/23.0/rv32/ilp32d/musl", + "default/linux/riscv/23.0/rv32/ilp32/musl", + "default/linux/riscv/23.0/rv32/split-usr/ilp32d/musl", + "default/linux/riscv/23.0/rv32/split-usr/ilp32/musl", + "default/linux/x86/23.0/i686/musl", + "default/linux/x86/23.0/i686/musl/selinux", + "default/linux/x86/23.0/i686/split-usr/musl", + "default/linux/x86/23.0/i686/split-usr/musl/selinux", + "default/linux/x86/23.0/i486/musl", + "default/linux/x86/23.0/i486/musl/selinux", + "default/linux/x86/23.0/i486/split-usr/musl", + "default/linux/x86/23.0/i486/split-usr/musl/selinux", + "prefix/linux/amd64", + "prefix/linux/arm", + "prefix/linux/ppc64", + "prefix/linux/ppc64le", + "prefix/linux/riscv", + "prefix/linux/x86", + "prefix/darwin/macos/10.5/ppc/gcc", + "prefix/darwin/macos/10.5/x86/gcc", + "prefix/darwin/macos/10.11/x64", + "prefix/darwin/macos/10.13/x64", + "prefix/darwin/macos/10.13/x64/gcc", + "prefix/darwin/macos/10.14/x64", + "prefix/darwin/macos/10.14/x64/gcc", + "prefix/darwin/macos/10.15/x64", + "prefix/darwin/macos/10.15/x64/gcc", + "prefix/darwin/macos/11.0/x64", + "prefix/darwin/macos/11.0/x64/gcc", + "prefix/darwin/macos/12.0/x64", + "prefix/darwin/macos/12.0/x64/gcc", + "prefix/darwin/macos/13.0/x64/gcc", + "prefix/darwin/macos/14.0/x64/gcc", + "prefix/darwin/macos/15.0/x64/gcc", + "prefix/darwin/macos/26.0/x64/gcc", + "prefix/darwin/macos/11.0/arm64", + "prefix/darwin/macos/11.0/arm64/gcc", + "prefix/darwin/macos/12.0/arm64", + "prefix/darwin/macos/12.0/arm64/gcc", + "prefix/darwin/macos/13.0/arm64/gcc", + "prefix/darwin/macos/14.0/arm64/gcc", + "prefix/darwin/macos/15.0/arm64/gcc", + "prefix/darwin/macos/26.0/arm64/gcc", + "prefix/sunos/solaris/5.11/x64", + ]; + + let repo = Repo::new("/var/db/repos/gentoo").expect("failed to open repo"); + + for profile in profiles { + repo.evaluate_profile(profile) + .unwrap_or_else(|e| panic!("failed to evaluate profile: {profile}: {e}")); + } +} diff --git a/tests/profile/read_mock_profile.rs b/tests/profile/read_mock_profile.rs new file mode 100644 index 0000000..440dce2 --- /dev/null +++ b/tests/profile/read_mock_profile.rs @@ -0,0 +1,89 @@ +use std::env::args; + +use gentoo_utils::{atom::Atom, repo::Repo, useflag::UseFlag}; +use itertools::Itertools; + +fn main() { + let repo_path = args() + .nth(1) + .expect("expected path to mockrepo as first argument"); + let repo = Repo::new(&repo_path).expect("failed to read repo"); + + let global_package_mask = repo + .package_mask() + .iter() + .map(Atom::to_string) + .sorted() + .collect::>(); + + assert_eq!(global_package_mask, vec!["app-editors/vim"]); + + assert_eq!( + repo.arch_list() + .iter() + .map(|arch| arch.get()) + .collect::>(), + vec!["amd64", "aarch64"] + ); + + let profile = repo + .evaluate_profile("gentoo-desktop") + .expect("failed to evaluate profile"); + + let r#use = profile.make_defaults()["USE"] + .split_ascii_whitespace() + .sorted() + .collect::>(); + + assert_eq!(r#use, vec!["emacs", "selinux",]); + + let packages = profile + .packages() + .iter() + .map(Atom::to_string) + .sorted() + .collect::>(); + + assert_eq!( + packages, + vec!["app-editors/emacs", "sec-policy/selinux-base"] + ); + + let packages_mask = profile + .package_mask() + .iter() + .map(Atom::to_string) + .sorted() + .collect::>(); + + assert_eq!(packages_mask, vec!["app-editors/vim"]); + + let emacs_use = profile + .package_use() + .iter() + .find_map(|(atom, flags)| { + if atom.clone().into_cp().to_string() == "app-editors/emacs" { + Some(flags) + } else { + None + } + }) + .unwrap() + .iter() + .map(UseFlag::to_string) + .sorted() + .collect::>(); + + assert_eq!(emacs_use, vec!["gui"]); + + let use_force = profile + .use_force() + .iter() + .map(UseFlag::to_string) + .sorted() + .collect::>(); + + assert_eq!(use_force, vec!["base", "caps", "default", "gui"]); + + assert!(profile.use_mask().is_empty()); +} diff --git a/tests/repo/repo.rs b/tests/repo/repo.rs index 20b6980..cf1ffa2 100644 --- a/tests/repo/repo.rs +++ b/tests/repo/repo.rs @@ -3,7 +3,7 @@ use std::error::Error; use gentoo_utils::repo::Repo; fn main() -> Result<(), Box> { - let repo = Repo::new("/var/db/repos/gentoo"); + let repo = Repo::new("/var/db/repos/gentoo").unwrap(); for result in repo.categories()? { let cat = result?;