use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::env; use std::fmt; use std::path::Path; use std::rc::Rc; use eyre::{eyre, Result}; use crate::args::Args; mod command_line; #[cfg(feature = "full")] mod conditional; #[cfg(feature = "full")] mod functions; mod inference_rules; mod input; mod r#macro; #[cfg(feature = "full")] mod pattern; mod target; mod token; use command_line::CommandLine; use inference_rules::InferenceRule; use input::FinishedMakefileReader; pub use input::MakefileReader; use r#macro::{Set as MacroSet, Source as MacroSource}; use target::Target; use token::TokenString; pub struct Makefile<'a> { inference_rules: Vec, pub macros: MacroSet<'static, 'static>, targets: RefCell>>>, pub first_non_special_target: Option, args: &'a Args, // TODO borrow warnings from Python version } impl<'a> Makefile<'a> { pub fn new(args: &'a Args) -> Self { let mut inference_rules = vec![]; let mut macros = MacroSet::new(); let mut targets = HashMap::new(); let first_non_special_target = None; if !args.no_builtin_rules { inference_rules.extend(builtin_inference_rules()); macros.add_builtins(); targets.extend( builtin_targets() .into_iter() .map(|t| (t.name.clone(), Rc::new(RefCell::new(t)))), ); } macros.add_env(); for r#macro in args.macros() { if let [name, value] = *r#macro.splitn(2, '=').collect::>() { macros.set( name.into(), MacroSource::CommandLineOrMakeflags, TokenString::text(value), ); } } Makefile { inference_rules, macros, targets: RefCell::new(targets), first_non_special_target, args, } } pub fn extend(&mut self, new: FinishedMakefileReader) { self.inference_rules.extend(new.inference_rules); self.macros.extend(new.macros); self.targets.borrow_mut().extend( new.targets .into_iter() .map(|(k, v)| (k, Rc::new(RefCell::new(v)))), ); if self.first_non_special_target.is_none() { self.first_non_special_target = new.first_non_special_target; } } fn special_target_has_prereq(&self, target: &str, name: &str) -> bool { let targets = self.targets.borrow(); match targets.get(target) { Some(target) => { let target = target.borrow(); target.prerequisites.is_empty() || target.prerequisites.iter().any(|e| e == name) } None => false, } } pub fn get_target(&self, name: &str) -> Result>> { // TODO implement .POSIX let follow_gnu = true; let vpath_options = match self.macros.get("VPATH") { Some((_, vpath)) if follow_gnu => { let vpath = self.expand_macros(vpath, None)?; env::split_paths(&vpath).collect() } _ => vec![], }; let targets = self.targets.borrow(); let mut new_target = None; let exists_but_infer_anyway = if follow_gnu { targets .get(name) .map_or(false, |target| target.borrow().commands.is_empty()) } else { false }; if !targets.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) .extension() .map_or_else(String::new, |ext| format!(".{}", ext.to_string_lossy())); // is compared to the list of suffixes specified by the .SUFFIXES special // 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 .inference_rules .iter() // for the first .s2.s1 rule... .filter(|rule| rule.product == suffix) { // 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() { None } else { Some(vpath_options.iter().map(|vpath| vpath.join(&prereq_path))) } .into_iter() .flatten(), ) .find(|prereq| prereq.exists()) { new_target = Some(Target { name: name.into(), prerequisites: vec![prereq.to_string_lossy().into()], commands: rule.commands.clone(), already_updated: Cell::new(false), }); break 'rules; } } } } 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") { let commands = default.borrow().commands.clone(); new_target = Some(Target { name: name.into(), prerequisites: vec![], commands, already_updated: Cell::new(false), }); } else { // if it already exists, it counts as up-to-date if Path::new(name).exists() { new_target = Some(Target { name: name.into(), prerequisites: vec![], commands: vec![], already_updated: Cell::new(true), }); } } } drop(targets); if let Some(new_target) = new_target { self.targets .borrow_mut() .insert(new_target.name.clone(), Rc::new(RefCell::new(new_target))); } let targets = self.targets.borrow(); Ok(Rc::clone( targets .get(name) .ok_or_else(|| eyre!("Target {:?} not found!", name))?, )) } pub fn update_target(&self, name: &str) -> Result<()> { self.get_target(name)?.borrow().update(self) } fn expand_macros(&self, text: &TokenString, target: Option<&Target>) -> Result { let target = target.cloned(); let lookup_internal = move |name: &str| { let target = target .as_ref() .ok_or_else(|| eyre!("internal macro but no current target!"))?; let macro_pieces = if name.starts_with('@') { // The $@ shall evaluate to the full target name of the // current target. vec![target.name.clone()] } else if name.starts_with('?') { // The $? macro shall evaluate to the list of prerequisites // that are newer than the current target. target .prerequisites .iter() .filter(|prereq| { self.get_target(prereq) .ok() .and_then(|prereq| prereq.borrow().newer_than(target)) .unwrap_or(false) }) .cloned() .collect() } else if name.starts_with('<') { // In an inference rule, the $< macro shall evaluate to the // filename whose existence allowed the inference rule to be // chosen for the target. In the .DEFAULT rule, the $< macro // shall evaluate to the current target name. target.prerequisites.clone() } else if name.starts_with('*') { // The $* macro shall evaluate to the current target name with // its suffix deleted. vec![Path::new(name).with_extension("").to_string_lossy().into()] } else { unreachable!() }; let macro_pieces = if name.ends_with('D') { macro_pieces .into_iter() .map(|x| { Path::new(&x) .parent() .ok_or_else(|| eyre!("no parent")) .map(|x| x.to_string_lossy().into()) }) .collect::>()? } else if name.ends_with('F') { macro_pieces .into_iter() .map(|x| { Path::new(&x) .file_name() .ok_or_else(|| eyre!("no filename")) .map(|x| x.to_string_lossy().into()) }) .collect::>()? } else { macro_pieces }; Ok(macro_pieces.join(" ")) }; self.macros.with_lookup(&lookup_internal).expand(text) } } impl fmt::Display for Makefile<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let header = |f: &mut fmt::Formatter, t: &str| { writeln!(f, "{}\n{:=^width$}", t, "", width = t.len()) }; header(f, "Inference Rules")?; for rule in &self.inference_rules { writeln!(f, "{}", rule)?; } writeln!(f)?; header(f, "Macros")?; writeln!(f, "{}", &self.macros)?; writeln!(f)?; header(f, "Targets")?; for target in self.targets.borrow().values() { writeln!(f, "{}", target.borrow())?; } Ok(()) } } fn builtin_inference_rules() -> Vec { // This is a terrible idea. macro_rules! prepend_dot { ($x:tt) => { concat!(".", stringify!($x)) }; () => { "" }; } macro_rules! make { {$(.$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())),+], } ),+] }; } make! { .c: "$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<" .f: "$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<" .sh: "cp $< $@" "chmod a+x $@" .c.o: "$(CC) $(CFLAGS) -c $<" .f.o: "$(FC) $(FFLAGS) -c $<" .y.o: "$(YACC) $(YFLAGS) $<" "$(CC) $(CFLAGS) -c y.tab.c" "rm -f y.tab.c" "mv y.tab.o $@" .l.o: "$(LEX) $(LFLAGS) $<" "$(CC) $(CFLAGS) -c lex.yy.c" "rm -f lex.yy.c" "mv lex.yy.o $@" .y.c: "$(YACC) $(YFLAGS) $<" "mv y.tab.c $@" .l.c: "$(LEX) $(LFLAGS) $<" "mv lex.yy.c $@" .c.a: "$(CC) -c $(CFLAGS) $<" "$(AR) $(ARFLAGS) $@ $*.o" "rm -f $*.o" .f.a: "$(FC) -c $(FFLAGS) $<" "$(AR) $(ARFLAGS) $@ $*.o" "rm -f $*.o" } } fn builtin_targets() -> Vec { // even i'm not going to do that just for this vec![Target { name: ".SUFFIXES".into(), prerequisites: vec![".o", ".c", ".y", ".l", ".a", ".sh", ".f"] .into_iter() .map(String::from) .collect(), commands: vec![], already_updated: Cell::new(false), }] }