aboutsummaryrefslogtreecommitdiff
path: root/src/makefile/mod.rs
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2021-03-28 14:57:03 -0600
committerMelody Horn <melody@boringcactus.com>2021-03-28 14:57:03 -0600
commitbc5038e6c344803bce76add47b13ceaa61a5bde3 (patch)
treeeec9a8fc36562aa4413cf8be8ef22910fdc75cac /src/makefile/mod.rs
parent82278c3a5aa93204c963a63cc3cfefea1d0fb3fd (diff)
downloadmakers-bc5038e6c344803bce76add47b13ceaa61a5bde3.tar.gz
makers-bc5038e6c344803bce76add47b13ceaa61a5bde3.zip
almost implement all functions
Diffstat (limited to 'src/makefile/mod.rs')
-rw-r--r--src/makefile/mod.rs306
1 files changed, 128 insertions, 178 deletions
diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs
index 1fc8303..7610197 100644
--- a/src/makefile/mod.rs
+++ b/src/makefile/mod.rs
@@ -7,21 +7,23 @@ use std::io::{BufRead, BufReader, Error as IoError};
use std::path::Path;
use std::rc::Rc;
+use crate::args::Args;
use lazy_static::lazy_static;
use regex::Regex;
-use crate::args::Args;
-
mod command_line;
mod conditional;
mod functions;
mod inference_rules;
+mod r#macro;
+mod pattern;
mod target;
mod token;
use command_line::CommandLine;
use conditional::{ConditionalLine, ConditionalState};
use inference_rules::InferenceRule;
+use r#macro::{LookupInternal, MacroSet, MacroSource};
use target::Target;
use token::{tokenize, Token, TokenString};
@@ -58,13 +60,6 @@ impl LineType {
}
}
-enum MacroSource {
- File,
- CommandLineOrMakeflags,
- Environment,
- Builtin,
-}
-
fn inference_match<'a>(
targets: &[&'a str],
prerequisites: &[String],
@@ -91,7 +86,7 @@ fn inference_match<'a>(
pub(crate) struct Makefile<'a> {
inference_rules: Vec<InferenceRule>,
- macros: HashMap<String, (MacroSource, TokenString)>,
+ macros: MacroSet,
targets: RefCell<HashMap<String, Rc<RefCell<Target>>>>,
pub(crate) first_non_special_target: Option<String>,
args: &'a Args,
@@ -101,15 +96,13 @@ pub(crate) struct Makefile<'a> {
impl<'a> Makefile<'a> {
pub(crate) fn new(args: &'a Args) -> Self {
let mut inference_rules = vec![];
- let mut macros = HashMap::new();
+ 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());
- for (k, v) in builtin_macros() {
- macros.insert(k.into(), (MacroSource::Builtin, v));
- }
+ macros.add_builtins();
targets.extend(
builtin_targets()
.into_iter()
@@ -117,20 +110,14 @@ impl<'a> Makefile<'a> {
);
}
- for (k, v) in env::vars() {
- if k != "MAKEFLAGS" && k != "SHELL" {
- macros.insert(k, (MacroSource::Environment, TokenString::text(v)));
- }
- }
+ macros.add_env();
for r#macro in args.macros() {
if let [name, value] = *r#macro.splitn(2, '=').collect::<Vec<_>>() {
- macros.insert(
+ macros.set(
name.into(),
- (
- MacroSource::CommandLineOrMakeflags,
- TokenString::text(value),
- ),
+ MacroSource::CommandLineOrMakeflags,
+ TokenString::text(value),
);
}
}
@@ -197,7 +184,7 @@ impl<'a> Makefile<'a> {
{
line.action(
conditional_stack.last(),
- |name| self.macros.contains_key(name),
+ |name| self.macros.is_defined(name),
|t| self.expand_macros(t, None),
)
.apply_to(&mut conditional_stack);
@@ -375,7 +362,7 @@ impl<'a> Makefile<'a> {
_ => {}
}
- let value = match self.macros.remove(name) {
+ let value = match self.macros.pop(name) {
Some((_, mut old_value)) if append => {
// TODO eagerly expand if appending to eagerly-expanded macro
old_value.extend(TokenString::text(" "));
@@ -384,7 +371,7 @@ impl<'a> Makefile<'a> {
}
_ => value,
};
- self.macros.insert(name.into(), (MacroSource::File, value));
+ self.macros.set(name.into(), MacroSource::File, value);
}
fn special_target_has_prereq(&self, target: &str, name: &str) -> bool {
@@ -501,104 +488,75 @@ impl<'a> Makefile<'a> {
}
fn expand_macros(&self, text: &TokenString, target: Option<&Target>) -> String {
- let mut result = String::new();
- for token in text.tokens() {
- match token {
- Token::Text(t) => result.push_str(t),
- Token::MacroExpansion { name, replacement } => {
- let internal_macro_names = &['@', '?', '<', '*'][..];
- let internal_macro_suffices = &['D', 'F'][..];
- let just_internal = name.len() == 1 && name.starts_with(internal_macro_names);
- let suffixed_internal = name.len() == 2
- && name.starts_with(internal_macro_names)
- && name.ends_with(internal_macro_suffices);
- let macro_value = if just_internal || suffixed_internal {
- let target = target.expect("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)
- .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 target = target.cloned();
+ let lookup_internal = move |name: &str| {
+ let target = target
+ .as_ref()
+ .expect("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)
+ .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()
- .expect("no parent")
- .to_string_lossy()
- .into()
- })
- .collect()
- } else if name.ends_with('F') {
- macro_pieces
- .into_iter()
- .map(|x| {
- Path::new(&x)
- .file_name()
- .expect("no filename")
- .to_string_lossy()
- .into()
- })
- .collect()
- } else {
- macro_pieces
- };
+ let macro_pieces = if name.ends_with('D') {
+ macro_pieces
+ .into_iter()
+ .map(|x| {
+ Path::new(&x)
+ .parent()
+ .expect("no parent")
+ .to_string_lossy()
+ .into()
+ })
+ .collect()
+ } else if name.ends_with('F') {
+ macro_pieces
+ .into_iter()
+ .map(|x| {
+ Path::new(&x)
+ .file_name()
+ .expect("no filename")
+ .to_string_lossy()
+ .into()
+ })
+ .collect()
+ } else {
+ macro_pieces
+ };
- macro_pieces.join(" ")
- } else {
- self.macros
- .get(name)
- .map_or_else(String::new, |(_, macro_value)| {
- self.expand_macros(macro_value, target)
- })
- };
- let macro_value = match replacement {
- Some((subst1, subst2)) => {
- let subst1 = self.expand_macros(subst1, target);
- let subst1_suffix = regex::escape(&subst1);
- let subst1_suffix =
- Regex::new(&format!(r"{}\b", subst1_suffix)).unwrap();
- let subst2 = self.expand_macros(subst2, target);
- subst1_suffix.replace_all(&macro_value, subst2).to_string()
- }
- None => macro_value,
- };
- result.push_str(&macro_value);
- }
- Token::FunctionCall { name, args } => {
- result.push_str(&self.expand_macros(&functions::call(name, args), None));
- }
- }
- }
- result
+ macro_pieces.join(" ")
+ };
+
+ let lookup_internal: Rc<dyn LookupInternal> = Rc::new(lookup_internal);
+ self.macros.lookup(Rc::downgrade(&lookup_internal));
+ self.macros.expand(text)
}
}
@@ -614,9 +572,7 @@ impl fmt::Display for Makefile<'_> {
writeln!(f)?;
header(f, "Macros")?;
- for (k, (_, v)) in &self.macros {
- writeln!(f, "{}={}", k, v)?;
- }
+ writeln!(f, "{}", &self.macros)?;
writeln!(f)?;
header(f, "Targets")?;
@@ -691,54 +647,6 @@ fn builtin_inference_rules() -> Vec<InferenceRule> {
"rm -f $*.o"
}
}
-fn builtin_macros() -> Vec<(&'static str, TokenString)> {
- // Fuck it, might as well.
- macro_rules! handle {
- ($value:ident) => {
- stringify!($value)
- };
- ($value:literal) => {
- $value
- };
- }
- macro_rules! make {
- ($($name:ident=$value:tt)+) => {vec![$(
- (stringify!($name), handle!($value).parse().unwrap())
- ),+]};
- }
-
- make![
- MAKE=makers
- AR=ar
- YACC=yacc
- YFLAGS=""
- LEX=lex
- LFLAGS=""
- LDFLAGS=""
-
- AS=as
- CC=cc
- CXX="g++"
- CPP="$(CC) -E"
- FC=f77
- PC=pc
- CO=co
- GET=get
- LINT=lint
- MAKEINFO=makeinfo
- TEX=tex
- TEXI2DVI=texi2dvi
- WEAVE=weave
- CWEAVE=cweave
- TANGLE=tangle
- CTANGLE=ctangle
- RM="rm -f"
-
- ARFLAGS="rv"
- CFLAGS=""
- FFLAGS=""
- ]
-}
fn builtin_targets() -> Vec<Target> {
// even i'm not going to do that just for this
vec![Target {
@@ -754,14 +662,14 @@ fn builtin_targets() -> Vec<Target> {
#[cfg(test)]
mod test {
- use super::*;
-
use std::io::Cursor;
+ use super::*;
+
fn empty_makefile(args: &Args) -> Makefile {
Makefile {
inference_rules: vec![],
- macros: HashMap::new(),
+ macros: MacroSet::new(),
targets: RefCell::new(HashMap::new()),
first_non_special_target: None,
args,
@@ -781,8 +689,50 @@ endif
let mut makefile = empty_makefile(&args);
makefile.and_read(Cursor::new(file));
assert_eq!(
- makefile.expand_macros(&"$(worked)".parse().unwrap(), None),
+ makefile.expand_macros(&TokenString::r#macro("worked"), None),
"yes"
);
}
+
+ #[test]
+ #[ignore = "I still haven't implemented `eval` or `define` or %-based macro substitution."]
+ fn eval() {
+ // This, for the record, is a terrible misfeature.
+ // If you need this, you probably shouldn't be using Make.
+ // But a lot of people are using this and still use Make anyway, so here we go,
+ // I guess.
+
+ let file = "
+PROGRAMS = server client
+
+server_OBJS = server.o server_priv.o server_access.o
+server_LIBS = priv protocol
+
+client_OBJS = client.o client_api.o client_mem.o
+client_LIBS = protocol
+
+# Everything after this is generic
+
+.PHONY: all
+all: $(PROGRAMS)
+
+define PROGRAM_template =
+ $(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
+ ALL_OBJS += $$($(1)_OBJS)
+endef
+
+$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
+
+$(PROGRAMS):
+ $(LINK.o) $^ $(LDLIBS) -o $@
+
+clean:
+ rm -f $(ALL_OBJS) $(PROGRAMS)
+ ";
+
+ let args = Args::empty();
+ let mut makefile = empty_makefile(&args);
+ makefile.and_read(Cursor::new(file));
+ assert!(makefile.targets.borrow().contains_key("server"))
+ }
}