diff options
Diffstat (limited to 'src/makefile/input.rs')
-rw-r--r-- | src/makefile/input.rs | 324 |
1 files changed, 257 insertions, 67 deletions
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<InferenceRule>, + 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::<Vec<_>>(); + + 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<String> { - 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<MacroAssignment> { 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<MacroAssignmentOutcome> { + 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<String> { @@ -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<InferenceRule>, - pub macros: HashMap<String, Macro>, - #[cfg(feature = "full")] - pub macro_exports: ExportConfig, + pub inference_rules: InferenceRuleSet, + pub macros: MacroSet, pub targets: HashMap<String, Target>, pub first_non_special_target: Option<String>, pub failed_includes: Vec<String>, @@ -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(()) } |