//! A (mostly) [POSIX-compatible](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html) //! make implemented in Rust. Not explicitly aiming for full support for //! [every GNU make feature](https://www.gnu.org/software/make/manual/html_node/index.html), //! but adding whichever features are strictly necessary to be compatible with existing //! GNUish makefiles. #![warn( unsafe_code, unused_crate_dependencies, variant_size_differences, clippy::cargo, clippy::nursery, clippy::str_to_string, clippy::unwrap_used, clippy::integer_arithmetic, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unwrap_in_result, clippy::clone_on_ref_ptr, clippy::todo )] use std::env; use std::fs::metadata; use std::io::stdin; use std::path::PathBuf; use std::rc::Rc; use eyre::{bail, Result}; mod args; mod makefile; use args::Args; use makefile::{Makefile, MakefileReader}; const DEFAULT_PATHS: &[&str] = &[ #[cfg(feature = "full")] "GNUmakefile", "makefile", "Makefile", ]; fn main() -> Result<()> { env_logger::init(); jane_eyre::install()?; let mut args = Args::from_env_and_args(); #[cfg(feature = "full")] if let Some(dir) = args.directory.as_ref() { env::set_current_dir(dir)?; } // If no makefile is specified, try some options. if args.makefile.is_empty() { let default_makefile = DEFAULT_PATHS.iter().find(|name| metadata(name).is_ok()); let default_makefile = match default_makefile { Some(x) => x, None => bail!("no makefile found (tried {})", DEFAULT_PATHS.join(", ")), }; args.makefile = vec![default_makefile.into()]; } // Read in the makefile(s) specified. env::set_var("MAKEFLAGS", args.makeflags()); // TODO dump command-line macros into environment // TODO add SHELL macro let mut makefile = Makefile::new(&args); let paths = Default::default(); for filename in &args.makefile { if filename == &PathBuf::from("-") { let macros = makefile.macros.with_overlay(); let file = MakefileReader::read(&args, macros, stdin().lock(), "-", Rc::clone(&paths))? .finish(); makefile.extend(file)?; } else { let macros = makefile.macros.with_overlay(); let file = MakefileReader::read_file(&args, macros, filename, Rc::clone(&paths))?.finish(); makefile.extend(file)?; }; } let makefiles_outdated = paths .borrow() .iter() .filter(|path| { makefile .get_target(path) .map_or(false, |target| !target.borrow().is_up_to_date(&makefile)) }) .cloned() .collect::>(); for outdated in makefiles_outdated { eprintln!("makefile {} out of date, rebuilding", outdated); makefile.update_target(&outdated)?; let macros = makefile.macros.with_overlay(); let file = MakefileReader::read_file(&args, macros, &outdated, Default::default())?.finish(); // TODO forget the stale data // TODO reread all the things, not just this one makefile.extend(file)?; } if args.print_everything { println!("{}", &makefile); } else { let targets = if args.targets().count() == 0 { let first_target = makefile.first_non_special_target.as_deref(); match first_target { Some(x) => vec![x], None => bail!("no targets given on command line or found in makefile."), } } else { args.targets().collect() }; for target in targets { makefile.update_target(target)?; } } Ok(()) }