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 std::process::exit;
|
||||||
|
|
||||||
use regex::Regex;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use path_clean::PathClean;
|
|
||||||
|
|
||||||
const START_KEY: &str = "#=mgen_start=#\n";
|
mod build_rule;
|
||||||
const END_KEY: &str = "\n#=mgen_end=#";
|
mod makefile;
|
||||||
|
mod rule_list;
|
||||||
|
use makefile::*;
|
||||||
|
use rule_list::*;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
@ -35,407 +32,6 @@ struct Args {
|
||||||
|
|
||||||
thread_local! {static ARGS: Args = Args::parse()}
|
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() {
|
fn main() {
|
||||||
|
|
||||||
let mut makefile_name = String::new();
|
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