From 028e1a631629c7f5959e522dee80188ea7534d8f Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sat, 27 Mar 2021 15:50:39 -0600 Subject: refactor makefile elements into submodules --- src/makefile/command_line.rs | 102 +++++++++++++++++++++ src/makefile/inference_rules.rs | 22 +++++ src/makefile/mod.rs | 194 ++-------------------------------------- src/makefile/target.rs | 77 ++++++++++++++++ 4 files changed, 208 insertions(+), 187 deletions(-) create mode 100644 src/makefile/command_line.rs create mode 100644 src/makefile/inference_rules.rs create mode 100644 src/makefile/target.rs (limited to 'src/makefile') 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 , 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 , 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, +} + +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, -} - -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, - commands: Vec, - already_updated: Cell, -} - -impl Target { - fn modified_time(&self) -> Option { - metadata(&self.name) - .and_then(|metadata| metadata.modified()) - .ok() - } - - fn newer_than(&self, other: &Target) -> Option { - 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 , 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 , 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, + pub commands: Vec, + pub already_updated: Cell, +} + +impl Target { + fn modified_time(&self) -> Option { + metadata(&self.name) + .and_then(|metadata| metadata.modified()) + .ok() + } + + pub fn newer_than(&self, other: &Target) -> Option { + 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(()) + } +} -- cgit v1.2.3