use std::env; use std::ffi::OsString; use std::path::PathBuf; use structopt::StructOpt; #[derive(StructOpt, Debug, PartialEq, Eq)] #[structopt(author, about)] pub struct Args { /// Cause environment variables, including those with null values, to override macro /// assignments within makefiles. #[structopt(short, long)] environment_overrides: bool, /// Specify a different makefile (or '-' for standard input). /// /// The argument makefile is a pathname of a description file, which is also referred /// to as the makefile. A pathname of '-' shall denote the standard input. There can /// be multiple instances of this option, and they shall be processed in the order /// specified. The effect of specifying the same option-argument more than once is /// unspecified. #[structopt(short = "f", long = "file", visible_alias = "makefile", number_of_values = 1, parse(from_os_str))] makefile: Vec, /// Ignore error codes returned by invoked commands. /// /// This mode is the same as if the special target .IGNORE were specified without /// prerequisites. #[structopt(short, long)] ignore_errors: bool, /// Continue to update other targets that do not depend on the current target if a /// non-ignored error occurs while executing the commands to bring a target /// up-to-date. #[structopt(short, long)] keep_going: bool, /// Write commands that would be executed on standard output, but do not execute them /// (but execute lines starting with '+'). /// /// However, lines with a ( '+' ) prefix shall be executed. In this mode, /// lines with an at-sign ( '@' ) character prefix shall be written to standard /// output. #[structopt(short = "n", long, visible_alias = "just-print", visible_alias = "recon")] dry_run: bool, /// Write to standard output the complete set of macro definitions and target /// descriptions. /// /// The output format is unspecified. #[structopt(short, long, visible_alias = "print-data-base")] print_everything: bool, /// Return a zero exit value if the target file is up-to-date; otherwise, return an /// exit value of 1. /// /// Targets shall not be updated if this option is specified. However, a makefile /// command line (associated with the targets) with a ( '+' ) prefix /// shall be executed. #[structopt(short, long)] question: bool, /// Clear the suffix list and do not use the built-in rules. #[structopt(short = "r", long)] no_builtin_rules: bool, /// Terminate make if an error occurs while executing the commands to bring a target /// up-to-date (default behavior, required by POSIX to be also a flag for some /// reason). /// /// This shall be the default and the opposite of -k. #[structopt(short = "S", long, visible_alias = "stop", hidden = true, overrides_with="keep-going")] no_keep_going: bool, /// Do not write makefile command lines or touch messages to standard output before /// executing. /// /// This mode shall be the same as if the special target .SILENT were specified /// without prerequisites. #[structopt(short, long, visible_alias = "quiet")] silent: bool, /// Update the modification time of each target as though a touch target had been /// executed. /// /// Targets that have prerequisites but no commands, or that are already up-to-date, /// shall not be touched in this manner. Write messages to standard output for each /// target file indicating the name of the file and that it was touched. Normally, /// the makefile command lines associated with each target are not executed. However, /// a command line with a ( '+' ) prefix shall be executed. #[structopt(short, long)] touch: bool, /// Target names or macro definitions. /// /// If no target is specified, while make is processing the makefiles, the first /// target that make encounters that is not a special target or an inference rule /// shall be used. targets_or_macros: Vec, } impl Args { pub fn from_env_and_args() -> Args { // POSIX spec says "Any options specified in the MAKEFLAGS environment variable // shall be evaluated before any options specified on the make utility command // line." // TODO make sure only flags show up in MAKEFLAGS (i.e. not -f or other stuff) // TODO accept "option letters without the leading characters" let env_makeflags = env::var("MAKEFLAGS").unwrap_or_default(); let env_makeflags = env_makeflags.split_whitespace() .map(|x| OsString::from(x)); let mut args = env::args_os(); // per the structopt docs, the first argument will be used as the binary name, // so we need to make sure it goes in before MAKEFLAGS let arg0 = args.next().unwrap_or_else(|| env!("CARGO_PKG_NAME").into()); let args = ::std::iter::once(arg0) .chain(env_makeflags.into_iter()) .chain(args); Args::from_iter(args) } } #[cfg(test)] mod test { use super::*; #[test] fn no_args() { let args: Vec = vec!["makers".into()]; let args = Args::from_iter(args.into_iter()); assert_eq!(args, Args { environment_overrides: false, makefile: vec![], ignore_errors: false, keep_going: false, dry_run: false, print_everything: false, question: false, no_builtin_rules: false, no_keep_going: false, silent: false, touch: false, targets_or_macros: vec![], }); } #[test] fn kitchen_sink_args() { let args = "makers -eiknpqrstf foo -f bruh bar baz=yeet"; let args = Args::from_iter(args.split_whitespace()); assert_eq!(args, Args { environment_overrides: true, makefile: vec!["foo".into(), "bruh".into()], ignore_errors: true, keep_going: true, dry_run: true, print_everything: true, question: true, no_builtin_rules: true, no_keep_going: false, silent: true, touch: true, targets_or_macros: vec!["bar".into(), "baz=yeet".into()], }); } #[test] fn keep_going_wrestling() { let args = "makers -kSkSkSSSkSkkSk -k -S -k -k -S -S -k"; let args = Args::from_iter(args.split_whitespace()); assert_eq!(args, Args { environment_overrides: false, makefile: vec![], ignore_errors: false, keep_going: true, dry_run: false, print_everything: false, question: false, no_builtin_rules: false, no_keep_going: false, silent: false, touch: false, targets_or_macros: vec![], }); } // TODO test MAKEFLAGS }