From 4f9299b4639802e05e1cb27d8eb40305ff8e110e Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Mon, 11 Nov 2024 17:15:08 -0700 Subject: implement rule-specific macros for targets --- src/makefile/inference_rules.rs | 97 +++++++++++- src/makefile/input.rs | 324 +++++++++++++++++++++++++++++++--------- src/makefile/macro.rs | 32 ++-- src/makefile/macro_scope.rs | 6 + src/makefile/mod.rs | 37 +++-- src/makefile/parse.rs | 19 +++ src/makefile/target.rs | 6 +- src/makefile/token.rs | 11 ++ 8 files changed, 431 insertions(+), 101 deletions(-) create mode 100644 src/makefile/parse.rs (limited to 'src/makefile') diff --git a/src/makefile/inference_rules.rs b/src/makefile/inference_rules.rs index 8d4f3b6..d7db1e6 100644 --- a/src/makefile/inference_rules.rs +++ b/src/makefile/inference_rules.rs @@ -1,18 +1,18 @@ -use std::fmt; +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; -use super::command_line::CommandLine; -use super::pattern::r#match; -use super::ItemSource; - -#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(Clone, Debug)] pub struct InferenceRule { pub source: ItemSource, pub products: Vec, pub prerequisites: Vec, pub commands: Vec, + pub macros: MacroSet, } impl InferenceRule { @@ -22,12 +22,14 @@ impl InferenceRule { s1: String, s2: String, commands: Vec, + macros: MacroSet, ) -> Self { Self { source, products: vec![format!("%{}", s1)], prerequisites: vec![format!("%{}", s2)], commands, + macros, } } @@ -58,6 +60,30 @@ impl InferenceRule { .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 { @@ -75,6 +101,63 @@ impl fmt::Display for InferenceRule { } } +#[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::*; @@ -88,6 +171,7 @@ mod test { ".o".to_owned(), ".c".to_owned(), vec![], + MacroSet::new(), ); assert!(rule.matches("foo.o")?); assert!(rule.matches("dir/foo.o")?); @@ -107,6 +191,7 @@ mod test { "goneall.gpg".to_owned(), ], commands: vec![], + macros: MacroSet::new(), }; assert!(rule.matches("licenseListPublisher-2.2.1.jar-valid")?); Ok(()) diff --git a/src/makefile/input.rs b/src/makefile/input.rs index 6333a66..9fdcf6a 100644 --- a/src/makefile/input.rs +++ b/src/makefile/input.rs @@ -17,18 +17,21 @@ use crate::args::Args; use super::conditional::{Line as ConditionalLine, State as ConditionalState}; #[cfg(feature = "full")] use super::eval_context::DeferredEvalContext; +use super::parse::{MacroAssignment, MacroAssignmentOutcome}; #[cfg(feature = "full")] use super::r#macro::ExportConfig; use super::r#macro::Macro; use super::target::StaticTargetSet; use super::token::Token; use super::{ - builtin_targets, CommandLine, InferenceRule, ItemSource, LookupInternal, MacroScopeStack, - MacroSet, Target, TokenString, + builtin_targets, CommandLine, InferenceRule, InferenceRuleSet, ItemSource, LookupInternal, + MacroScopeStack, MacroSet, Target, TokenString, }; enum LineType { Rule, + #[cfg(feature = "full")] + RuleMacro, Macro, Include, #[cfg(feature = "full")] @@ -70,6 +73,9 @@ impl LineType { return Self::Rule; } (Some(c), Some(e)) if c < e => { + #[cfg(feature = "full")] + return Self::RuleMacro; + #[cfg(not(feature = "full"))] return Self::Rule; } (None, Some(_)) => { @@ -187,7 +193,7 @@ impl Default for NextLineSettings { pub struct MakefileReader<'a, 'parent, R: BufRead> { file_name: String, - pub inference_rules: Vec, + pub inference_rules: InferenceRuleSet, pub stack: MacroScopeStack<'parent>, pub macros: MacroSet, pub targets: StaticTargetSet, @@ -252,7 +258,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { let name = name.into(); let mut reader = Self { file_name: name.clone(), - inference_rules: Vec::new(), + inference_rules: InferenceRuleSet::default(), stack, macros, targets: Default::default(), @@ -324,6 +330,16 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { ) })?; } + #[cfg(feature = "full")] + LineType::RuleMacro => { + self.read_rule_macro(line_tokens, line_number) + .wrap_err_with(|| { + format!( + "while parsing rule-specific macro definition starting on line {}", + line_number + ) + })?; + } LineType::Macro => { self.read_macro(line_tokens, line_number) .wrap_err_with(|| { @@ -590,8 +606,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { None => (not_targets, vec![]), }; if prerequisites.contains_text("=") { - log::error!("rule-specific macros are not implemented yet"); - return Ok(()); + bail!("handling rule-specific macro as rule"); } #[cfg(feature = "full")] let mut deferred_eval_context = DeferredEvalContext::new(self); @@ -661,6 +676,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { products: targets.into_iter().map(|x| x.to_owned()).collect(), prerequisites, commands, + macros: MacroSet::new(), }; if let Some(static_targets) = static_targets { @@ -676,12 +692,13 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { .first_match(real_target)? .and_then(|x| x.get(1).map(|x| x.as_str().to_owned())), already_updated: Cell::new(false), + macros: MacroSet::new(), }; self.targets.put(new_target); } } } else { - self.inference_rules.push(new_rule); + self.inference_rules.put(new_rule); } return Ok(()); } @@ -717,6 +734,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { inference_match.s1.to_owned(), inference_match.s2.to_owned(), commands, + MacroSet::new(), ); log::trace!( "suffix-based inference rule defined by {:?} - {:?}", @@ -724,11 +742,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { &new_rule, ); - self.inference_rules.retain(|existing_rule| { - (&existing_rule.prerequisites, &existing_rule.products) - != (&new_rule.prerequisites, &new_rule.products) - }); - self.inference_rules.push(new_rule); + self.inference_rules.put(new_rule); } else { log::trace!( "{}:{}: new target {:?} based on {:?}", @@ -748,6 +762,135 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { commands: commands.clone(), stem: None, already_updated: Cell::new(false), + macros: MacroSet::new(), + }; + self.targets.put(new_target); + } + } + + Ok(()) + } + + #[cfg(feature = "full")] + fn read_rule_macro(&mut self, line_tokens: TokenString, line_number: usize) -> Result<()> { + let (targets, macro_def) = line_tokens + .split_once(":") + .ok_or_else(|| eyre!("read_rule couldn't find a ':' on line {}", line_number))?; + lazy_static! { + // my kingdom for lookahead + static ref NON_EAGER_EXPANSION_ASSIGNMENT_COLON: Regex = #[allow(clippy::unwrap_used)] Regex::new(":[^:=]").unwrap(); + } + if macro_def.matches_regex(&NON_EAGER_EXPANSION_ASSIGNMENT_COLON) { + bail!("GNUful static patterns not yet implemented in rule-specific macros"); + }; + let targets = self.expand_macros(&targets)?; + let targets = targets.split_whitespace().collect::>(); + + let (name, value) = macro_def + .split_once("=") + .ok_or_else(|| eyre!("read_macro couldn't find a '=' on line {}", line_number))?; + let macro_assignment = self.parse_macro_assignment(name, value, line_number)?; + + if targets.is_empty() { + return Ok(()); + } + + // we don't know yet if it's a target rule or an inference rule (or a GNUish "pattern rule") + let inference_match = inference_match(&targets, &[]); + let is_pattern = targets.iter().all(|x| x.contains('%')); + + // TODO resolve against existing stack + let mut macro_set = MacroSet::new(); + if let Some(outcome) = self + .check_macro_assignment_outcome(¯o_assignment, self.stack.with_scope(&self.macros)) + { + let (name, value) = self.resolve_macro_value(macro_assignment, outcome, line_number)?; + // TODO trace + macro_set.set(name, value); + } + + if is_pattern { + let new_rule = InferenceRule { + source: ItemSource::File { + name: self.file_name.clone(), + line: line_number, + }, + products: targets.into_iter().map(|x| x.to_owned()).collect(), + prerequisites: vec![], + commands: vec![], + macros: macro_set, + }; + + log::error!( + "{}:{}: inference rule specific macros not yet working", + &self.file_name, + line_number + ); + + self.inference_rules.put(new_rule); + return Ok(()); + } + + // don't interpret things like `.tmp: ; mkdir -p $@` as single-suffix rules + let inference_match = inference_match.and_then(|inference| { + if self.special_target_has_prereq(".SUFFIXES", inference.s1, false) + && (inference.s2.is_empty() + || self.special_target_has_prereq(".SUFFIXES", inference.s2, false)) + { + Some(inference) + } else { + log::info!( + "{}:{}: looks like {:?} is not a suffix rule because .SUFFIXES is {:?}", + &self.file_name, + line_number, + inference, + self.targets + .get(".SUFFIXES") + .or_else(|| self.built_in_targets.get(".SUFFIXES")) + .map(|x| &x.prerequisites) + ); + None + } + }); + + if let Some(inference_match) = inference_match { + let new_rule = InferenceRule::new_suffix( + ItemSource::File { + name: self.file_name.clone(), + line: line_number, + }, + inference_match.s1.to_owned(), + inference_match.s2.to_owned(), + vec![], + macro_set, + ); + log::error!( + "{}:{}: inference rule specific macros not yet working", + &self.file_name, + line_number + ); + + self.inference_rules.put(new_rule); + } else { + log::trace!( + "{}:{}: target {:?} gets macros {:?}", + &self.file_name, + line_number, + &targets, + ¯o_set + ); + for target in targets { + if self.first_non_special_target.is_none() && !target.starts_with('.') { + self.first_non_special_target = Some(target.into()); + } + // TODO handle appending to built-in (it's Complicated) + let new_target = Target { + name: target.into(), + prerequisites: vec![], + commands: vec![], + stem: None, + already_updated: Cell::new(false), + macros: macro_set.clone(), }; self.targets.put(new_target); } @@ -758,7 +901,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { /// If successful, returns the name of the macro which was read. fn read_macro(&mut self, mut line_tokens: TokenString, line_number: usize) -> Result { - let (name, mut value) = if cfg!(feature = "full") && line_tokens.starts_with("define ") { + let (name, value) = if cfg!(feature = "full") && line_tokens.starts_with("define ") { line_tokens.strip_prefix("define "); if line_tokens.ends_with("=") { line_tokens.strip_suffix("="); @@ -786,10 +929,36 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { .split_once("=") .ok_or_else(|| eyre!("read_macro couldn't find a '=' on line {}", line_number))? }; + let macro_assignment = self.parse_macro_assignment(name, value, line_number)?; + let macro_name = macro_assignment.name.clone(); + if let Some(outcome) = self + .check_macro_assignment_outcome(¯o_assignment, self.stack.with_scope(&self.macros)) + { + let (name, value) = self.resolve_macro_value(macro_assignment, outcome, line_number)?; + log::trace!( + "{}:{}: setting macro {} to {:?}", + &self.file_name, + line_number, + &name, + &value + ); + self.macros.set(name, value); + } + Ok(macro_name) + } + + fn parse_macro_assignment( + &mut self, + name: TokenString, + mut value: TokenString, + line_number: usize, + ) -> Result { let name = self.expand_macros(&name)?; - // GNUisms are annoying, but popular + #[cfg(feature = "full")] let mut expand_value = false; + #[cfg(feature = "full")] let mut skip_if_defined = false; + #[cfg(feature = "full")] let mut append = false; #[cfg(feature = "full")] @@ -821,59 +990,85 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { value }; - let skipped = match self - .stack - .with_scope(&self.macros) - .get(name) - .map(|x| x.source.clone()) - { + Ok(MacroAssignment { + name: name.to_owned(), + value, + #[cfg(feature = "full")] + expand_value, + #[cfg(feature = "full")] + skip_if_defined, + #[cfg(feature = "full")] + append, + }) + } + + /// For aliasing reasons, applying a macro assignment is done in three steps: + /// 1. Determine what the assignment will do, and if it will append, fetch and clone the original value. Reads both self and the stack. + /// 2. Resolve the new value of the macro, eagerly expanding if appending to an eagerly expanded macro. May write to self due to eval, and since the stack will include `&self.macros`, must be separate from 1. + /// 3. Actually perform the assignment in a [MacroSet]. Since the [MacroSet] may be `&self.macros`, must be separate from 1 and 2. + fn check_macro_assignment_outcome( + &self, + macro_assignment: &MacroAssignment, + stack: MacroScopeStack, + ) -> Option { + let skipped = match stack.get(¯o_assignment.name).map(|x| x.source.clone()) { // We always let command line or MAKEFLAGS macros override macros from the file. Some(ItemSource::CommandLineOrMakeflags) => true, // We let environment variables override macros from the file only if the command-line argument to do that was given Some(ItemSource::Environment) => self.args.environment_overrides, - Some(_) => skip_if_defined, + Some(_) => macro_assignment.skip_if_defined, None => false, }; if skipped { - return Ok(name.to_owned()); + None + } else { + Some(match stack.get(¯o_assignment.name) { + Some(old_value) if macro_assignment.append => { + MacroAssignmentOutcome::AppendedTo(old_value.into_owned()) + } + _ => MacroAssignmentOutcome::Set, + }) } + } - log::trace!( - "{}:{}: setting macro {} to {}", - &self.file_name, - line_number, - name, - &value - ); - - let value = match self.stack.with_scope(&self.macros).get(name) { - Some(old_value) if append => { - let mut old_value = old_value.into_owned(); + fn resolve_macro_value( + &mut self, + macro_assignment: MacroAssignment, + outcome: MacroAssignmentOutcome, + line_number: usize, + ) -> Result<(String, Macro)> { + match outcome { + MacroAssignmentOutcome::AppendedTo(mut old_value) => { #[cfg(feature = "full")] let value = if old_value.eagerly_expanded { - TokenString::text(self.expand_macros(&value).wrap_err_with(|| { - format!("while defining {} on line {}", name, line_number) - })?) + TokenString::text(self.expand_macros(¯o_assignment.value).wrap_err_with( + || { + format!( + "while defining {} on line {}", + macro_assignment.name, line_number + ) + }, + )?) } else { - value + macro_assignment.value }; old_value.text.extend(TokenString::text(" ")); old_value.text.extend(value); - old_value + Ok((macro_assignment.name, old_value)) } - _ => Macro { - source: ItemSource::File { - name: self.file_name.clone(), - line: line_number, + MacroAssignmentOutcome::Set => Ok(( + macro_assignment.name, + Macro { + source: ItemSource::File { + name: self.file_name.clone(), + line: line_number, + }, + text: macro_assignment.value, + #[cfg(feature = "full")] + eagerly_expanded: macro_assignment.expand_value, }, - text: value, - #[cfg(feature = "full")] - eagerly_expanded: expand_value, - }, - }; - self.macros.set(name.into(), value); - - Ok(name.to_owned()) + )), + } } fn expand_macros(&mut self, text: &TokenString) -> Result { @@ -909,9 +1104,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { pub fn finish(self) -> FinishedMakefileReader { FinishedMakefileReader { inference_rules: self.inference_rules, - macros: self.macros.data, - #[cfg(feature = "full")] - macro_exports: self.macros.exported, + macros: self.macros, targets: self.targets.into(), first_non_special_target: self.first_non_special_target, failed_includes: self.failed_includes, @@ -920,11 +1113,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { fn extend(&mut self, new: FinishedMakefileReader) { self.inference_rules.extend(new.inference_rules); - self.macros.extend( - new.macros, - #[cfg(feature = "full")] - new.macro_exports, - ); + self.macros.extend(new.macros); for (_, target) in new.targets { self.targets.put(target); } @@ -936,10 +1125,8 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { } pub struct FinishedMakefileReader { - pub inference_rules: Vec, - pub macros: HashMap, - #[cfg(feature = "full")] - pub macro_exports: ExportConfig, + pub inference_rules: InferenceRuleSet, + pub macros: MacroSet, pub targets: HashMap, pub first_non_special_target: Option, pub failed_includes: Vec, @@ -1158,7 +1345,8 @@ info: prerequisites: vec!["bar".to_owned(), "baz".to_owned()], commands: vec![], stem: None, - already_updated: Cell::new(false) + already_updated: Cell::new(false), + macros: MacroSet::new(), } ); assert_eq!( @@ -1168,7 +1356,8 @@ info: prerequisites: vec!["test#post".to_owned()], commands: vec![], stem: None, - already_updated: Cell::new(false) + already_updated: Cell::new(false), + macros: MacroSet::new(), } ); assert_eq!( @@ -1178,7 +1367,8 @@ info: prerequisites: vec![], commands: vec![CommandLine::from(TokenString::text("hello # there")),], stem: None, - already_updated: Cell::new(false) + already_updated: Cell::new(false), + macros: MacroSet::new(), } ); @@ -1271,11 +1461,11 @@ test: c )?; let makefile = makefile.finish(); assert_eq!( - makefile.macros.get("x").map(|x| &x.text), + makefile.macros.get_non_recursive("x").map(|x| &x.text), Some(&TokenString::text("3")) ); assert!( - matches!(makefile.macro_exports, ExportConfig::Only(exported) if exported.contains("x")) + matches!(makefile.macros.exported, ExportConfig::Only(exported) if exported.contains("x")) ); Ok(()) } diff --git a/src/makefile/macro.rs b/src/makefile/macro.rs index b2a1510..3b472a3 100644 --- a/src/makefile/macro.rs +++ b/src/makefile/macro.rs @@ -14,7 +14,7 @@ use super::{ItemSource, TokenString}; #[cfg(feature = "full")] use eyre::Result; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Macro { pub source: ItemSource, pub text: TokenString, @@ -23,7 +23,7 @@ pub struct Macro { } #[cfg(feature = "full")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ExportConfig { Only(HashSet), AllBut(HashSet), @@ -73,7 +73,7 @@ impl ExportConfig { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Set { pub data: HashMap, #[cfg(feature = "full")] @@ -128,13 +128,9 @@ impl Set { self.data.insert(name, r#macro); } - pub fn extend( - &mut self, - other: HashMap, - #[cfg(feature = "full")] other_exports: ExportConfig, - ) { + pub fn extend(&mut self, other: Self) { #[cfg(feature = "full")] - match (&mut self.exported, other_exports) { + match (&mut self.exported, other.exported) { (ExportConfig::Only(se), ExportConfig::Only(oe)) => { se.extend(oe); } @@ -142,13 +138,25 @@ impl Set { sne.extend(one); } (ExportConfig::Only(se), ExportConfig::AllBut(one)) => { - se.extend(other.keys().filter(|name| !one.contains(*name)).cloned()); + se.extend( + other + .data + .keys() + .filter(|name| !one.contains(*name)) + .cloned(), + ); } (ExportConfig::AllBut(sne), ExportConfig::Only(oe)) => { - sne.extend(other.keys().filter(|name| !oe.contains(*name)).cloned()); + sne.extend( + other + .data + .keys() + .filter(|name| !oe.contains(*name)) + .cloned(), + ); } } - self.data.extend(other); + self.data.extend(other.data); } #[cfg(feature = "full")] diff --git a/src/makefile/macro_scope.rs b/src/makefile/macro_scope.rs index ba9b6cf..c03870f 100644 --- a/src/makefile/macro_scope.rs +++ b/src/makefile/macro_scope.rs @@ -43,6 +43,12 @@ impl<'a> MacroScope for LookupInternal<'a> { } } +impl MacroScope for Option<&T> { + fn get(&self, name: &str) -> Option> { + self.as_ref().and_then(|value| value.get(name)) + } +} + // warning on undefined macros is useful but can get repetitive fast lazy_static! { static ref WARNINGS_EMITTED: RwLock> = Default::default(); diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs index 9b2f626..4a0c5e3 100644 --- a/src/makefile/mod.rs +++ b/src/makefile/mod.rs @@ -8,7 +8,9 @@ use std::rc::Rc; use eyre::{bail, eyre, Result, WrapErr}; use command_line::CommandLine; -use inference_rules::InferenceRule; +#[cfg(feature = "full")] +use functions::NO_EVAL; +use inference_rules::{InferenceRule, InferenceRuleSet}; use input::FinishedMakefileReader; pub use input::MakefileReader; use lookup_internal::LookupInternal; @@ -19,8 +21,6 @@ use target::{DynamicTargetSet, Target}; use token::TokenString; use crate::args::Args; -#[cfg(feature = "full")] -use crate::makefile::functions::NO_EVAL; mod command_line; #[cfg(feature = "full")] @@ -34,6 +34,7 @@ mod input; mod lookup_internal; mod r#macro; mod macro_scope; +mod parse; mod pattern; mod target; mod token; @@ -52,7 +53,7 @@ pub enum ItemSource { } pub struct Makefile<'a> { - inference_rules: Vec, + inference_rules: InferenceRuleSet, builtin_inference_rules: Vec, pub macros: MacroSet, targets: DynamicTargetSet, @@ -132,7 +133,7 @@ impl<'a> Makefile<'a> { } Makefile { - inference_rules: vec![], + inference_rules: InferenceRuleSet::default(), builtin_inference_rules: inference_rules, macros, targets, @@ -144,11 +145,7 @@ impl<'a> Makefile<'a> { pub fn extend(&mut self, new: FinishedMakefileReader) -> Result<()> { self.inference_rules.extend(new.inference_rules); - self.macros.extend( - new.macros, - #[cfg(feature = "full")] - new.macro_exports, - ); + self.macros.extend(new.macros); for (_, target) in new.targets { self.targets.put(target); } @@ -212,7 +209,12 @@ impl<'a> Makefile<'a> { .inference_rules .iter() .chain(self.builtin_inference_rules.iter()) - .filter(|rule| !banned_rules.contains(rule)) + .filter(|rule| { + !banned_rules.iter().any(|banned_rule| { + banned_rule.products == rule.products + && banned_rule.prerequisites == rule.prerequisites + }) + }) .filter(|rule| rule.matches(name).unwrap_or(false)); for rule in inference_rule_candidates { log::trace!( @@ -271,6 +273,7 @@ impl<'a> Makefile<'a> { .first_match(name)? .and_then(|x| x.get(1).map(|x| x.as_str().to_owned())), already_updated: Cell::new(false), + macros: MacroSet::new(), }); break; } @@ -313,6 +316,7 @@ impl<'a> Makefile<'a> { commands, stem: None, already_updated: Cell::new(false), + macros: MacroSet::new(), }); } else { // if it already exists, it counts as up-to-date @@ -323,6 +327,7 @@ impl<'a> Makefile<'a> { commands: vec![], stem: None, already_updated: Cell::new(true), + macros: MacroSet::new(), }); } } @@ -355,6 +360,7 @@ impl<'a> Makefile<'a> { MacroScopeStack::default() .with_scope(&self.macros) .with_scope(&LookupInternal::new(target, &|name| self.get_target(name))) + .with_scope(&target.map(|target| &target.macros)) .expand( text, #[cfg(feature = "full")] @@ -409,6 +415,7 @@ fn builtin_inference_rules() -> Vec { prepend_dot!($($second)?).into(), concat!(".", stringify!($first)).into(), vec![$(CommandLine::from($cmd.parse().unwrap())),+], + MacroSet::new(), ) ),+] }; @@ -464,6 +471,7 @@ fn builtin_targets() -> Vec { commands: vec![], stem: None, already_updated: Cell::new(false), + macros: MacroSet::new(), }] } @@ -482,9 +490,10 @@ mod test { products: vec!["this-is-a-%-case".to_owned()], prerequisites: vec![], commands: vec![], + macros: MacroSet::new(), }; let file = Makefile { - inference_rules: vec![rule], + inference_rules: vec![rule].into(), builtin_inference_rules: vec![], macros: MacroSet::new(), targets: Default::default(), @@ -508,6 +517,7 @@ mod test { commands: vec![], stem: None, already_updated: Cell::new(false), + macros: MacroSet::new(), }; let phony = Target { name: ".PHONY".to_string(), @@ -515,13 +525,14 @@ mod test { commands: vec![], stem: None, already_updated: Cell::new(false), + macros: MacroSet::new(), }; let targets = DynamicTargetSet::default(); targets.put(target); targets.put(phony); let file = Makefile { - inference_rules: vec![], + inference_rules: InferenceRuleSet::default(), builtin_inference_rules: vec![], macros: MacroSet::new(), targets, diff --git a/src/makefile/parse.rs b/src/makefile/parse.rs new file mode 100644 index 0000000..191b7e0 --- /dev/null +++ b/src/makefile/parse.rs @@ -0,0 +1,19 @@ +use super::Macro; +use super::TokenString; + +#[derive(Debug)] +pub struct MacroAssignment { + pub name: String, + pub value: TokenString, + #[cfg(feature = "full")] + pub expand_value: bool, + #[cfg(feature = "full")] + pub skip_if_defined: bool, + #[cfg(feature = "full")] + pub append: bool, +} + +pub enum MacroAssignmentOutcome { + Set, + AppendedTo(Macro), +} diff --git a/src/makefile/target.rs b/src/makefile/target.rs index d6fac2c..e106c54 100644 --- a/src/makefile/target.rs +++ b/src/makefile/target.rs @@ -8,9 +8,7 @@ use std::time::SystemTime; use eyre::{Result, WrapErr}; -use crate::makefile::command_line::CommandLine; - -use super::Makefile; +use super::{CommandLine, MacroSet, Makefile}; #[derive(PartialEq, Eq, Clone, Debug)] pub struct Target { @@ -19,6 +17,7 @@ pub struct Target { pub commands: Vec, pub stem: Option, pub already_updated: Cell, + pub macros: MacroSet, } impl Target { @@ -43,6 +42,7 @@ impl Target { self.stem = self.stem.take().or(other.stem); let already_updated = self.already_updated.get() || other.already_updated.get(); self.already_updated.set(already_updated); + self.macros.extend(other.macros); } } } diff --git a/src/makefile/token.rs b/src/makefile/token.rs index 396380b..dad4694 100644 --- a/src/makefile/token.rs +++ b/src/makefile/token.rs @@ -17,6 +17,7 @@ use nom::{ character::complete::{space0, space1}, multi::separated_list1, }; +use regex::Regex; trait Err<'a>: 'a + ParseError<&'a str> + ContextError<&'a str> {} impl<'a, T: 'a + ParseError<&'a str> + ContextError<&'a str>> Err<'a> for T {} @@ -144,6 +145,16 @@ impl TokenString { } }) } + + pub fn matches_regex(&self, regex: &Regex) -> bool { + self.0.iter().any(|x| { + if let Token::Text(x) = x { + regex.is_match(x) + } else { + false + } + }) + } } impl fmt::Display for TokenString { -- cgit v1.2.3