use std::collections::HashMap; use std::{fmt, mem}; use super::pattern::r#match; use super::{CommandLine, ItemSource, MacroSet}; use eyre::{eyre, OptionExt, Result}; use regex::Captures; #[derive(Clone, Debug)] pub struct InferenceRule { pub source: ItemSource, pub products: Vec, pub prerequisites: Vec, pub commands: Vec, pub macros: MacroSet, } impl InferenceRule { /// s1 is the product, s2 is the prereq pub fn new_suffix( source: ItemSource, s1: String, s2: String, commands: Vec, macros: MacroSet, ) -> Self { Self { source, products: vec![format!("%{}", s1)], prerequisites: vec![format!("%{}", s2)], commands, macros, } } pub fn first_match<'s, 't: 's>(&'s self, target_name: &'t str) -> Result>> { self.products .iter() .map(|pattern| { // TODO find a better way to make the self_subdir_match test pass r#match(pattern.strip_prefix("./").unwrap_or(pattern), target_name) }) .try_fold(None, |x, y| y.map(|y| x.or(y))) } pub fn matches(&self, target_name: &str) -> Result { self.first_match(target_name).map(|x| x.is_some()) } pub fn prereqs<'s>( &'s self, target_name: &'s str, ) -> Result + 's> { let capture = self .first_match(target_name)? .ok_or_else(|| eyre!("asked non-matching inference rule for prerequisites"))?; let percent_expansion = capture .get(1) .ok_or_eyre("should've matched the %")? .as_str(); Ok(self .prerequisites .iter() .map(move |p| p.replace('%', percent_expansion))) } fn extend(&mut self, other: Self) { assert_eq!(&self.products, &other.products); match (self.commands.is_empty(), other.commands.is_empty()) { (false, false) => { // both rules have commands, so replace this entirely *self = other; } (true, false) => { // this rule doesn't have commands, but the other one does, // so it's the real one let mut other = other; mem::swap(self, &mut other); self.extend(other); } (false, true) | (true, true) => { // this rule might have commands, but the other one doesn't, // so append non-command stuff // TODO decide something smart about sources self.prerequisites.extend(other.prerequisites); self.macros.extend(other.macros); } } } } impl fmt::Display for InferenceRule { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!( f, "{}: {}", self.products.join(" "), self.prerequisites.join(" ") )?; for command in &self.commands { writeln!(f, "\t{}", command)?; } Ok(()) } } #[derive(Clone, Default)] pub struct InferenceRuleSet { /// Maps from products to a map from prerequisites to rules. data: HashMap, HashMap, InferenceRule>>, } impl InferenceRuleSet { pub fn get(&self, products: &[String], prerequisites: &[String]) -> Option<&InferenceRule> { self.data.get(products).and_then(|x| x.get(prerequisites)) } fn get_mut( &mut self, products: &[String], prerequisites: &[String], ) -> Option<&mut InferenceRule> { self.data .get_mut(products) .and_then(|x| x.get_mut(prerequisites)) } pub fn put(&mut self, rule: InferenceRule) { if let Some(existing_rule) = self.get_mut(&rule.products, &rule.prerequisites) { existing_rule.extend(rule); } else { self.data .entry(rule.products.clone()) .or_default() .insert(rule.prerequisites.clone(), rule); } } pub fn len(&self) -> usize { self.data.len() } pub fn extend(&mut self, other: Self) { for other in other.data.into_values().flat_map(HashMap::into_values) { self.put(other); } } pub fn iter(&self) -> impl Iterator { self.data.values().flat_map(HashMap::values) } } impl From> for InferenceRuleSet { fn from(value: Vec) -> Self { let mut result = Self::default(); for rule in value { result.put(rule); } result } } #[cfg(test)] mod test { use super::*; type R = Result<()>; #[test] fn suffix_match() -> R { let rule = InferenceRule::new_suffix( ItemSource::Builtin, ".o".to_owned(), ".c".to_owned(), vec![], MacroSet::new(), ); assert!(rule.matches("foo.o")?); assert!(rule.matches("dir/foo.o")?); Ok(()) } #[cfg(feature = "full")] #[test] fn percent_match() -> R { // thanks, SPDX License List let rule = InferenceRule { source: ItemSource::Builtin, products: vec!["licenseListPublisher-%.jar-valid".to_owned()], prerequisites: vec![ "licenseListPublisher-%.jar.asc".to_owned(), "licenseListPublisher-%.jar".to_owned(), "goneall.gpg".to_owned(), ], commands: vec![], macros: MacroSet::new(), }; assert!(rule.matches("licenseListPublisher-2.2.1.jar-valid")?); Ok(()) } #[cfg(feature = "full")] #[test] fn subdir_match() -> R { let rule = InferenceRule { source: ItemSource::Builtin, products: vec!["a/%.o".to_owned()], prerequisites: vec!["a/%.c".to_owned()], commands: vec![], macros: MacroSet::new(), }; assert!(rule.matches("a/foo.o")?); Ok(()) } #[cfg(feature = "full")] #[test] fn self_subdir_match() -> R { let rule = InferenceRule { source: ItemSource::Builtin, products: vec!["./%.o".to_owned()], prerequisites: vec!["./%.c".to_owned()], commands: vec![], macros: MacroSet::new(), }; assert!(rule.matches("foo.o")?); Ok(()) } }