aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2021-04-03 18:01:04 -0600
committerMelody Horn <melody@boringcactus.com>2021-04-03 18:01:04 -0600
commitf73d0b2256f2afc05995929c930b2eeab6800ecf (patch)
tree8c580ae21e50a5d3c166a568bbc5955ffcb341ad
parent7c2a09e5c75d87d5dd728623517899ca7cada630 (diff)
downloadmakers-f73d0b2256f2afc05995929c930b2eeab6800ecf.tar.gz
makers-f73d0b2256f2afc05995929c930b2eeab6800ecf.zip
implement GNUish '%'-based inference rules
-rw-r--r--src/makefile/inference_rules.rs55
-rw-r--r--src/makefile/input.rs26
-rw-r--r--src/makefile/mod.rs62
3 files changed, 106 insertions, 37 deletions
diff --git a/src/makefile/inference_rules.rs b/src/makefile/inference_rules.rs
index 3d18730..dfbb07c 100644
--- a/src/makefile/inference_rules.rs
+++ b/src/makefile/inference_rules.rs
@@ -1,19 +1,62 @@
use std::fmt;
-use crate::makefile::command_line::CommandLine;
+use eyre::{eyre, Result};
+use regex::Captures;
+
+use super::command_line::CommandLine;
+use super::pattern::r#match;
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct InferenceRule {
- /// POSIX calls this ".s1" but that's not useful.
- pub product: String,
- /// POSIX calls this ".s2" but that's not useful.
- pub prereq: String,
+ pub products: Vec<String>,
+ pub prerequisites: Vec<String>,
pub commands: Vec<CommandLine>,
}
+impl InferenceRule {
+ /// s1 is the product, s2 is the prereq
+ pub fn new_suffix(s1: String, s2: String, commands: Vec<CommandLine>) -> Self {
+ Self {
+ products: vec![format!("%.{}", s1)],
+ prerequisites: vec![format!("%.{}", s2)],
+ commands,
+ }
+ }
+
+ fn first_match<'s, 't: 's>(&'s self, target_name: &'t str) -> Result<Option<Captures<'t>>> {
+ self.products
+ .iter()
+ .map(|pattern| r#match(pattern, target_name))
+ .try_fold(None, |x, y| y.map(|y| x.or(y)))
+ }
+
+ pub fn matches(&self, target_name: &str) -> Result<bool> {
+ self.first_match(target_name).map(|x| x.is_some())
+ }
+
+ pub fn prereqs<'s>(
+ &'s self,
+ target_name: &'s str,
+ ) -> Result<impl Iterator<Item = String> + '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).expect("should've matched the %").as_str();
+ Ok(self
+ .prerequisites
+ .iter()
+ .map(move |p| p.replace('%', percent_expansion)))
+ }
+}
+
impl fmt::Display for InferenceRule {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, "{}{}:", &self.prereq, &self.product)?;
+ writeln!(
+ f,
+ "{}: {}",
+ self.products.join(" "),
+ self.prerequisites.join(" ")
+ )?;
for command in &self.commands {
writeln!(f, "\t{}", command)?;
}
diff --git a/src/makefile/input.rs b/src/makefile/input.rs
index 3abdb8b..3a3508f 100644
--- a/src/makefile/input.rs
+++ b/src/makefile/input.rs
@@ -375,19 +375,33 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> {
return Ok(());
}
- // we don't know yet if it's a target rule or an inference rule
+ // 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, &prerequisites);
+ #[cfg(feature = "full")]
+ let is_pattern = targets.iter().all(|x| x.contains('%'));
- if let Some(inference_match) = inference_match {
+ #[cfg(feature = "full")]
+ if is_pattern {
let new_rule = InferenceRule {
- product: inference_match.name("s1").unwrap().as_str().to_owned(),
- prereq: inference_match.name("s2").unwrap().as_str().to_owned(),
+ products: targets.into_iter().map(|x| x.to_owned()).collect(),
+ prerequisites,
commands,
};
+ self.inference_rules.push(new_rule);
+ return Ok(());
+ }
+
+ if let Some(inference_match) = inference_match {
+ let new_rule = InferenceRule::new_suffix(
+ inference_match.name("s1").unwrap().as_str().to_owned(),
+ inference_match.name("s2").unwrap().as_str().to_owned(),
+ commands,
+ );
+
self.inference_rules.retain(|existing_rule| {
- (&existing_rule.prereq, &existing_rule.product)
- != (&new_rule.prereq, &new_rule.product)
+ (&existing_rule.prerequisites, &existing_rule.products)
+ != (&new_rule.prerequisites, &new_rule.products)
});
self.inference_rules.push(new_rule);
} else {
diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs
index 3d824b9..225ff55 100644
--- a/src/makefile/mod.rs
+++ b/src/makefile/mod.rs
@@ -2,7 +2,7 @@ use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::env;
use std::fmt;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::rc::Rc;
use eyre::{eyre, Result};
@@ -103,7 +103,7 @@ impl<'a> Makefile<'a> {
pub fn get_target(&self, name: &str) -> Result<Rc<RefCell<Target>>> {
// TODO implement .POSIX
- let follow_gnu = true;
+ let follow_gnu = cfg!(feature = "full");
let vpath_options = match self.macros.get("VPATH") {
Some((_, vpath)) if follow_gnu => {
@@ -113,16 +113,16 @@ impl<'a> Makefile<'a> {
_ => vec![],
};
- let targets = self.targets.borrow();
let mut new_target = None;
let exists_but_infer_anyway = if follow_gnu {
- targets
+ self.targets
+ .borrow()
.get(name)
.map_or(false, |target| target.borrow().commands.is_empty())
} else {
false
};
- if !targets.contains_key(name) || exists_but_infer_anyway {
+ if !self.targets.borrow().contains_key(name) || exists_but_infer_anyway {
// When no target rule is found to update a target, the inference rules shall
// be checked. The suffix of the target to be built...
let suffix = Path::new(name)
@@ -132,39 +132,51 @@ impl<'a> Makefile<'a> {
// targets. If the .s1 suffix is found in .SUFFIXES...
if self.special_target_has_prereq(".SUFFIXES", &suffix) || suffix.is_empty() {
// the inference rules shall be searched in the order defined...
- 'rules: for rule in self
+ // TODO implement GNUish shortest-stem-first matching
+ let inference_rule_candidates = self
.inference_rules
.iter()
- // for the first .s2.s1 rule...
- .filter(|rule| rule.product == suffix)
- {
+ .filter(|rule| rule.matches(name).unwrap_or(false));
+ for rule in inference_rule_candidates {
// whose prerequisite file ($*.s2) exists.
- let prereq_path =
- Path::new(name).with_extension(rule.prereq.trim_start_matches('.'));
- if let Some(prereq) = std::iter::once(prereq_path.clone())
- .chain(
- if prereq_path.is_absolute() {
+ let prereq_paths = rule
+ .prereqs(name)?
+ .map(|prereq_path_name| {
+ if name == prereq_path_name {
+ // we can't build this based on itself! fuck outta here
+ return None;
+ }
+ if self.get_target(&prereq_path_name).is_ok() {
+ return Some(prereq_path_name);
+ }
+ let prereq_path = PathBuf::from(prereq_path_name);
+ let prereq_vpath_options = if prereq_path.is_absolute() {
None
} else {
Some(vpath_options.iter().map(|vpath| vpath.join(&prereq_path)))
}
.into_iter()
- .flatten(),
- )
- .find(|prereq| prereq.exists())
- {
+ .flatten();
+ std::iter::once(prereq_path.clone())
+ .chain(prereq_vpath_options)
+ .find(|prereq| prereq.exists())
+ .map(|path| path.to_string_lossy().to_string())
+ })
+ .collect::<Option<Vec<String>>>();
+ if let Some(prereqs) = prereq_paths {
new_target = Some(Target {
name: name.into(),
- prerequisites: vec![prereq.to_string_lossy().into()],
+ prerequisites: prereqs,
commands: rule.commands.clone(),
already_updated: Cell::new(false),
});
- break 'rules;
+ break;
}
}
}
}
+ let targets = self.targets.borrow();
if !targets.contains_key(name) && new_target.is_none() {
// well, inference didn't work. is there a default?
if let Some(default) = targets.get(".DEFAULT") {
@@ -315,11 +327,11 @@ fn builtin_inference_rules() -> Vec<InferenceRule> {
{$(.$first:tt$(.$second:tt)?:
$($cmd:literal)+)+} => {
vec![$(
- InferenceRule {
- product: prepend_dot!($($second)?).into(),
- prereq: concat!(".", stringify!($first)).into(),
- commands: vec![$(CommandLine::from($cmd.parse().unwrap())),+],
- }
+ InferenceRule::new_suffix(
+ prepend_dot!($($second)?).into(),
+ concat!(".", stringify!($first)).into(),
+ vec![$(CommandLine::from($cmd.parse().unwrap())),+],
+ )
),+]
};
}