finally organized code
This commit is contained in:
		
							parent
							
								
									81d51c0dbf
								
							
						
					
					
						commit
						60f122de78
					
				
					 4 changed files with 423 additions and 409 deletions
				
			
		
							
								
								
									
										140
									
								
								src/build_rule.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/build_rule.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,140 @@
 | 
			
		|||
use std::{path::PathBuf, fs};
 | 
			
		||||
use crate::ARGS;
 | 
			
		||||
 | 
			
		||||
use path_clean::PathClean;
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
 | 
			
		||||
use crate::makefile::*;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub enum BuildRuleKind<T> {
 | 
			
		||||
    Object(T),
 | 
			
		||||
    Target(T),
 | 
			
		||||
    Directory(T),
 | 
			
		||||
    Convenience(T),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug,Clone)]
 | 
			
		||||
pub struct BuildRule {
 | 
			
		||||
    pub target: String,
 | 
			
		||||
    pub prerequisites: Vec<String>,
 | 
			
		||||
    pub recipe_commands: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl BuildRule {
 | 
			
		||||
    pub fn get_headers_from_c_file(path: &str, makefile: &Makefile) -> Vec<String> {
 | 
			
		||||
        let mut headers: Vec<String> = 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<String> = Vec::new();
 | 
			
		||||
 | 
			
		||||
        prerequisites.push(String::from(path));
 | 
			
		||||
 | 
			
		||||
        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<BuildRule> {
 | 
			
		||||
    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(_) => "Linking",
 | 
			
		||||
                    BuildRuleKind::Directory(_) => "Creating directory",
 | 
			
		||||
                    BuildRuleKind::Convenience(_) => {
 | 
			
		||||
                        special = true;
 | 
			
		||||
                        if rule.target.contains("clean") {
 | 
			
		||||
                            ret.push_str(&format!("{}printf '\\e[90m:: Cleaning up ...\\e[0m'\n", command_prefix));
 | 
			
		||||
                        }
 | 
			
		||||
                        if rule.target.contains("run") {
 | 
			
		||||
                            ret.push_str(&format!("{}printf '\\e[34m:: Running\\e[0m \\e[1m%s\\e[0m \\e[34m...\\e[0m\\n' $(TARGET)\n", command_prefix));
 | 
			
		||||
                        }
 | 
			
		||||
                        ""
 | 
			
		||||
                    },
 | 
			
		||||
                };
 | 
			
		||||
                if !special {
 | 
			
		||||
                    ret.push_str(&format!("{}printf '\\e[90m:: {}\\e[0m \\e[1m%s\\e[0m \\e[90m...\\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::<Vec<_>>()
 | 
			
		||||
                         .join("\n"));
 | 
			
		||||
 | 
			
		||||
            if args.pretty {
 | 
			
		||||
                ret.push_str(&format!("\n{}printf ' \\e[32mdone\\e[0m\\n'", command_prefix));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										414
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										414
									
								
								src/main.rs
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,14 +1,11 @@
 | 
			
		|||
use std::path::PathBuf;
 | 
			
		||||
use std::io::ErrorKind;
 | 
			
		||||
use std::fs::{self, DirEntry};
 | 
			
		||||
use std::process::exit;
 | 
			
		||||
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use path_clean::PathClean;
 | 
			
		||||
 | 
			
		||||
const START_KEY: &str = "#=mgen_start=#\n";
 | 
			
		||||
const END_KEY: &str = "\n#=mgen_end=#";
 | 
			
		||||
mod build_rule;
 | 
			
		||||
mod makefile;
 | 
			
		||||
mod rule_list;
 | 
			
		||||
use makefile::*;
 | 
			
		||||
use rule_list::*;
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
struct Args {
 | 
			
		||||
| 
						 | 
				
			
			@ -35,407 +32,6 @@ struct Args {
 | 
			
		|||
 | 
			
		||||
thread_local! {static ARGS: Args = Args::parse()}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
enum BuildRuleKind<T> {
 | 
			
		||||
    Object(T),
 | 
			
		||||
    Target(T),
 | 
			
		||||
    Directory(T),
 | 
			
		||||
    Convenience(T),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug,Clone)]
 | 
			
		||||
struct BuildRule {
 | 
			
		||||
    target: String,
 | 
			
		||||
    prerequisites: Vec<String>,
 | 
			
		||||
    recipe_commands: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
struct Makefile {
 | 
			
		||||
    path: String,
 | 
			
		||||
    prologue: String,
 | 
			
		||||
    mgen_zone: String,
 | 
			
		||||
    epilogue: String,
 | 
			
		||||
    include_dirs: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct RuleList(Vec<BuildRuleKind<BuildRule>>);
 | 
			
		||||
 | 
			
		||||
impl BuildRule {
 | 
			
		||||
    fn get_headers_from_c_file(path: &str, makefile: &Makefile) -> Vec<String> {
 | 
			
		||||
        let mut headers: Vec<String> = 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
 | 
			
		||||
    }
 | 
			
		||||
    fn new_from_c_file(path: &str, makefile: &Makefile) -> BuildRule {
 | 
			
		||||
 | 
			
		||||
        let mut prerequisites: Vec<String> = Vec::new();
 | 
			
		||||
 | 
			
		||||
        prerequisites.push(String::from(path));
 | 
			
		||||
 | 
			
		||||
        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<BuildRule> {
 | 
			
		||||
    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(_) => "Linking",
 | 
			
		||||
                    BuildRuleKind::Directory(_) => "Creating directory",
 | 
			
		||||
                    BuildRuleKind::Convenience(_) => {
 | 
			
		||||
                        special = true;
 | 
			
		||||
                        if rule.target.contains("clean") {
 | 
			
		||||
                            ret.push_str(&format!("{}printf '\\e[90m:: Cleaning up ...\\e[0m'\n", command_prefix));
 | 
			
		||||
                        }
 | 
			
		||||
                        if rule.target.contains("run") {
 | 
			
		||||
                            ret.push_str(&format!("{}printf '\\e[34m:: Running\\e[0m \\e[1m%s\\e[0m \\e[34m...\\e[0m\\n' $(TARGET)\n", command_prefix));
 | 
			
		||||
                        }
 | 
			
		||||
                        ""
 | 
			
		||||
                    },
 | 
			
		||||
                };
 | 
			
		||||
                if !special {
 | 
			
		||||
                    ret.push_str(&format!("{}printf '\\e[90m:: {}\\e[0m \\e[1m%s\\e[0m \\e[90m...\\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::<Vec<_>>()
 | 
			
		||||
                         .join("\n"));
 | 
			
		||||
 | 
			
		||||
            if args.pretty {
 | 
			
		||||
                ret.push_str(&format!("\n{}printf ' \\e[32mdone\\e[0m\\n'", command_prefix));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Makefile {
 | 
			
		||||
    fn new_from_path(path: &str) -> Makefile {
 | 
			
		||||
        let contents = match fs::read_to_string(path) {
 | 
			
		||||
            Ok(v) => v,
 | 
			
		||||
            Err(e) => match e.kind() {
 | 
			
		||||
                // doesn't matter if the file doesn't exist, it will be created 
 | 
			
		||||
                // later.
 | 
			
		||||
                ErrorKind::NotFound => return Makefile { 
 | 
			
		||||
                    path: String::from(path), prologue: String::from(""),
 | 
			
		||||
                    mgen_zone: String::from(""), epilogue: String::from(""),
 | 
			
		||||
                    include_dirs: vec![String::from("")] },
 | 
			
		||||
                _ => panic!("error: cannot read makefile: {e}")
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let contains_start = contents.find(START_KEY).is_some();
 | 
			
		||||
        let contains_end = contents.find(END_KEY).is_some();
 | 
			
		||||
 | 
			
		||||
        if (contains_start && !contains_end) || (!contains_start && 
 | 
			
		||||
                                                                contains_end) {
 | 
			
		||||
            panic!("error: makefile contains one mgen guard but not the other. cannot continue.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !(contains_start && contains_end) {
 | 
			
		||||
            let include_dirs = Makefile::get_include_dirs(&contents);
 | 
			
		||||
            return Makefile { path: String::from(path), prologue: contents,
 | 
			
		||||
                mgen_zone: String::from(""), epilogue: String::from(""), include_dirs };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // contains the part before the start key and everything after
 | 
			
		||||
        let pre_and_after: Vec<&str> = contents.split(START_KEY).collect();
 | 
			
		||||
        // contains the space between start and end keys and also the space after
 | 
			
		||||
        let gen_and_post: Vec<&str> = pre_and_after[1].split(END_KEY).collect();
 | 
			
		||||
 | 
			
		||||
        let include_dirs = Makefile::get_include_dirs(&contents);
 | 
			
		||||
        
 | 
			
		||||
        Makefile { 
 | 
			
		||||
            path: String::from(path),
 | 
			
		||||
            prologue: String::from(pre_and_after[0]),
 | 
			
		||||
            mgen_zone: String::from(gen_and_post[0]),
 | 
			
		||||
            epilogue: String::from(gen_and_post[1]),
 | 
			
		||||
            include_dirs
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    fn get_include_dirs(search: &String) -> Vec<String> {
 | 
			
		||||
        let mut include_dirs: Vec<String> = Vec::new();
 | 
			
		||||
 | 
			
		||||
        let makefile_regex = Regex::new(r"INCS\s*=\s*(.*)").unwrap();
 | 
			
		||||
        let caps = makefile_regex.captures_iter(search);
 | 
			
		||||
        let dirlist = match caps.map(|c| c.extract()).last() {
 | 
			
		||||
            Some((_, [v])) => v,
 | 
			
		||||
            None => {
 | 
			
		||||
                return include_dirs
 | 
			
		||||
            }
 | 
			
		||||
            ,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        let incdir_regex = Regex::new(r"\s*-I\s*(.*?)(?:[\s]|$)").unwrap();
 | 
			
		||||
        let caps = incdir_regex.captures_iter(&dirlist);
 | 
			
		||||
        for (_, [dir]) in caps.map(|c| c.extract()) {
 | 
			
		||||
            include_dirs.push(String::from(dir));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        include_dirs
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    fn contains_builddir_def(&self) -> bool {
 | 
			
		||||
        let re = Regex::new(r"\s+BUILDDIR = ").unwrap();
 | 
			
		||||
        re.is_match(&(self.prologue)) || re.is_match(&(self.epilogue))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn contains_objdir_def(&self) -> bool {
 | 
			
		||||
        let re = Regex::new(r"\s+OBJDIR = ").unwrap();
 | 
			
		||||
        re.is_match(&(self.prologue)) || re.is_match(&(self.epilogue))
 | 
			
		||||
    }
 | 
			
		||||
     
 | 
			
		||||
    fn write_new_generated_content(&self, generated_content: String) -> std::io::Result<()> {
 | 
			
		||||
        let content = [&self.prologue, START_KEY, &generated_content, END_KEY, &self.epilogue].concat();
 | 
			
		||||
 | 
			
		||||
        fs::write(&self.path, &content)?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToString for RuleList {
 | 
			
		||||
    fn to_string(&self) -> String {
 | 
			
		||||
        let targets = &self.get_target_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
        let objects = &self.get_object_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
        let directories = &self.get_directory_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
        let conveniences = &self.get_convenience_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
 | 
			
		||||
        let all = [targets, objects, directories, conveniences];
 | 
			
		||||
        let mut nonzero: Vec<String> = Vec::new();
 | 
			
		||||
 | 
			
		||||
        for category in all {
 | 
			
		||||
            if category.len() != 0 {
 | 
			
		||||
                nonzero.push(category.clone());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        nonzero.join("\n\n")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RuleList {
 | 
			
		||||
    fn new() -> RuleList{
 | 
			
		||||
        RuleList(vec![])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn recursively_gen_objects(&mut self, proj_dir_path: &str, makefile: &Makefile) -> std::io::Result<()>{
 | 
			
		||||
        let files = fs::read_dir(proj_dir_path)?;
 | 
			
		||||
        for file in files {
 | 
			
		||||
            let file = file?;
 | 
			
		||||
            let is_dir = file.metadata()?.is_dir();
 | 
			
		||||
 | 
			
		||||
            // ignore hidden files
 | 
			
		||||
            if file_is_hidden(&file) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let path = file.path();
 | 
			
		||||
 | 
			
		||||
            if is_dir {
 | 
			
		||||
                self.recursively_gen_objects(path.to_str().unwrap(), makefile)?;
 | 
			
		||||
            } else {
 | 
			
		||||
                match path.extension() {
 | 
			
		||||
                    Some(v) => {
 | 
			
		||||
                        let v = v.to_str().unwrap();
 | 
			
		||||
                        if v != "c" {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        let path = path.to_str().unwrap();
 | 
			
		||||
                        self.0.push(BuildRuleKind::Object(BuildRule::new_from_c_file(path, makefile)));
 | 
			
		||||
                    },
 | 
			
		||||
                    None => {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_obj_targets(&self) -> Vec<String>{
 | 
			
		||||
        let mut ret: Vec<String> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Object(r) => ret.push(r.target.clone()),
 | 
			
		||||
                _ => break,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn add_target_rule(&mut self) {
 | 
			
		||||
        let mut objects = self.get_obj_targets();
 | 
			
		||||
        let target_comm = String::from(
 | 
			
		||||
            format!("$(CC) $(LDFLAGS) $(INCS) -o $(BUILDDIR)/$(TARGET) {} $(LDLIBS)", objects.join(" "))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        objects.push(String::from("| $(OBJDIR) $(BUILDDIR)"));
 | 
			
		||||
        let prereqs = objects;
 | 
			
		||||
 | 
			
		||||
        self.0.push(BuildRuleKind::Target(BuildRule {
 | 
			
		||||
            target: String::from("$(BUILDDIR)/$(TARGET)"),
 | 
			
		||||
            prerequisites: prereqs,
 | 
			
		||||
            recipe_commands: vec![target_comm],
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn add_directory_rule(&mut self, dir: &str) {
 | 
			
		||||
        self.0.push(BuildRuleKind::Directory(BuildRule {
 | 
			
		||||
            target: dir.to_string(),
 | 
			
		||||
            prerequisites: vec![],
 | 
			
		||||
            recipe_commands: vec![format!("mkdir -p {dir}").to_string()]
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn add_clean_rule(&mut self) {
 | 
			
		||||
        self.0.push(BuildRuleKind::Convenience(BuildRule {
 | 
			
		||||
            target: String::from(".PHONY: clean\nclean"),
 | 
			
		||||
            prerequisites: vec![],
 | 
			
		||||
            recipe_commands: vec![String::from("rm -r $(OBJDIR)"), String::from("rm -r $(BUILDDIR)")]
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn add_run_rule(&mut self) {
 | 
			
		||||
        self.0.push(BuildRuleKind::Convenience(BuildRule {
 | 
			
		||||
            target: String::from(".PHONY: run\nrun"),
 | 
			
		||||
            prerequisites: vec![String::from("$(BUILDDIR)/$(TARGET)")],
 | 
			
		||||
            recipe_commands: vec![String::from("./$(BUILDDIR)/$(TARGET)")]
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_target_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Target(r) => ret.push(BuildRuleKind::Target(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_object_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Object(r) => ret.push(BuildRuleKind::Object(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_directory_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Directory(r) => ret.push(BuildRuleKind::Directory(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_convenience_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Convenience(r) => ret.push(BuildRuleKind::Convenience(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn file_is_hidden(file: &DirEntry) -> bool{
 | 
			
		||||
    let file_name = file.file_name();
 | 
			
		||||
    let str_name = file_name.to_str().unwrap();
 | 
			
		||||
    let first_char = str_name.chars().nth(0).unwrap();
 | 
			
		||||
    if first_char == '.' {
 | 
			
		||||
        true
 | 
			
		||||
    } else {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
 | 
			
		||||
    let mut makefile_name = String::new();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										104
									
								
								src/makefile.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/makefile.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
use std::{fs, io::ErrorKind};
 | 
			
		||||
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
 | 
			
		||||
const START_KEY: &str = "#=mgen_start=#\n";
 | 
			
		||||
const END_KEY: &str = "\n#=mgen_end=#";
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[allow(dead_code)]
 | 
			
		||||
pub struct Makefile {
 | 
			
		||||
    pub path: String,
 | 
			
		||||
    pub prologue: String,
 | 
			
		||||
    pub mgen_zone: String,
 | 
			
		||||
    pub epilogue: String,
 | 
			
		||||
    pub include_dirs: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Makefile {
 | 
			
		||||
    pub fn new_from_path(path: &str) -> Makefile {
 | 
			
		||||
        let contents = match fs::read_to_string(path) {
 | 
			
		||||
            Ok(v) => v,
 | 
			
		||||
            Err(e) => match e.kind() {
 | 
			
		||||
                // doesn't matter if the file doesn't exist, it will be created 
 | 
			
		||||
                // later.
 | 
			
		||||
                ErrorKind::NotFound => return Makefile { 
 | 
			
		||||
                    path: String::from(path), prologue: String::from(""),
 | 
			
		||||
                    mgen_zone: String::from(""), epilogue: String::from(""),
 | 
			
		||||
                    include_dirs: vec![String::from("")] },
 | 
			
		||||
                _ => panic!("error: cannot read makefile: {e}")
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let contains_start = contents.find(START_KEY).is_some();
 | 
			
		||||
        let contains_end = contents.find(END_KEY).is_some();
 | 
			
		||||
 | 
			
		||||
        if (contains_start && !contains_end) || (!contains_start && 
 | 
			
		||||
                                                                contains_end) {
 | 
			
		||||
            panic!("error: makefile contains one mgen guard but not the other. cannot continue.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !(contains_start && contains_end) {
 | 
			
		||||
            let include_dirs = Makefile::get_include_dirs(&contents);
 | 
			
		||||
            return Makefile { path: String::from(path), prologue: contents,
 | 
			
		||||
                mgen_zone: String::from(""), epilogue: String::from(""), include_dirs };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // contains the part before the start key and everything after
 | 
			
		||||
        let pre_and_after: Vec<&str> = contents.split(START_KEY).collect();
 | 
			
		||||
        // contains the space between start and end keys and also the space after
 | 
			
		||||
        let gen_and_post: Vec<&str> = pre_and_after[1].split(END_KEY).collect();
 | 
			
		||||
 | 
			
		||||
        let include_dirs = Makefile::get_include_dirs(&contents);
 | 
			
		||||
        
 | 
			
		||||
        Makefile { 
 | 
			
		||||
            path: String::from(path),
 | 
			
		||||
            prologue: String::from(pre_and_after[0]),
 | 
			
		||||
            mgen_zone: String::from(gen_and_post[0]),
 | 
			
		||||
            epilogue: String::from(gen_and_post[1]),
 | 
			
		||||
            include_dirs
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn get_include_dirs(search: &String) -> Vec<String> {
 | 
			
		||||
        let mut include_dirs: Vec<String> = Vec::new();
 | 
			
		||||
 | 
			
		||||
        let makefile_regex = Regex::new(r"INCS\s*=\s*(.*)").unwrap();
 | 
			
		||||
        let caps = makefile_regex.captures_iter(search);
 | 
			
		||||
        let dirlist = match caps.map(|c| c.extract()).last() {
 | 
			
		||||
            Some((_, [v])) => v,
 | 
			
		||||
            None => {
 | 
			
		||||
                return include_dirs
 | 
			
		||||
            }
 | 
			
		||||
            ,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        let incdir_regex = Regex::new(r"\s*-I\s*(.*?)(?:[\s]|$)").unwrap();
 | 
			
		||||
        let caps = incdir_regex.captures_iter(&dirlist);
 | 
			
		||||
        for (_, [dir]) in caps.map(|c| c.extract()) {
 | 
			
		||||
            include_dirs.push(String::from(dir));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        include_dirs
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    pub fn contains_builddir_def(&self) -> bool {
 | 
			
		||||
        let re = Regex::new(r"\s+BUILDDIR = ").unwrap();
 | 
			
		||||
        re.is_match(&(self.prologue)) || re.is_match(&(self.epilogue))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn contains_objdir_def(&self) -> bool {
 | 
			
		||||
        let re = Regex::new(r"\s+OBJDIR = ").unwrap();
 | 
			
		||||
        re.is_match(&(self.prologue)) || re.is_match(&(self.epilogue))
 | 
			
		||||
    }
 | 
			
		||||
     
 | 
			
		||||
    pub fn write_new_generated_content(&self, generated_content: String) -> std::io::Result<()> {
 | 
			
		||||
        let content = [&self.prologue, START_KEY, &generated_content, END_KEY, &self.epilogue].concat();
 | 
			
		||||
 | 
			
		||||
        fs::write(&self.path, &content)?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										174
									
								
								src/rule_list.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								src/rule_list.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,174 @@
 | 
			
		|||
use std::fs;
 | 
			
		||||
use std::fs::DirEntry;
 | 
			
		||||
 | 
			
		||||
use crate::build_rule::*;
 | 
			
		||||
use crate::makefile::*;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct RuleList(pub Vec<BuildRuleKind<BuildRule>>);
 | 
			
		||||
 | 
			
		||||
impl RuleList {
 | 
			
		||||
    pub fn new() -> RuleList{
 | 
			
		||||
        RuleList(vec![])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn recursively_gen_objects(&mut self, proj_dir_path: &str, makefile: &Makefile) -> std::io::Result<()>{
 | 
			
		||||
        let files = fs::read_dir(proj_dir_path)?;
 | 
			
		||||
        for file in files {
 | 
			
		||||
            let file = file?;
 | 
			
		||||
            let is_dir = file.metadata()?.is_dir();
 | 
			
		||||
 | 
			
		||||
            // ignore hidden files
 | 
			
		||||
            if file_is_hidden(&file) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let path = file.path();
 | 
			
		||||
 | 
			
		||||
            if is_dir {
 | 
			
		||||
                self.recursively_gen_objects(path.to_str().unwrap(), makefile)?;
 | 
			
		||||
            } else {
 | 
			
		||||
                match path.extension() {
 | 
			
		||||
                    Some(v) => {
 | 
			
		||||
                        let v = v.to_str().unwrap();
 | 
			
		||||
                        if v != "c" {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        let path = path.to_str().unwrap();
 | 
			
		||||
                        self.0.push(BuildRuleKind::Object(BuildRule::new_from_c_file(path, makefile)));
 | 
			
		||||
                    },
 | 
			
		||||
                    None => {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    },
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_obj_targets(&self) -> Vec<String>{
 | 
			
		||||
        let mut ret: Vec<String> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Object(r) => ret.push(r.target.clone()),
 | 
			
		||||
                _ => break,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_target_rule(&mut self) {
 | 
			
		||||
        let mut objects = self.get_obj_targets();
 | 
			
		||||
        let target_comm = String::from(
 | 
			
		||||
            format!("$(CC) $(LDFLAGS) $(INCS) -o $(BUILDDIR)/$(TARGET) {} $(LDLIBS)", objects.join(" "))
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        objects.push(String::from("| $(OBJDIR) $(BUILDDIR)"));
 | 
			
		||||
        let prereqs = objects;
 | 
			
		||||
 | 
			
		||||
        self.0.push(BuildRuleKind::Target(BuildRule {
 | 
			
		||||
            target: String::from("$(BUILDDIR)/$(TARGET)"),
 | 
			
		||||
            prerequisites: prereqs,
 | 
			
		||||
            recipe_commands: vec![target_comm],
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_directory_rule(&mut self, dir: &str) {
 | 
			
		||||
        self.0.push(BuildRuleKind::Directory(BuildRule {
 | 
			
		||||
            target: dir.to_string(),
 | 
			
		||||
            prerequisites: vec![],
 | 
			
		||||
            recipe_commands: vec![format!("mkdir -p {dir}").to_string()]
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_clean_rule(&mut self) {
 | 
			
		||||
        self.0.push(BuildRuleKind::Convenience(BuildRule {
 | 
			
		||||
            target: String::from(".PHONY: clean\nclean"),
 | 
			
		||||
            prerequisites: vec![],
 | 
			
		||||
            recipe_commands: vec![String::from("rm -r $(OBJDIR)"), String::from("rm -r $(BUILDDIR)")]
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn add_run_rule(&mut self) {
 | 
			
		||||
        self.0.push(BuildRuleKind::Convenience(BuildRule {
 | 
			
		||||
            target: String::from(".PHONY: run\nrun"),
 | 
			
		||||
            prerequisites: vec![String::from("$(BUILDDIR)/$(TARGET)")],
 | 
			
		||||
            recipe_commands: vec![String::from("./$(BUILDDIR)/$(TARGET)")]
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_target_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Target(r) => ret.push(BuildRuleKind::Target(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_object_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Object(r) => ret.push(BuildRuleKind::Object(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_directory_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Directory(r) => ret.push(BuildRuleKind::Directory(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_convenience_rules(&self) -> Vec<BuildRuleKind<BuildRule>> {
 | 
			
		||||
        let mut ret: Vec<BuildRuleKind<BuildRule>> = Vec::new();
 | 
			
		||||
        for rule in &self.0 {
 | 
			
		||||
            match rule {
 | 
			
		||||
                BuildRuleKind::Convenience(r) => ret.push(BuildRuleKind::Convenience(r.clone())),
 | 
			
		||||
                _ => continue,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ret
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn file_is_hidden(file: &DirEntry) -> bool{
 | 
			
		||||
    let file_name = file.file_name();
 | 
			
		||||
    let str_name = file_name.to_str().unwrap();
 | 
			
		||||
    let first_char = str_name.chars().nth(0).unwrap();
 | 
			
		||||
    if first_char == '.' {
 | 
			
		||||
        true
 | 
			
		||||
    } else {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToString for RuleList {
 | 
			
		||||
    fn to_string(&self) -> String {
 | 
			
		||||
        let targets = &self.get_target_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
        let objects = &self.get_object_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
        let directories = &self.get_directory_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
        let conveniences = &self.get_convenience_rules().iter().map(|v| v.to_string()).collect::<Vec<_>>().join("\n");
 | 
			
		||||
 | 
			
		||||
        let all = [targets, objects, directories, conveniences];
 | 
			
		||||
        let mut nonzero: Vec<String> = Vec::new();
 | 
			
		||||
 | 
			
		||||
        for category in all {
 | 
			
		||||
            if category.len() != 0 {
 | 
			
		||||
                nonzero.push(category.clone());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        nonzero.join("\n\n")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in a new issue