From f73d0b2256f2afc05995929c930b2eeab6800ecf Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sat, 3 Apr 2021 18:01:04 -0600 Subject: implement GNUish '%'-based inference rules --- src/makefile/inference_rules.rs | 55 ++++++++++++++++++++++++++++++++---- src/makefile/input.rs | 26 +++++++++++++---- src/makefile/mod.rs | 62 ++++++++++++++++++++++++----------------- 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, + pub prerequisites: Vec, pub commands: Vec, } +impl InferenceRule { + /// s1 is the product, s2 is the prereq + pub fn new_suffix(s1: String, s2: String, commands: Vec) -> Self { + Self { + products: vec![format!("%.{}", s1)], + prerequisites: vec![format!("%.{}", s2)], + commands, + } + } + + fn first_match<'s, 't: 's>(&'s self, target_name: &'t str) -> Result>> { + 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 { + self.first_match(target_name).map(|x| x.is_some()) + } + + pub fn prereqs<'s>( + &'s self, + target_name: &'s str, + ) -> Result + '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>> { // 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::>>(); + 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 { {$(.$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())),+], + ) ),+] }; } -- cgit v1.2.3