From 956fb629cbbe21cd19eaae1523a54a48b14abf6f Mon Sep 17 00:00:00 2001 From: John Turner Date: Sat, 29 Nov 2025 20:43:28 +0000 Subject: [PATCH 01/10] impl profile evaluation --- src/repo/mod.rs | 12 +- src/repo/profile/make_defaults/mod.rs | 135 ++++++ src/repo/profile/make_defaults/parsers.rs | 88 ++++ src/repo/profile/mod.rs | 157 +++++++ src/repo/profile/package/mod.rs | 95 +++++ src/repo/profile/package/parsers.rs | 13 + src/repo/profile/package_use/mod.rs | 125 ++++++ src/repo/profile/package_use/parsers.rs | 36 ++ src/repo/profile/packages/mod.rs | 75 ++++ src/repo/profile/packages/parsers.rs | 14 + src/repo/profile/parsers.rs | 35 ++ src/repo/profile/useflags/mod.rs | 94 +++++ tests/meson.build | 2 + tests/profile/meson.build | 1 + tests/profile/read_all_profiles.rs | 483 ++++++++++++++++++++++ 15 files changed, 1364 insertions(+), 1 deletion(-) create mode 100644 src/repo/profile/make_defaults/mod.rs create mode 100644 src/repo/profile/make_defaults/parsers.rs create mode 100644 src/repo/profile/mod.rs create mode 100644 src/repo/profile/package/mod.rs create mode 100644 src/repo/profile/package/parsers.rs create mode 100644 src/repo/profile/package_use/mod.rs create mode 100644 src/repo/profile/package_use/parsers.rs create mode 100644 src/repo/profile/packages/mod.rs create mode 100644 src/repo/profile/packages/parsers.rs create mode 100644 src/repo/profile/parsers.rs create mode 100644 src/repo/profile/useflags/mod.rs create mode 100644 tests/profile/meson.build create mode 100644 tests/profile/read_all_profiles.rs diff --git a/src/repo/mod.rs b/src/repo/mod.rs index eb68839..b039bd1 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -11,11 +11,15 @@ 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::Profile, + }, useflag::IUseFlag, }; pub mod ebuild; +pub mod profile; #[derive(Debug, thiserror::Error)] pub enum Error { @@ -27,6 +31,8 @@ pub enum Error { Unicode(PathBuf), #[error("parser error: {0}")] Parser(String), + #[error("profile error: {0}")] + Profile(profile::Error), } #[derive(Debug, Clone, Get)] @@ -63,6 +69,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 { 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/mod.rs b/src/repo/profile/mod.rs new file mode 100644 index 0000000..3edd170 --- /dev/null +++ b/src/repo/profile/mod.rs @@ -0,0 +1,157 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + io::{self, Read}, + path::{Path, PathBuf}, +}; + +use get::Get; +use itertools::Itertools; + +use crate::{atom::Atom, useflag::UseFlag}; + +mod make_defaults; +mod package; +mod package_use; +mod packages; +mod parsers; +mod useflags; + +#[derive(Debug, Clone)] +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), +} + +#[derive(Debug, Clone, Get)] +pub struct Profile { + #[get(kind = "deref")] + path: PathBuf, + #[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 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, + 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/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/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/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/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..2a2a541 --- /dev/null +++ b/tests/profile/meson.build @@ -0,0 +1 @@ +tests += {meson.current_source_dir() / 'read_all_profiles.rs': []} diff --git a/tests/profile/read_all_profiles.rs b/tests/profile/read_all_profiles.rs new file mode 100644 index 0000000..6295ec8 --- /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"); + + for profile in profiles { + repo.evaluate_profile(profile) + .unwrap_or_else(|e| panic!("failed to evaluate profile: {profile}: {e}")); + } +} -- 2.49.1 From 81a87015814d1b6f3a1e71974755ff6b24186c7e Mon Sep 17 00:00:00 2001 From: John Turner Date: Tue, 9 Dec 2025 22:15:08 +0000 Subject: [PATCH 02/10] add profile related source files to sources variable --- src/repo/meson.build | 1 + src/repo/profile/make_defaults/meson.build | 1 + src/repo/profile/meson.build | 6 ++++++ src/repo/profile/package/meson.build | 1 + src/repo/profile/package_use/meson.build | 1 + src/repo/profile/packages/meson.build | 1 + src/repo/profile/useflags/meson.build | 1 + 7 files changed, 12 insertions(+) create mode 100644 src/repo/profile/make_defaults/meson.build create mode 100644 src/repo/profile/meson.build create mode 100644 src/repo/profile/package/meson.build create mode 100644 src/repo/profile/package_use/meson.build create mode 100644 src/repo/profile/packages/meson.build create mode 100644 src/repo/profile/useflags/meson.build diff --git a/src/repo/meson.build b/src/repo/meson.build index c1be7a7..d59b73e 100644 --- a/src/repo/meson.build +++ b/src/repo/meson.build @@ -1,3 +1,4 @@ sources += files('mod.rs') subdir('ebuild') +subdir('profile') 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/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/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_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/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/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') -- 2.49.1 From 93fc514763864910b3aa3f5d2c3465e8aeaf456d Mon Sep 17 00:00:00 2001 From: John Turner Date: Thu, 11 Dec 2025 01:55:26 +0000 Subject: [PATCH 03/10] read eapi file in profiles --- src/repo/profile/mod.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs index 3edd170..edbc880 100644 --- a/src/repo/profile/mod.rs +++ b/src/repo/profile/mod.rs @@ -8,7 +8,7 @@ use std::{ use get::Get; use itertools::Itertools; -use crate::{atom::Atom, useflag::UseFlag}; +use crate::{Parseable, atom::Atom, repo::ebuild::Eapi, useflag::UseFlag}; mod make_defaults; mod package; @@ -43,12 +43,15 @@ pub enum Error { 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: Option, #[get(kind = "deref")] parents: Vec, make_defaults: HashMap, @@ -87,6 +90,16 @@ impl Profile { 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)) => Some(eapi), + Ok(Err(rest)) => return Err(Error::Parser(rest)), + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => None, + Err(e) => return Err(Error::Io(eapi_path, e)), + }; + let make_defaults = make_defaults::evaluate(&parents, &path)?; let packages = packages::evaluate(&parents, &path)?; @@ -110,6 +123,7 @@ impl Profile { Ok(Self { path: path.as_ref().to_path_buf(), parents, + eapi, make_defaults, packages, package_mask, -- 2.49.1 From 18b7a468b2c8b0ecb6d502352940f2c840cd4809 Mon Sep 17 00:00:00 2001 From: John Turner Date: Thu, 11 Dec 2025 02:00:40 +0000 Subject: [PATCH 04/10] read deprecated file in profiles --- src/repo/profile/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs index edbc880..4736444 100644 --- a/src/repo/profile/mod.rs +++ b/src/repo/profile/mod.rs @@ -52,6 +52,7 @@ pub struct Profile { #[get(kind = "deref")] path: PathBuf, eapi: Option, + deprecated: Option, #[get(kind = "deref")] parents: Vec, make_defaults: HashMap, @@ -100,6 +101,13 @@ impl Profile { 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)?; @@ -124,6 +132,7 @@ impl Profile { path: path.as_ref().to_path_buf(), parents, eapi, + deprecated, make_defaults, packages, package_mask, -- 2.49.1 From c34afdbf1407fc385f080e205d4bcda165ce698c Mon Sep 17 00:00:00 2001 From: John Turner Date: Thu, 11 Dec 2025 02:42:09 +0000 Subject: [PATCH 05/10] add docs to profile module --- src/repo/profile/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs index 4736444..31861b9 100644 --- a/src/repo/profile/mod.rs +++ b/src/repo/profile/mod.rs @@ -1,3 +1,16 @@ +//! Evaluate profiles: +//! ```rust +//! use gentoo_utils::repo::Repo; +//! +//! let repo = Repo::new("/var/db/repos/gentoo"); +//! 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}, -- 2.49.1 From 7e61192fbac6fbb20f9ac395f391f280021778a0 Mon Sep 17 00:00:00 2001 From: John Turner Date: Thu, 11 Dec 2025 23:50:12 +0000 Subject: [PATCH 06/10] default to Eapi 0 if no eapi file exists --- src/repo/profile/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs index 31861b9..8bdb724 100644 --- a/src/repo/profile/mod.rs +++ b/src/repo/profile/mod.rs @@ -64,7 +64,7 @@ pub enum Error { pub struct Profile { #[get(kind = "deref")] path: PathBuf, - eapi: Option, + eapi: Eapi, deprecated: Option, #[get(kind = "deref")] parents: Vec, @@ -108,9 +108,9 @@ impl Profile { let eapi = match fs::read_to_string(&eapi_path) .map(|s| Eapi::parse(s.trim()).map_err(str::to_string)) { - Ok(Ok(eapi)) => Some(eapi), + Ok(Ok(eapi)) => eapi, Ok(Err(rest)) => return Err(Error::Parser(rest)), - Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => None, + Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => Eapi::parse("0").unwrap(), Err(e) => return Err(Error::Io(eapi_path, e)), }; -- 2.49.1 From 106e8de849cfbba25115fcad2161c19b2064eeef Mon Sep 17 00:00:00 2001 From: John Turner Date: Fri, 12 Dec 2025 01:59:57 +0000 Subject: [PATCH 07/10] read repo_name when opening repos --- fuzz/atom/parser/gencorpus.rs | 2 +- fuzz/atom/vercmp/gencorpus.rs | 2 +- src/lib.rs | 3 ++- src/repo/mod.rs | 20 +++++++++++++++++--- src/repo/profile/mod.rs | 3 ++- tests/profile/read_all_profiles.rs | 2 +- tests/repo/repo.rs | 2 +- 7 files changed, 25 insertions(+), 9 deletions(-) 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/mod.rs b/src/repo/mod.rs index b039bd1..cbfa87a 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -23,6 +23,8 @@ 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}")] @@ -39,6 +41,8 @@ pub enum Error { pub struct Repo { #[get(kind = "deref")] path: PathBuf, + #[get(kind = "deref")] + name: String, } #[derive(Debug, Clone, Get)] @@ -55,10 +59,20 @@ 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)), + }; + + Ok(Self { path: path.as_ref().to_path_buf(), - } + name, + }) } pub fn categories(&self) -> Result { diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs index 8bdb724..28c6b06 100644 --- a/src/repo/profile/mod.rs +++ b/src/repo/profile/mod.rs @@ -2,7 +2,8 @@ //! ```rust //! 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"); //! let profile = repo.evaluate_profile("default/linux/23.0") //! .expect("failed to evaluate profile"); //! diff --git a/tests/profile/read_all_profiles.rs b/tests/profile/read_all_profiles.rs index 6295ec8..4a214a0 100644 --- a/tests/profile/read_all_profiles.rs +++ b/tests/profile/read_all_profiles.rs @@ -474,7 +474,7 @@ fn main() { "prefix/sunos/solaris/5.11/x64", ]; - let repo = Repo::new("/var/db/repos/gentoo"); + let repo = Repo::new("/var/db/repos/gentoo").expect("failed to open repo"); for profile in profiles { repo.evaluate_profile(profile) 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?; -- 2.49.1 From fd3efced2991852375b719bdb321c7ac24a488e4 Mon Sep 17 00:00:00 2001 From: John Turner Date: Fri, 12 Dec 2025 04:15:12 +0000 Subject: [PATCH 08/10] add mockrepo tests --- tests/profile/meson.build | 6 ++ .../mockrepo/profiles/base/make.defaults | 1 + .../profile/mockrepo/profiles/base/use.force | 1 + .../profiles/features/emacs/gui/package.use | 1 + .../profiles/features/emacs/gui/parent | 1 + .../profiles/features/emacs/gui/use.force | 1 + .../profiles/features/emacs/make.defaults | 1 + .../profiles/features/emacs/package.mask | 1 + .../profiles/features/emacs/package.use | 1 + .../mockrepo/profiles/features/emacs/packages | 1 + .../profiles/features/emacs/use.force | 1 + .../profiles/features/selinux/make.defaults | 2 + .../profiles/features/selinux/packages | 1 + .../profiles/features/selinux/use.force | 1 + .../profiles/features/selinux/use.mask | 1 + .../profiles/gentoo-desktop/make.defaults | 1 + .../profiles/gentoo-desktop/package.use | 1 + .../mockrepo/profiles/gentoo-desktop/parent | 3 + .../mockrepo/profiles/gentoo-desktop/use.mask | 1 + tests/profile/mockrepo/profiles/repo_name | 1 + tests/profile/read_mock_profile.rs | 71 +++++++++++++++++++ 21 files changed, 99 insertions(+) create mode 100644 tests/profile/mockrepo/profiles/base/make.defaults create mode 100644 tests/profile/mockrepo/profiles/base/use.force create mode 100644 tests/profile/mockrepo/profiles/features/emacs/gui/package.use create mode 100644 tests/profile/mockrepo/profiles/features/emacs/gui/parent create mode 100644 tests/profile/mockrepo/profiles/features/emacs/gui/use.force create mode 100644 tests/profile/mockrepo/profiles/features/emacs/make.defaults create mode 100644 tests/profile/mockrepo/profiles/features/emacs/package.mask create mode 100644 tests/profile/mockrepo/profiles/features/emacs/package.use create mode 100644 tests/profile/mockrepo/profiles/features/emacs/packages create mode 100644 tests/profile/mockrepo/profiles/features/emacs/use.force create mode 100644 tests/profile/mockrepo/profiles/features/selinux/make.defaults create mode 100644 tests/profile/mockrepo/profiles/features/selinux/packages create mode 100644 tests/profile/mockrepo/profiles/features/selinux/use.force create mode 100644 tests/profile/mockrepo/profiles/features/selinux/use.mask create mode 100644 tests/profile/mockrepo/profiles/gentoo-desktop/make.defaults create mode 100644 tests/profile/mockrepo/profiles/gentoo-desktop/package.use create mode 100644 tests/profile/mockrepo/profiles/gentoo-desktop/parent create mode 100644 tests/profile/mockrepo/profiles/gentoo-desktop/use.mask create mode 100644 tests/profile/mockrepo/profiles/repo_name create mode 100644 tests/profile/read_mock_profile.rs diff --git a/tests/profile/meson.build b/tests/profile/meson.build index 2a2a541..47ab3bb 100644 --- a/tests/profile/meson.build +++ b/tests/profile/meson.build @@ -1 +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/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/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_mock_profile.rs b/tests/profile/read_mock_profile.rs new file mode 100644 index 0000000..82c3c1c --- /dev/null +++ b/tests/profile/read_mock_profile.rs @@ -0,0 +1,71 @@ +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 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()); +} -- 2.49.1 From 4969177893027037e48eba3110544d97bb84caaa Mon Sep 17 00:00:00 2001 From: John Turner Date: Sat, 13 Dec 2025 04:49:28 +0000 Subject: [PATCH 09/10] read global package.mask --- src/repo/mod.rs | 28 +++++++++++++++++++- src/repo/profile/mod.rs | 2 +- tests/profile/mockrepo/profiles/package.mask | 1 + tests/profile/read_mock_profile.rs | 10 +++++++ 4 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 tests/profile/mockrepo/profiles/package.mask diff --git a/src/repo/mod.rs b/src/repo/mod.rs index cbfa87a..250825b 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -13,7 +13,7 @@ use crate::{ atom::{self, Atom}, repo::{ ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri}, - profile::Profile, + profile::{LineBasedFileExpr, Profile}, }, useflag::IUseFlag, }; @@ -43,6 +43,8 @@ pub struct Repo { path: PathBuf, #[get(kind = "deref")] name: String, + #[get(kind = "deref")] + package_mask: Vec, } #[derive(Debug, Clone, Get)] @@ -69,9 +71,19 @@ impl Repo { 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)), + }; + Ok(Self { path: path.as_ref().to_path_buf(), name, + package_mask, }) } @@ -340,6 +352,20 @@ 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 parse_depends(line: &str) -> Result>, Error> { Depend::::parser() .separated_by(ascii_whitespace1()) diff --git a/src/repo/profile/mod.rs b/src/repo/profile/mod.rs index 28c6b06..37909c7 100644 --- a/src/repo/profile/mod.rs +++ b/src/repo/profile/mod.rs @@ -32,7 +32,7 @@ mod parsers; mod useflags; #[derive(Debug, Clone)] -enum LineBasedFileExpr { +pub(super) enum LineBasedFileExpr { Comment, Expr(T), } 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/read_mock_profile.rs b/tests/profile/read_mock_profile.rs index 82c3c1c..da50216 100644 --- a/tests/profile/read_mock_profile.rs +++ b/tests/profile/read_mock_profile.rs @@ -8,6 +8,16 @@ fn main() { .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"]); + let profile = repo .evaluate_profile("gentoo-desktop") .expect("failed to evaluate profile"); -- 2.49.1 From 789a68ff36fffd3d25b203dc25de20c06ae16d93 Mon Sep 17 00:00:00 2001 From: John Turner Date: Sat, 13 Dec 2025 05:36:26 +0000 Subject: [PATCH 10/10] read arch.list --- src/repo/meson.build | 2 +- src/repo/mod.rs | 29 +++++++++++++++++++++++ src/repo/parsers.rs | 20 ++++++++++++++++ tests/profile/mockrepo/profiles/arch.list | 2 ++ tests/profile/read_mock_profile.rs | 8 +++++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/repo/parsers.rs create mode 100644 tests/profile/mockrepo/profiles/arch.list diff --git a/src/repo/meson.build b/src/repo/meson.build index d59b73e..e706ad9 100644 --- a/src/repo/meson.build +++ b/src/repo/meson.build @@ -1,4 +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 250825b..0443afc 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -19,6 +19,7 @@ use crate::{ }; pub mod ebuild; +mod parsers; pub mod profile; #[derive(Debug, thiserror::Error)] @@ -37,6 +38,9 @@ pub enum Error { 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")] @@ -45,6 +49,8 @@ pub struct Repo { name: String, #[get(kind = "deref")] package_mask: Vec, + #[get(kind = "deref")] + arch_list: Vec, } #[derive(Debug, Clone, Get)] @@ -80,10 +86,19 @@ impl Repo { 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, }) } @@ -366,6 +381,20 @@ fn read_package_mask(input: &str) -> Result, Error> { .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/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/read_mock_profile.rs b/tests/profile/read_mock_profile.rs index da50216..440dce2 100644 --- a/tests/profile/read_mock_profile.rs +++ b/tests/profile/read_mock_profile.rs @@ -18,6 +18,14 @@ fn main() { 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"); -- 2.49.1