aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/makefile/command_line.rs102
-rw-r--r--src/makefile/inference_rules.rs22
-rw-r--r--src/makefile/mod.rs194
-rw-r--r--src/makefile/target.rs77
4 files changed, 208 insertions, 187 deletions
diff --git a/src/makefile/command_line.rs b/src/makefile/command_line.rs
new file mode 100644
index 0000000..0e0e745
--- /dev/null
+++ b/src/makefile/command_line.rs
@@ -0,0 +1,102 @@
+use std::fmt;
+
+use crate::makefile::target::Target;
+use crate::makefile::token::{Token, TokenString};
+use crate::makefile::Makefile;
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct CommandLine {
+ /// If the command prefix contains a <hyphen-minus>, or the -i option is present, or
+ /// the special target .IGNORE has either the current target as a prerequisite or has
+ /// no prerequisites, any error found while executing the command shall be ignored.
+ ignore_errors: bool,
+ /// If the command prefix contains an at-sign and the make utility command line -n
+ /// option is not specified, or the -s option is present, or the special target
+ /// .SILENT has either the current target as a prerequisite or has no prerequisites,
+ /// the command shall not be written to standard output before it is executed.
+ silent: bool,
+ /// If the command prefix contains a <plus-sign>, this indicates a makefile command
+ /// line that shall be executed even if -n, -q, or -t is specified.
+ always_execute: bool,
+ execution_line: TokenString,
+}
+
+impl CommandLine {
+ pub fn from(mut line: TokenString) -> Self {
+ let mut ignore_errors = false;
+ let mut silent = false;
+ let mut always_execute = false;
+
+ if let Token::Text(text) = line.first_token_mut() {
+ let mut text_chars = text.chars().peekable();
+ while let Some(x) = text_chars.next_if(|x| matches!(x, '-' | '@' | '+')) {
+ match x {
+ '-' => ignore_errors = true,
+ '@' => silent = true,
+ '+' => always_execute = true,
+ _ => unreachable!(),
+ }
+ }
+ *text = text_chars.collect();
+ }
+
+ CommandLine {
+ ignore_errors,
+ silent,
+ always_execute,
+ execution_line: line,
+ }
+ }
+
+ pub 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));
+
+ if !silent {
+ println!("{}", execution_line);
+ }
+
+ 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())
+ .expect("execution line shouldn't have a null in the middle");
+ // TODO pass shell "-e" if errors are not ignored
+ let return_value = unsafe { libc::system(execution_line.as_ptr()) };
+ if return_value != 0 {
+ // apparently there was an error. do we care?
+ if !ignore_error {
+ // TODO handle this error gracefully
+ panic!("error from command execution!");
+ }
+ }
+ }
+}
+
+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");
+ write!(f, "{}", execution_line)?;
+ Ok(())
+ }
+}
diff --git a/src/makefile/inference_rules.rs b/src/makefile/inference_rules.rs
new file mode 100644
index 0000000..3d18730
--- /dev/null
+++ b/src/makefile/inference_rules.rs
@@ -0,0 +1,22 @@
+use std::fmt;
+
+use crate::makefile::command_line::CommandLine;
+
+#[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 commands: Vec<CommandLine>,
+}
+
+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 {
+ writeln!(f, "\t{}", command)?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs
index d968bb3..e07cdfd 100644
--- a/src/makefile/mod.rs
+++ b/src/makefile/mod.rs
@@ -2,206 +2,26 @@ use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::env;
use std::fmt;
-use std::fs::{metadata, File};
+use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::rc::Rc;
-use std::time::SystemTime;
use lazy_static::lazy_static;
use regex::Regex;
use crate::args::Args;
+mod command_line;
+mod inference_rules;
+mod target;
mod token;
+use command_line::CommandLine;
+use inference_rules::InferenceRule;
+use target::Target;
use token::{tokenize, Token, TokenString};
-#[derive(PartialEq, Eq, Clone, Debug)]
-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 fmt::Display for InferenceRule {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- writeln!(f, "{}{}:", &self.prereq, &self.product)?;
- for command in &self.commands {
- writeln!(f, "\t{}", command)?;
- }
- Ok(())
- }
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct Target {
- name: String,
- prerequisites: Vec<String>,
- commands: Vec<CommandLine>,
- already_updated: Cell<bool>,
-}
-
-impl Target {
- fn modified_time(&self) -> Option<SystemTime> {
- metadata(&self.name)
- .and_then(|metadata| metadata.modified())
- .ok()
- }
-
- 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_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: &Makefile) -> bool {
- if self.already_updated.get() {
- return true;
- }
- let exists = metadata(&self.name).is_ok();
- let newer_than_all_dependencies = self.prerequisites.iter().all(|t| {
- self.newer_than(&file.get_target(t).borrow())
- .unwrap_or(false)
- });
- exists && newer_than_all_dependencies
- }
-
- pub fn update(&self, file: &Makefile) {
- for prereq in &self.prerequisites {
- file.update_target(prereq);
- }
- if !self.is_up_to_date(file) {
- self.execute_commands(file);
- }
- self.already_updated.set(true);
- }
-
- fn execute_commands(&self, file: &Makefile) {
- for command in &self.commands {
- command.execute(file, self);
- }
- }
-}
-
-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(())
- }
-}
-
-#[derive(PartialEq, Eq, Clone, Debug)]
-pub struct CommandLine {
- /// If the command prefix contains a <hyphen-minus>, or the -i option is present, or
- /// the special target .IGNORE has either the current target as a prerequisite or has
- /// no prerequisites, any error found while executing the command shall be ignored.
- ignore_errors: bool,
- /// If the command prefix contains an at-sign and the make utility command line -n
- /// option is not specified, or the -s option is present, or the special target
- /// .SILENT has either the current target as a prerequisite or has no prerequisites,
- /// the command shall not be written to standard output before it is executed.
- silent: bool,
- /// If the command prefix contains a <plus-sign>, this indicates a makefile command
- /// line that shall be executed even if -n, -q, or -t is specified.
- always_execute: bool,
- execution_line: TokenString,
-}
-
-impl CommandLine {
- fn from(mut line: TokenString) -> Self {
- let mut ignore_errors = false;
- let mut silent = false;
- let mut always_execute = false;
-
- if let Token::Text(text) = line.first_token_mut() {
- let mut text_chars = text.chars().peekable();
- while let Some(x) = text_chars.next_if(|x| matches!(x, '-' | '@' | '+')) {
- match x {
- '-' => ignore_errors = true,
- '@' => silent = true,
- '+' => always_execute = true,
- _ => unreachable!(),
- }
- }
- *text = text_chars.collect();
- }
-
- CommandLine {
- ignore_errors,
- silent,
- always_execute,
- execution_line: line,
- }
- }
-
- 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));
-
- if !silent {
- println!("{}", execution_line);
- }
-
- 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())
- .expect("execution line shouldn't have a null in the middle");
- // TODO pass shell "-e" if errors are not ignored
- let return_value = unsafe { libc::system(execution_line.as_ptr()) };
- if return_value != 0 {
- // apparently there was an error. do we care?
- if !ignore_error {
- // TODO handle this error gracefully
- panic!("error from command execution!");
- }
- }
- }
-}
-
-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");
- write!(f, "{}", execution_line)?;
- Ok(())
- }
-}
-
enum MacroSource {
File,
CommandLineOrMAKEFLAGS,
diff --git a/src/makefile/target.rs b/src/makefile/target.rs
new file mode 100644
index 0000000..1fbc75b
--- /dev/null
+++ b/src/makefile/target.rs
@@ -0,0 +1,77 @@
+use std::cell::Cell;
+use std::fmt;
+use std::fs::metadata;
+use std::time::SystemTime;
+
+use crate::makefile::command_line::CommandLine;
+
+use super::Makefile;
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct Target {
+ pub name: String,
+ pub prerequisites: Vec<String>,
+ pub commands: Vec<CommandLine>,
+ pub already_updated: Cell<bool>,
+}
+
+impl Target {
+ fn modified_time(&self) -> Option<SystemTime> {
+ metadata(&self.name)
+ .and_then(|metadata| metadata.modified())
+ .ok()
+ }
+
+ pub 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_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: &Makefile) -> bool {
+ if self.already_updated.get() {
+ return true;
+ }
+ let exists = metadata(&self.name).is_ok();
+ let newer_than_all_dependencies = self.prerequisites.iter().all(|t| {
+ self.newer_than(&file.get_target(t).borrow())
+ .unwrap_or(false)
+ });
+ exists && newer_than_all_dependencies
+ }
+
+ pub fn update(&self, file: &Makefile) {
+ for prereq in &self.prerequisites {
+ file.update_target(prereq);
+ }
+ if !self.is_up_to_date(file) {
+ self.execute_commands(file);
+ }
+ self.already_updated.set(true);
+ }
+
+ fn execute_commands(&self, file: &Makefile) {
+ for command in &self.commands {
+ command.execute(file, self);
+ }
+ }
+}
+
+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(())
+ }
+}