diff --git a/src/args.rs b/src/args.rs index 4c2869e..9c6fe85 100644 --- a/src/args.rs +++ b/src/args.rs @@ -4,15 +4,27 @@ use clap::Parser; use const_format::formatcp; +const SUBMAKE_FORBIDDEN_FLAGS: [&str; 5] = ["-j", "-C", "-f", "-o", "-W"]; + +const BUILD_MODE: &str = if cfg!(debug_assertions) { + "Debug" +} else { + "Release" +}; + /// Represents the `clap`-based arguments provided by this binary. #[derive(Clone, Debug, Parser)] #[clap( - name = "make (oxidized)", - version, - about, - after_help = formatcp!( - "License: {}\nSource: {}", env!("CARGO_PKG_LICENSE"), env!("CARGO_PKG_REPOSITORY") - ), +name = "make (oxidized)", +version, +about, +after_help = formatcp ! ( +"License: {}\nSource: {}\nVersion: {}\nBuild type: {}", +env ! ("CARGO_PKG_LICENSE"), +env ! ("CARGO_PKG_REPOSITORY"), +env ! ("CARGO_PKG_VERSION"), +BUILD_MODE +), )] pub struct Args { /// Target(s) (if none specifired, use first regular target). @@ -45,10 +57,10 @@ pub struct Args { /// Don't execute recipes; just print them. #[arg( - short = 'n', - long = "just-print", - visible_alias("dry-run"), - visible_alias("recon") + short = 'n', + long = "just-print", + visible_alias("dry-run"), + visible_alias("recon") )] pub just_print: bool, @@ -58,11 +70,11 @@ pub struct Args { /// Consider FILE to be very new to simulate "what if" it changed. #[arg( - short = 'W', - long = "what-if", - value_name = "FILE", - visible_alias("new-file"), - visible_alias("assume-new") + short = 'W', + long = "what-if", + value_name = "FILE", + visible_alias("new-file"), + visible_alias("assume-new") )] pub new_file: Vec, @@ -70,3 +82,45 @@ pub struct Args { #[arg(long)] pub license: bool, } + +/// Converts the arguments to a string that can be passed to a sub-make invocation. +pub(crate) fn args_to_submake_str() -> String { + let mut single_flags = Vec::new(); + let mut arguments = Vec::new(); + // Rudimetary MAKEFLAGS parsing, the '-j' flag handling is not implemented yet. + // TODO: This should probably be a `Result` instead of a `panic!`. + // NOTE: Maybe change the way this is done to a pure IPC solution? when sub-make is used? + std::env::args() + .collect::>() + .iter() + .for_each(|arg| { + let mut arg_mod = arg.clone(); + // If the argument contains a space, we need to quote it. + if arg.contains(' ') { + arg_mod = format!("\"{}\"", arg); + } + + if SUBMAKE_FORBIDDEN_FLAGS.contains(&arg_mod.as_str()) { + return; + } + + if arg_mod.starts_with("--") { + arguments.push(arg[2..].to_string()); + } else if arg_mod.starts_with('-') { + single_flags.push(arg[1..].to_string()); + } + }); + + let mut result = "-".to_string(); + if single_flags.is_empty() { + result = "".to_string(); + } else { + result.insert_str(1, &single_flags.join("")); + } + + for argument in arguments { + result.push_str(&format!(" --{}", argument)); + } + + result +} diff --git a/src/vars.rs b/src/vars.rs index df5cd6c..abce936 100644 --- a/src/vars.rs +++ b/src/vars.rs @@ -9,94 +9,7 @@ use std::collections::HashMap; -use lazy_static::lazy_static; - -const BAD_VARIABLE_CHARS: [char; 3] = [':', '#', '=']; -const DEFAULT_SUFFIXES: [&str; 13] = [ - ".C", ".F", ".S", ".c", ".cc", ".cpp", ".def", ".f", ".m", ".mod", ".p", ".r", ".s", -]; - -/// List of variables where setting the value to blank means to reset it to the default value. -const BLANK_MEANS_DEFAULT_VARS: [&str; 1] = [".RECIPEPREFIX"]; - -lazy_static! { - /// Variables which are set in a non-recursive context by default, and can be overridden by the - /// environment. `SHELL` is not included, since it cannot be overridden by the environment, unless - /// explicitly directed to by `-e`. - static ref DEFAULT_VARS: HashMap = HashMap::from( - [ - (".RECIPEPREFIX", "\t"), - (".SHELLFLAGS", "-c"), - ("AR", "ar"), - ("ARFLAGS", "rv"), - ("AS", "as"), - ("CC", "cc"), - ("CO", "co"), - ("CTANGLE", "ctangle"), - ("CWEAVE", "cweave"), - ("CXX", "c++"), - ("FC", "f77"), - ("GET", "get"), - ("LD", "ld"), - ("LEX", "lex"), - ("LINT", "lint"), - ("M2C", "m2c"), - ("OBJC", "cc"), - ("PC", "pc"), - ("RM", "rm -f"), - ("TANGLE", "tangle"), - ("TEX", "tex"), - ("WEAVE", "weave"), - ("YACC", "yacc"), - ].map(|(k, v)| (k.to_string(), v.to_string())) - ); -} - -/// Variables which are set in a recursive context by default, and can be overridden by the -/// environment. -#[rustfmt::skip] -const DEFAULT_RECURSIVE_VARS: [(&str, &str); 36] = [ - ("OUTPUT_OPTION", "-o $@"), - - // Compiler definitions. - ("CPP", "$(CC) -E"), - ("F77", "$(FC)"), - ("F77FLAGS", "$(FFLAGS)"), - ("LEX.m", "$(LEX) $(LFLAGS) -t"), - ("YACC.m", "$(YACC) $(YFLAGS)"), - ("YACC.y", "$(YACC) $(YFLAGS)"), - - // Implicit rule definitions. - ("COMPILE.C", "$(COMPILE.cc)"), - ("COMPILE.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"), - ("COMPILE.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c"), - ("COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"), - ("COMPILE.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"), - ("COMPILE.cpp", "$(COMPILE.cc)"), - ("COMPILE.def", "$(M2C) $(M2FLAGS) $(DEFFLAGS) $(TARGET_ARCH)"), - ("COMPILE.f", "$(FC) $(FFLAGS) $(TARGET_ARCH) -c"), - ("COMPILE.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"), - ("COMPILE.mod", "$(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH)"), - ("COMPILE.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"), - ("COMPILE.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -c"), - ("COMPILE.s", "$(AS) $(ASFLAGS) $(TARGET_MACH)"), - ("LINK.C", "$(LINK.cc)"), - ("LINK.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH)"), - ("LINK.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.cpp", "$(LINK.cc)"), - ("LINK.f", "$(FC) $(FFLAGS) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.o", "$(CC) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.r", "$(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH)"), - ("LINK.s", "$(CC) $(ASFLAGS) $(LDFLAGS) $(TARGET_MACH)"), - ("LINT.c", "$(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH)"), - ("PREPROCESS.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -F"), - ("PREPROCESS.S", "$(CC) -E $(CPPFLAGS)"), - ("PREPROCESS.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -F"), -]; +const DEFAULT_RECIPE_PREFIX: char = '\t'; /// Represents the "raw" environment coming from the OS. pub type Env = HashMap; @@ -113,9 +26,10 @@ pub struct Var { pub struct Vars { map: HashMap, - /// Variable to return when a variable is not found. This is allocated during initialization to - /// prevent multiple blank allocations in the map and lifetime tracking. + // Heap-allocated "constant" `Var` objects, setup during initialization, designed to reduce + // multiple allocations and lifetime tracking. blank: Var, + default_recipe_prefix: Var, } impl Vars { @@ -128,26 +42,20 @@ impl Vars { value: "".to_string(), recursive: false, }, + default_recipe_prefix: Var { + value: DEFAULT_RECIPE_PREFIX.to_string(), + recursive: false, + }, }; - // Set default vars. - for (k, v) in DEFAULT_VARS.iter() { - vars.set(k, v, false).unwrap(); - } + let exe_path = std::env::current_exe() + .expect("failed to get executable path") + .canonicalize().expect("Failed to canonicalize executable path") + .to_string_lossy().into_owned(); - // Set default recursive vars. - for (k, v) in DEFAULT_RECURSIVE_VARS { - vars.set(k, v, true).unwrap(); - } - - // Set `SHELL` to `/bin/sh` by default. - vars.set("SHELL", "/bin/sh", false).unwrap(); - - // Set default `SUFFIXES` and `.SUFFIXES`. - vars.set("SUFFIXES", &DEFAULT_SUFFIXES.join(" "), false) - .unwrap(); - vars.set(".SUFFIXES", &DEFAULT_SUFFIXES.join(" "), false) - .unwrap(); + vars.set("MAKE", &exe_path, false).expect("Failed to set MAKE variable"); + vars.set("MAKEFLAGS", &crate::args::args_to_submake_str(), false) + .expect("Failed to set MAKEFLAGS variable"); // Use `set` to initialize data. for (k, v) in init { @@ -157,38 +65,51 @@ impl Vars { vars } - /// Public interface for getting variables. For unknown keys, the `blank` object is returned. We - /// should try to keep this interface as fast/simple as possible since it's used far more often - /// than `set` (e.g., used for each line to check for recipe prefix). + /// Public interface for getting variables. For unknown keys, the `blank` object is returned, + /// and some special keys have default values. pub fn get(&self, k: impl AsRef) -> &Var { let k = k.as_ref().trim(); - - match self.map.get(k) { - None => &self.blank, - Some(var) => var, + match k { + // Special variables. + ".RECIPEPREFIX" => match self.map.get(k) { + None => &self.default_recipe_prefix, + Some(var) => { + if var.value.is_empty() { + &self.default_recipe_prefix + } else { + var + } + } + }, + // Normal variables. + _ => match self.map.get(k) { + None => &self.blank, + Some(var) => var, + }, } } /// Public interface for setting variables. pub fn set>(&mut self, k: S, v: S, recursive: bool) -> Result<(), String> { let k = k.into().trim().to_string(); - let mut v = v.into(); + let v = v.into(); - // Do not insert bad variable names. + // Variable names must not include whitespace or any chars in the set: `:#=`. for ch in k.chars() { if ch.is_whitespace() { return Err("Variable contains whitespace.".to_string()); } - if BAD_VARIABLE_CHARS.contains(&ch) { - return Err(format!("Variable contains bad character '{}'.", ch)); + if let Some(bad_char) = match ch { + ':' => Some(':'), + '#' => Some('#'), + '=' => Some('='), + _ => None, + } { + return Err(format!("Variable contains bad character '{}'.", bad_char)); } } - if BLANK_MEANS_DEFAULT_VARS.contains(&&k[..]) && v.is_empty() { - v = DEFAULT_VARS.get(&k).unwrap().to_string(); - } - self.map.insert( k, Var { @@ -203,13 +124,7 @@ impl Vars { impl From for Vars { fn from(env: Env) -> Self { let mut vars = Self::new([]); - for (k, v) in env { - // Do not load `SHELL` from the environment. - if k == "SHELL" { - continue; - } - vars.map.insert( k, Var { @@ -232,11 +147,6 @@ mod tests { let vars = Vars::new([("A", "B")]); assert_eq!(vars.get("A").value, "B"); assert_eq!(vars.get("B").value, ""); - assert_eq!(vars.get("SHELL").value, "/bin/sh"); - assert_eq!( - vars.get("COMPILE.c").value, - "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c" - ); } #[test]