aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2021-03-26 19:43:40 -0600
committerMelody Horn <melody@boringcactus.com>2021-03-26 19:43:40 -0600
commit45fa49be5dc2630187e78c8adec498b751d9481e (patch)
treed6af3f08f4349caa867795ef52a4a356a1d8ce76
parentabd55e36d781645566a7815c7712ded6b5cc1923 (diff)
downloadmakers-45fa49be5dc2630187e78c8adec498b751d9481e.tar.gz
makers-45fa49be5dc2630187e78c8adec498b751d9481e.zip
overhaul target handling
-rw-r--r--src/main.rs18
-rw-r--r--src/makefile/mod.rs555
-rw-r--r--src/makefile/token.rs49
3 files changed, 487 insertions, 135 deletions
diff --git a/src/main.rs b/src/main.rs
index 4048886..2764e97 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -26,10 +26,6 @@ fn main() {
// TODO dump command-line macros into environment
// TODO add SHELL macro
let mut makefile = Makefile::new(args.clone());
- if !args.no_builtin_rules {
- makefile.add_builtins();
- }
- makefile.add_env();
for filename in &args.makefile {
if filename == &PathBuf::from("-") {
makefile.and_read(stdin().lock());
@@ -37,4 +33,18 @@ fn main() {
makefile.and_read_file(filename);
};
}
+
+ if args.print_everything {
+ println!("{}", &makefile);
+ }
+
+ let targets = if args.targets().count() == 0 {
+ vec![makefile.first_non_special_target.clone().expect("couldn't find a target!")]
+ } else {
+ args.targets().cloned().collect()
+ };
+
+ for target in targets {
+ makefile.update_target(&target);
+ }
}
diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs
index 75873d2..e8adac9 100644
--- a/src/makefile/mod.rs
+++ b/src/makefile/mod.rs
@@ -1,8 +1,11 @@
+use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::env;
+use std::fmt;
use std::fs::{File, metadata};
use std::io::{BufRead, BufReader};
use std::path::Path;
+use std::rc::Rc;
use std::time::SystemTime;
use lazy_static::lazy_static;
@@ -14,31 +17,22 @@ mod token;
use token::{tokenize, Token, TokenString};
-pub enum RuleType {
- Inference,
- Target,
-}
-
#[derive(PartialEq, Eq, Clone)]
-pub struct Rule {
- name: String,
- prerequisites: Vec<String>,
+pub struct InferenceRule {
+ /// POSIX calls this ".s1" but that's not useful.
+ product: String,
+ /// POSIX calls this ".s2" but that's not useful.
+ prereq: String,
commands: Vec<CommandLine>,
}
-impl Rule {
- pub fn r#type(&self) -> RuleType {
- if self.name.contains(".") && !self.name.contains("/") {
- RuleType::Inference
- } else {
- RuleType::Target
- }
- }
-
- fn execute_commands(&self, file: &Makefile, target: &Target) {
+impl fmt::Display for InferenceRule {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "{}{}:", &self.prereq, &self.product)?;
for command in &self.commands {
- command.execute(file, target);
+ writeln!(f, "\t{}", command)?;
}
+ Ok(())
}
}
@@ -46,8 +40,8 @@ impl Rule {
pub struct Target {
name: String,
prerequisites: Vec<String>,
- rule: Option<Rule>,
- already_updated: bool,
+ commands: Vec<CommandLine>,
+ already_updated: Cell<bool>,
}
impl Target {
@@ -58,45 +52,55 @@ impl Target {
}
fn newer_than(&self, other: &Target) -> Option<bool> {
+ let self_updated = self.already_updated.get();
+ let other_updated = other.already_updated.get();
Some(match (self.modified_time(), other.modified_time()) {
(Some(self_mtime), Some(other_mtime)) => self_mtime >= other_mtime,
// per POSIX: "If the target does not exist after the target has been
// successfully made up-to-date, the target shall be treated as being
// newer than any target for which it is a prerequisite."
- (None, _) if self.already_updated && other.prerequisites.contains(&self.name) => true,
- (_, None) if other.already_updated && self.prerequisites.contains(&other.name) => false,
+ (None, _) if self_updated && other.prerequisites.contains(&self.name) => true,
+ (_, None) if other_updated && self.prerequisites.contains(&other.name) => false,
_ => return None,
})
}
- fn is_up_to_date(&self, file: &mut Makefile) -> bool {
- if self.already_updated {
+ fn is_up_to_date(&self, file: &Makefile) -> bool {
+ if self.already_updated.get() {
return true;
}
let exists = metadata(&self.name).is_ok();
- if exists && self.rule.is_none() {
- return true;
- }
let newer_than_all_dependencies = self.prerequisites
.iter()
- .all(|t| self.newer_than(&file.get_target(t)).unwrap_or(false));
- if exists && newer_than_all_dependencies {
- return true;
- }
- false
+ .all(|t| self.newer_than(&file.get_target(t).borrow()).unwrap_or(false));
+ exists && newer_than_all_dependencies
}
- fn update(&mut self, file: &mut Makefile) {
+ pub fn update(&self, file: &Makefile) {
for prereq in &self.prerequisites {
file.update_target(prereq);
}
if !self.is_up_to_date(file) {
- match &self.rule {
- Some(rule) => rule.execute_commands(file, self),
- None => panic!("target doesn't exist & no rule to make it"), // TODO handle this error well
- }
+ self.execute_commands(file);
+ }
+ self.already_updated.set(true);
+ }
+
+ fn execute_commands(&self, file: &Makefile) {
+ for command in &self.commands {
+ command.execute(file, self);
}
- self.already_updated = true;
+ }
+}
+
+impl fmt::Display for Target {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let prereqs = self.prerequisites.join(" ");
+ writeln!(f, "{}: {}", &self.name, prereqs)?;
+ for command in &self.commands {
+ writeln!(f, "\t{}", command)?;
+ }
+ Ok(())
}
}
@@ -148,28 +152,29 @@ impl CommandLine {
}
fn execute(&self, file: &Makefile, target: &Target) {
+ let ignore_error = self.ignore_errors ||
+ file.args.ignore_errors ||
+ file.special_target_has_prereq(".IGNORE", &target.name);
+ let silent = (self.silent && !file.args.dry_run) ||
+ file.args.silent ||
+ file.special_target_has_prereq(".SILENT", &target.name);
+
+ let execution_line = file.expand_macros(&self.execution_line, Some(target));
+
let avoid_execution = file.args.dry_run || file.args.question || file.args.touch;
if avoid_execution && !self.always_execute {
return;
}
- let execution_line = file.expand_macros(&self.execution_line);
-
- let self_silent = self.silent && !file.args.dry_run;
- let special_target_silent = file.rules.get(".SILENT")
- .map_or(false, |silent_target| {
- silent_target.prerequisites.is_empty() || silent_target.prerequisites.contains(&target.name)
- });
- let silent = self_silent || file.args.silent || special_target_silent;
if !silent {
println!("{}", execution_line);
}
- let special_target_ignore = file.rules.get(".IGNORE")
- .map_or(false, |ignore_target| {
- ignore_target.prerequisites.is_empty() || ignore_target.prerequisites.contains(&target.name)
- });
- let ignore_error = self.ignore_errors || file.args.ignore_errors || special_target_ignore;
+ let should_execute = self.always_execute ||
+ !(file.args.dry_run || file.args.question || file.args.touch);
+ if !should_execute {
+ return;
+ }
// TODO don't fuck this up
let execution_line = ::std::ffi::CString::new(execution_line.as_bytes())
@@ -186,6 +191,24 @@ impl CommandLine {
}
}
+impl fmt::Display for CommandLine {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.ignore_errors {
+ write!(f, "-")?;
+ }
+ if self.silent {
+ write!(f, "@")?;
+ }
+ if self.always_execute {
+ write!(f, "+")?;
+ }
+ let execution_line = format!("{}", &self.execution_line);
+ let execution_line = execution_line.replace("\n", "↵\n");
+ writeln!(f, "{}", execution_line)?;
+ Ok(())
+ }
+}
+
enum MacroSource {
File,
CommandLineOrMAKEFLAGS,
@@ -194,38 +217,53 @@ enum MacroSource {
}
pub struct Makefile {
- rules: HashMap<String, Rule>,
+ inference_rules: Vec<InferenceRule>,
macros: HashMap<String, (MacroSource, TokenString)>,
- targets: HashMap<String, Target>,
+ targets: RefCell<HashMap<String, Rc<RefCell<Target>>>>,
+ pub first_non_special_target: Option<String>,
args: Args,
+ // TODO borrow warnings from Python version
}
impl Makefile {
pub fn new(args: Args) -> Makefile {
- Makefile {
- rules: HashMap::new(),
- macros: HashMap::new(),
- targets: HashMap::new(),
- args,
+ let mut inference_rules = vec![];
+ let mut macros = HashMap::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));
+ }
+ targets.extend(builtin_targets().into_iter()
+ .map(|t| (t.name.clone(), Rc::new(RefCell::new(t)))));
}
- }
- pub fn add_builtins(&mut self) -> &mut Makefile {
- self.rules.extend(BUILTIN_RULES.iter().map(|(name, rule)| (name.to_string(), rule.clone())));
- self
- }
+ for (k, v) in env::vars() {
+ if k != "MAKEFLAGS" && k != "SHELL" {
+ macros.insert(k, (MacroSource::Environment, TokenString::text(v)));
+ }
+ }
- pub fn add_env(&mut self) -> &mut Makefile {
- self.macros.extend(env::vars()
- .filter_map(|(name, value)| {
- if name == "MAKEFLAGS" || name == "SHELL" {
- None
- } else {
- Some((name, (MacroSource::Environment, TokenString::from(vec![Token::Text(value)]))))
+ for r#macro in args.macros() {
+ let pieces = r#macro.splitn(2, '=').collect::<Vec<_>>();
+ match *pieces {
+ [name, value] => {
+ macros.insert(name.into(), (MacroSource::CommandLineOrMAKEFLAGS, TokenString::text(value)));
}
- })
- );
- self
+ _ => {}
+ }
+ }
+
+ Makefile {
+ inference_rules,
+ macros,
+ targets: RefCell::new(targets),
+ first_non_special_target,
+ args,
+ }
}
pub fn and_read_file(&mut self, path: impl AsRef<Path>) -> &mut Makefile {
@@ -248,7 +286,7 @@ impl Makefile {
// TODO handle I/O errors at all
let mut line = line.expect("failed to read line of makefile!");
- // handle escaped newlines (TODO exception for command lines)
+ // handle escaped newlines
while line.ends_with(r"\") {
let next_line = match lines_iter.next() {
Some(x) => x,
@@ -256,9 +294,11 @@ impl Makefile {
};
let next_line = next_line.expect("failed to read line of makefile!");
let next_line = next_line.trim_start();
+ line.pop();
line.push(' ');
line.push_str(next_line);
}
+
// handle comments
lazy_static! {
static ref COMMENT: Regex = Regex::new("#.*$").unwrap();
@@ -269,11 +309,10 @@ impl Makefile {
if let Some(line) = line.strip_prefix("include ") {
// remove extra leading space
let line = line.trim_start();
- let line = self.expand_macros(&tokenize(line));
+ let line = self.expand_macros(&tokenize(line), None);
let fields = line.split_whitespace();
// POSIX says we only have to handle a single filename, but GNU make
// handles arbitrarily many filenames, and it's not like that's more work
- // TODO have some way of linting for non-portable constructs
for field in fields {
self.and_read_file(field);
}
@@ -322,18 +361,34 @@ impl Makefile {
match line_type {
LineType::Rule => {
let (targets, not_targets) = line_tokens.split_once(':').unwrap();
- let targets = self.expand_macros(&targets);
+ let targets = self.expand_macros(&targets, None);
let targets = targets.split_whitespace().map(|x| x.into()).collect::<Vec<String>>();
let (prerequisites, mut commands) = match not_targets.split_once(';') {
- Some((prerequisites, commands)) => (prerequisites, vec![commands]),
+ Some((prerequisites, mut command)) => {
+ while command.ends_with(r"\") && lines_iter.peek().is_some() {
+ command.strip_suffix(r"\");
+ command.extend(tokenize(&lines_iter.next().unwrap().unwrap()));
+ }
+ (prerequisites, vec![command])
+ }
None => (not_targets, vec![]),
};
- let prerequisites = self.expand_macros(&prerequisites);
+ let prerequisites = self.expand_macros(&prerequisites, None);
let prerequisites = prerequisites.split_whitespace().map(|x| x.into()).collect::<Vec<String>>();
while lines_iter.peek().and_then(|x| x.as_ref().ok()).map_or(false, |line| line.starts_with('\t')) {
let line = lines_iter.next().unwrap().unwrap();
- let line = line.strip_prefix("\t").unwrap();
+ let mut line: String = line.strip_prefix("\t").unwrap().into();
+ while line.ends_with('\\') {
+ match lines_iter.next() {
+ Some(Ok(next_line)) => {
+ let next_line = next_line.strip_prefix("\t").unwrap_or(&next_line);
+ line.push('\n');
+ line.push_str(next_line);
+ }
+ _ => break
+ }
+ }
commands.push(line.parse().unwrap());
}
@@ -341,32 +396,110 @@ impl Makefile {
.map(CommandLine::from)
.collect::<Vec<_>>();
- for target in targets {
- match self.rules.get_mut(&target) {
- Some(old_rule) if commands.is_empty() => {
- old_rule.prerequisites.extend(prerequisites.clone());
+ if targets.is_empty() {
+ continue;
+ }
+
+ // we don't know yet if it's a target rule or an inference rule
+ lazy_static! {
+ static ref INFERENCE_RULE: Regex = Regex::new(r"^(?P<s2>(\.[^/.]+)?)(?P<s1>\.[^/.]+)$").unwrap();
+ static ref SPECIAL_TARGET: Regex = Regex::new(r"^\.[A-Z]+$").unwrap();
+ }
+
+ let inference_match = INFERENCE_RULE.captures(&targets[0]);
+ let special_target_match = SPECIAL_TARGET.captures(&targets[0]);
+
+ let inference_rule = targets.len() == 1 && prerequisites.len() == 0 && inference_match.is_some() && special_target_match.is_none();
+ if inference_rule {
+ let inference_match = inference_match.unwrap();
+ let new_rule = InferenceRule {
+ product: inference_match.name("s1").unwrap().as_str().to_string(),
+ prereq: inference_match.name("s2").unwrap().as_str().to_string(),
+ commands,
+ };
+
+ self.inference_rules.retain(|existing_rule|
+ (&existing_rule.prereq, &existing_rule.product) != (&new_rule.prereq, &new_rule.product));
+ self.inference_rules.push(new_rule);
+ } else {
+ for target in targets {
+ if self.first_non_special_target.is_none() && !target.starts_with('.') {
+ self.first_non_special_target = Some(target.clone());
}
- _ => {
- self.rules.insert(target.clone(), Rule {
- name: target,
- prerequisites: prerequisites.clone(),
- commands: commands.clone(),
- });
+ let mut targets = self.targets.borrow_mut();
+ match targets.get_mut(&target) {
+ Some(old_target) if commands.is_empty() && !(target == ".SUFIXES" && prerequisites.is_empty()) => {
+ let mut old_target = old_target.borrow_mut();
+ let new_prerequisites = prerequisites.iter()
+ .filter(|x| !old_target.prerequisites.contains(x))
+ .cloned()
+ .collect::<Vec<_>>();
+ old_target.prerequisites.extend(new_prerequisites);
+ }
+ _ => {
+ let new_target = Target {
+ name: target.clone(),
+ prerequisites: prerequisites.clone(),
+ commands: commands.clone(),
+ already_updated: Cell::new(false),
+ };
+ targets.insert(target.clone(), Rc::new(RefCell::new(new_target)));
+ }
}
}
}
},
LineType::Macro => {
- let (name, value) = line_tokens.split_once('=').unwrap();
- let name = self.expand_macros(&name);
- match self.macros.get(&name) {
+ let (name, mut value) = line_tokens.split_once('=').unwrap();
+ let name = self.expand_macros(&name, None);
+ // GNUisms are annoying, but popular
+ let mut expand_value = false;
+ let mut skip_if_defined = false;
+ let mut append = false;
+ let name = if let Some(real_name) = name.strip_suffix("::") {
+ expand_value = true;
+ real_name
+ } else if let Some(real_name) = name.strip_suffix(":") {
+ expand_value = true;
+ real_name
+ } else if let Some(real_name) = name.strip_suffix("?") {
+ skip_if_defined = true;
+ real_name
+ } else if let Some(real_name) = name.strip_suffix("+") {
+ append = true;
+ real_name
+ } else {
+ &name
+ };
+
+ let name = name.trim_end();
+ value.trim_start();
+
+ let value = if expand_value {
+ TokenString::text(self.expand_macros(&value, None))
+ } else {
+ value
+ };
+
+ match self.macros.get(name) {
// We always let command line or MAKEFLAGS macros override macros from the file.
Some((MacroSource::CommandLineOrMAKEFLAGS, _)) => continue,
// We let environment variables override macros from the file only if the command-line argument to do that was given
Some((MacroSource::Environment, _)) if self.args.environment_overrides => continue,
+ _ if skip_if_defined => continue,
_ => {}
}
- self.macros.insert(name, (MacroSource::File, value));
+
+ let value = match self.macros.remove(name) {
+ Some((_, mut old_value)) if append => {
+ // TODO eagerly expand if appending to eagerly-expanded macro
+ old_value.extend(TokenString::text(" "));
+ old_value.extend(value);
+ old_value
+ },
+ _ => value
+ };
+ self.macros.insert(name.into(), (MacroSource::File, value));
}
LineType::Unknown => {
panic!("Unknown line {:?}", line_tokens);
@@ -377,57 +510,182 @@ impl Makefile {
self
}
- fn get_target(&mut self, name: impl Into<String>) -> &mut Target {
- let name = name.into();
- {
- let rules_get_name = self.rules.get(&name);
- let make_target = || {
- if let Some(target_rule) = rules_get_name {
- return Target {
- name: name.clone(),
- prerequisites: target_rule.prerequisites.clone(),
- already_updated: false,
- rule: Some(target_rule.clone())
+ 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.len() == 0 || target.prerequisites.contains(&name.into())
+ },
+ None => false,
+ }
+ }
+
+ pub fn get_target(&self, name: &str) -> Rc<RefCell<Target>> {
+ // 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 mut targets = self.targets.borrow_mut();
+ let exists_but_infer_anyway = if follow_gnu {
+ match targets.get(name) {
+ Some(target) => {
+ let target = target.borrow();
+ target.commands.is_empty()
+ }
+ None => false,
+ }
+ } 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| ext.to_string_lossy().into());
+ // 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 {
+ // for the first .s2.s1 rule...
+ if rule.product == suffix {
+ // whose prerequisite file ($*.s2) exists.
+ let prereq_path = Path::new(name).with_extension(rule.prereq.clone());
+ let prereq_path_options = if prereq_path.is_absolute() {
+ vec![prereq_path]
+ } else {
+ let mut options = vec![prereq_path.clone()];
+ options.extend(vpath_options.iter().map(|vpath| vpath.join(&prereq_path)));
+ options
+ };
+ for prereq in prereq_path_options {
+ if prereq.exists() {
+ let new_target = Target {
+ name: name.into(),
+ prerequisites: vec![prereq.to_string_lossy().into()],
+ commands: rule.commands.clone(),
+ already_updated: Cell::new(false),
+ };
+ targets.insert(name.into(), Rc::new(RefCell::new(new_target)));
+ break 'rules;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if !targets.contains_key(name) {
+ // well, inference didn't work. is there a default?
+ if let Some(default) = targets.get(".DEFAULT") {
+ let default = default.borrow();
+ let commands = default.commands.clone();
+ drop(default);
+ let new_target = Target {
+ name: name.into(),
+ prerequisites: vec![],
+ commands,
+ already_updated: Cell::new(false),
+ };
+ targets.insert(name.into(), Rc::new(RefCell::new(new_target)));
+ } else {
+ // if it already exists, it counts as up-to-date
+ if Path::new(name).exists() {
+ let new_target = Target {
+ name: name.into(),
+ prerequisites: vec![],
+ commands: vec![],
+ already_updated: Cell::new(true),
};
+ targets.insert(name.into(), Rc::new(RefCell::new(new_target)));
}
- panic!("uhhhhh i don't even know anymore bro");
- };
- self.targets
- .entry(name.clone())
- .or_insert_with(make_target);
+ }
}
- self.targets.get_mut(&name).unwrap()
+
+ drop(targets);
+ let targets = self.targets.borrow();
+
+ targets.get(name).expect("Target not found!").clone()
}
- fn update_target(&mut self, name: impl Into<String>) {
- // This is the dumbest fucking thing I've ever had to do.
- // We can't leave it in the map, because then we have overlapping mutable borrows of self,
- // so we have to remove it from the map, do the work, and then re-insert it into the map.
- // Fuck this so much.
- // Why the goddamn hell do I even write Rust.
- let name = name.into();
- {
- let _ = self.get_target(name.clone());
- }
- let mut target = self.targets.remove(&name).unwrap();
+ pub fn update_target(&self, name: &str) {
+ let target = self.get_target(name);
+ let target = target.borrow();
target.update(self);
- self.targets.insert(name.clone(), target);
}
- fn expand_macros(&self, text: &TokenString) -> String {
+ 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 (_, macro_value) = &self.macros[name];
- let macro_value = self.expand_macros(macro_value);
+ 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 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 {
+ match self.macros.get(name) {
+ Some((_, macro_value)) => self.expand_macros(&macro_value, target),
+ None => {
+ String::new()
+ }
+ }
+ };
let macro_value = match replacement {
Some((subst1, subst2)) => {
- let subst1 = self.expand_macros(subst1);
+ 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);
+ let subst2 = self.expand_macros(subst2, target);
subst1_suffix.replace_all(&macro_value, subst2).to_string()
},
None => macro_value,
@@ -440,5 +698,40 @@ impl Makefile {
}
}
-const BUILTIN_RULES: &'static [(&'static str, Rule)] = &[];
-const BUILTIN_SUFFIX_LIST: &'static [&'static str] = &[];
+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")?;
+ for (k, (_, v)) in &self.macros {
+ writeln!(f, "{}={}", k, v)?;
+ }
+ writeln!(f)?;
+
+ header(f, "Targets")?;
+ let targets = self.targets.borrow();
+ for (_, target) in &*targets {
+ let target = target.borrow();
+ writeln!(f, "{}", target)?;
+ }
+
+ Ok(())
+ }
+}
+
+fn builtin_inference_rules() -> Vec<InferenceRule> {
+ todo!()
+}
+fn builtin_macros() -> Vec<(&'static str, TokenString)> {
+ todo!()
+}
+fn builtin_targets() -> Vec<Target> {
+ todo!()
+}
diff --git a/src/makefile/token.rs b/src/makefile/token.rs
index 3f69b50..4943010 100644
--- a/src/makefile/token.rs
+++ b/src/makefile/token.rs
@@ -1,3 +1,4 @@
+use std::fmt;
use std::str::FromStr;
use nom::{
@@ -14,6 +15,10 @@ use nom::{
pub struct TokenString(Vec<Token>);
impl TokenString {
+ pub fn text(text: impl Into<String>) -> Self {
+ Self(vec![Token::Text(text.into())])
+ }
+
pub fn tokens(&self) -> impl Iterator<Item=&Token> {
self.0.iter()
}
@@ -41,6 +46,40 @@ impl TokenString {
}
None
}
+
+ pub fn ends_with(&self, pattern: &str) -> bool {
+ match self.0.last() {
+ Some(Token::Text(t)) => t.ends_with(pattern),
+ _ => false
+ }
+ }
+
+ pub fn strip_suffix(&mut self, suffix: &str) {
+ if let Some(Token::Text(t)) = self.0.last_mut() {
+ if let Some(x) = t.strip_suffix(suffix) {
+ *t = x.into()
+ }
+ }
+ }
+
+ pub fn extend(&mut self, other: TokenString) {
+ self.0.extend(other.0);
+ }
+
+ pub fn trim_start(&mut self) {
+ if let Some(Token::Text(t)) = self.0.last_mut() {
+ *t = t.trim_start().into();
+ }
+ }
+}
+
+impl fmt::Display for TokenString {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ for t in &self.0 {
+ write!(f, "{}", t)?;
+ }
+ Ok(())
+ }
}
#[derive(PartialEq, Eq, Clone, Debug)]
@@ -52,6 +91,16 @@ pub enum Token {
},
}
+impl fmt::Display for Token {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Token::Text(t) => write!(f, "{}", t),
+ Token::MacroExpansion { name, replacement: None } => write!(f, "$({})", name),
+ Token::MacroExpansion { name, replacement: Some((r1, r2)) } => write!(f, "$({}:{}={})", name, r1, r2),
+ }
+ }
+}
+
fn macro_name(input: &str) -> IResult<&str, &str> {
// POSIX says "periods, underscores, digits, and alphabetics from the portable character set"
take_while1(|c: char| {