aboutsummaryrefslogtreecommitdiff
path: root/src/makefile/input.rs
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2024-11-11 17:15:08 -0700
committerMelody Horn <melody@boringcactus.com>2024-11-11 17:15:08 -0700
commit4f9299b4639802e05e1cb27d8eb40305ff8e110e (patch)
tree0da5529c68c82e97aed67d842e50f6285e79e6c2 /src/makefile/input.rs
parentfbbcf325b8bbe72f924da6a7cdb128d973ef0026 (diff)
downloadmakers-4f9299b4639802e05e1cb27d8eb40305ff8e110e.tar.gz
makers-4f9299b4639802e05e1cb27d8eb40305ff8e110e.zip
implement rule-specific macros for targets
Diffstat (limited to 'src/makefile/input.rs')
-rw-r--r--src/makefile/input.rs324
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(&macro_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,
+ &macro_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(&macro_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(&macro_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(&macro_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(&macro_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(())
}