aboutsummaryrefslogtreecommitdiff
path: root/src
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
parentfbbcf325b8bbe72f924da6a7cdb128d973ef0026 (diff)
downloadmakers-4f9299b4639802e05e1cb27d8eb40305ff8e110e.tar.gz
makers-4f9299b4639802e05e1cb27d8eb40305ff8e110e.zip
implement rule-specific macros for targets
Diffstat (limited to 'src')
-rw-r--r--src/makefile/inference_rules.rs97
-rw-r--r--src/makefile/input.rs324
-rw-r--r--src/makefile/macro.rs32
-rw-r--r--src/makefile/macro_scope.rs6
-rw-r--r--src/makefile/mod.rs37
-rw-r--r--src/makefile/parse.rs19
-rw-r--r--src/makefile/target.rs6
-rw-r--r--src/makefile/token.rs11
8 files changed, 431 insertions, 101 deletions
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<String>,
pub prerequisites: Vec<String>,
pub commands: Vec<CommandLine>,
+ pub macros: MacroSet,
}
impl InferenceRule {
@@ -22,12 +22,14 @@ impl InferenceRule {
s1: String,
s2: String,
commands: Vec<CommandLine>,
+ 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<Vec<String>, HashMap<Vec<String>, 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<Item = &InferenceRule> {
+ self.data.values().flat_map(HashMap::values)
+ }
+}
+
+impl From<Vec<InferenceRule>> for InferenceRuleSet {
+ fn from(value: Vec<InferenceRule>) -> 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<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(())
}
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<String>),
AllBut(HashSet<String>),
@@ -73,7 +73,7 @@ impl ExportConfig {
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Set {
pub data: HashMap<String, Macro>,
#[cfg(feature = "full")]
@@ -128,13 +128,9 @@ impl Set {
self.data.insert(name, r#macro);
}
- pub fn extend(
- &mut self,
- other: HashMap<String, Macro>,
- #[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<T: MacroScope> MacroScope for Option<&T> {
+ fn get(&self, name: &str) -> Option<Cow<Macro>> {
+ 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<HashSet<String>> = 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<InferenceRule>,
+ inference_rules: InferenceRuleSet,
builtin_inference_rules: Vec<InferenceRule>,
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<InferenceRule> {
prepend_dot!($($second)?).into(),
concat!(".", stringify!($first)).into(),
vec![$(CommandLine::from($cmd.parse().unwrap())),+],
+ MacroSet::new(),
)
),+]
};
@@ -464,6 +471,7 @@ fn builtin_targets() -> Vec<Target> {
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<CommandLine>,
pub stem: Option<String>,
pub already_updated: Cell<bool>,
+ 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 {