use std::{path::PathBuf, fs}; use path_clean::PathClean; use regex::Regex; use crate::makefile::*; use crate::ARGS; #[derive(Debug)] #[allow(dead_code)] pub enum BuildRuleKind { Object(T), Target(T), Directory(T), Convenience(T), } #[derive(Debug,Clone)] pub struct BuildRule { pub target: String, pub prerequisites: Vec, pub recipe_commands: Vec, } impl BuildRule { pub fn get_headers_from_c_file(path: &str, makefile: &Makefile) -> Vec { let mut headers: Vec = Vec::new(); let file_contents = fs::read_to_string(path).unwrap_or_else(|e| { panic!("error: could not read file {path}: {e}"); }); let file_path = PathBuf::from(path); let file_dir = file_path.parent().unwrap(); let includes_regex = Regex::new(r#"#include\s+"(.*?)""#).unwrap(); let caps = includes_regex.captures_iter(&file_contents); 'fileloop: for (_, [include_file]) in caps.map(|c| c.extract()) { let path = file_dir.join(PathBuf::from(include_file)).clean(); match path.exists() { true => { headers.push(String::from(path.to_str().unwrap())); continue; }, false => (), } for dir in makefile.include_dirs.iter() { let dir = PathBuf::from(&dir); let path = dir.join(PathBuf::from(include_file)).clean(); match path.exists() { true => { headers.push(String::from(path.to_str().unwrap())); continue 'fileloop; }, false => (), } } let path = file_dir.join(PathBuf::from(include_file)); headers.push(String::from(path.to_str().unwrap())); } headers } pub fn new_from_c_file(path: &str, makefile: &Makefile) -> BuildRule { let mut prerequisites: Vec = Vec::new(); prerequisites.push((PathBuf::from(path)).clean().to_str().unwrap().to_string()); prerequisites.append(&mut BuildRule::get_headers_from_c_file(path, makefile)); prerequisites.push(String::from("| $(OBJDIR)")); let file_path = PathBuf::from(path); let obj_name = file_path.with_extension("o"); let obj_name = obj_name.file_name().unwrap(); let obj_path = PathBuf::from("$(OBJDIR)").join(obj_name); let obj_path = obj_path.to_str().unwrap(); let compile_command = format!("$(CC) $(CFLAGS) $(INCS) -o {} -c {}", obj_path, path); BuildRule { target: String::from(obj_path), prerequisites, recipe_commands: vec![compile_command] } } } impl ToString for BuildRuleKind { fn to_string(&self) -> String { let rule = match self { BuildRuleKind::Object(v) => v, BuildRuleKind::Target(v) => v, BuildRuleKind::Directory(v) => v, BuildRuleKind::Convenience(v) => v, }; let mut ret = String::new(); ret.push_str(&format!("{}: ", rule.target)); ret.push_str(&rule.prerequisites.join(" ")); ret.push_str(&format!("\n")); ARGS.with(|args| { let mut command_prefix = "\t"; let mut special = false; if args.pretty { command_prefix = "\t@"; let verb = match self { BuildRuleKind::Object(_) => "Building", BuildRuleKind::Target(_) => { if args.static_library { "Creating static library" } else if args.dynamic_library { "Creating dynamic library" } else { "Linking" } }, BuildRuleKind::Directory(_) => "Creating directory", BuildRuleKind::Convenience(_) => { special = true; if rule.target.contains("clean") { ret.push_str(&format!("{}printf ':: Cleaning up ...\\e[0m'\n", command_prefix)); } if rule.target.contains("run") { ret.push_str(&format!("{}printf '\\e[36m:: Running\\e[0m \\e[1m%s\\e[0m \\e[36m...\\e[0m\\n' $(TARGET)\n", command_prefix)); } "" }, }; if !special { ret.push_str(&format!("{}printf ':: {} \\e[1m%s\\e[0m ...\\e[0m' {}\n", command_prefix, verb, (PathBuf::from(&rule.target)).file_name().unwrap().to_str().unwrap())); } } ret.push_str(&rule.recipe_commands.iter() .map(|v| format!("{}{}", command_prefix, v)) .collect::>() .join("\n")); if args.pretty { ret.push_str(&format!("\n{}printf ' \\e[32mdone\\e[0m\\n'", command_prefix)); } }); ret } }