diff options
| author | Melody Horn <melody@boringcactus.com> | 2021-04-03 18:01:04 -0600 | 
|---|---|---|
| committer | Melody Horn <melody@boringcactus.com> | 2021-04-03 18:01:04 -0600 | 
| commit | f73d0b2256f2afc05995929c930b2eeab6800ecf (patch) | |
| tree | 8c580ae21e50a5d3c166a568bbc5955ffcb341ad /src | |
| parent | 7c2a09e5c75d87d5dd728623517899ca7cada630 (diff) | |
| download | makers-f73d0b2256f2afc05995929c930b2eeab6800ecf.tar.gz makers-f73d0b2256f2afc05995929c930b2eeab6800ecf.zip | |
implement GNUish '%'-based inference rules
Diffstat (limited to 'src')
| -rw-r--r-- | src/makefile/inference_rules.rs | 55 | ||||
| -rw-r--r-- | src/makefile/input.rs | 26 | ||||
| -rw-r--r-- | 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<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())),+], +                )              ),+]          };      } |