proper data model, target inheritance done.

This commit is contained in:
Noah Swerhun 2024-03-04 17:06:44 -06:00
parent 8199e20885
commit 4a69bda90d
2 changed files with 195 additions and 108 deletions

View file

@ -1,7 +1,11 @@
use clap::Parser; use clap::Parser;
use core::panic;
use std::fs::{read, write}; use std::fs::{read, write};
use toml::{Table, Value}; use toml::{Table, Value};
mod target_config;
use target_config::*;
#[derive(Parser)] #[derive(Parser)]
struct Args { struct Args {
#[arg(short, long, default_value = "ngen.toml")] #[arg(short, long, default_value = "ngen.toml")]
@ -11,74 +15,16 @@ struct Args {
output_file: String, output_file: String,
} }
fn print_tabs(num: usize) { fn target_generator(config: &TargetConfig) -> String {
for _ in 0..num {
print!(" ");
}
}
fn print_table(table: &Table, depth: usize) {
for k in table.keys() {
match table.get(k) {
Some(v) => match v {
Value::Table(t) => {
print_tabs(depth);
println!("{k}:");
print_table(&t, depth + 1)
}
Value::String(s) => {
print_tabs(depth);
println!("{k}: {s}");
}
Value::Array(a) => {
print_tabs(depth);
println!("(array){k}:");
for element in a {
match element {
Value::String(s) => println!(" {}", s),
_ => todo!(),
}
}
}
_ => continue,
},
None => continue,
}
}
}
fn target_generator(name: &str, config_table: &Table) -> String {
let mut ret = String::from("\n"); let mut ret = String::from("\n");
let name = &config.name;
let clang = Value::String(String::from("clang")); let outfile = &config.outfile;
let blank = Value::String(String::from("")); let compiler = &config.compiler;
let aout = Value::String(String::from("a.out")); let compiler_flags = &config.compiler_flags.join(" ");
let linker = &config.linker;
let compiler = match config_table.get("compiler").unwrap_or(&clang) { let linker_flags = config.linker_flags.join(" ");
Value::String(s) => s, let linker_libs = config.linker_libs.join(" ");
_ => panic!("fatal: {name}.compiler: invalid type (must be string)"), let sources = &config.sources;
};
let compiler_flags = match config_table.get("compiler_flags").unwrap_or(&blank) {
Value::String(s) => s,
_ => panic!("fatal: {name}.compiler_flags: invalid type (must be string)"),
};
let linker_flags = match config_table.get("linker_flags").unwrap_or(&blank) {
Value::String(s) => s,
_ => panic!("fatal: {name}.linker_flags: invalid type (must be string)"),
};
let linker_libs = match config_table.get("linker_libs").unwrap_or(&blank) {
Value::String(s) => s,
_ => panic!("fatal: {name}.linker_libs: invalid type (must be string)"),
};
let outfile = match config_table.get("outfile").unwrap_or(&aout) {
Value::String(s) => s,
_ => panic!("fatal: {name}.outfile: invalid type (must be string)"),
};
let compiler_linker = Value::String(compiler.to_string());
let linker = match config_table.get("linker").unwrap_or(&compiler_linker) {
Value::String(s) => s,
_ => panic!("fatal: {name}.linker: invalid type (must be string)"),
};
ret.push_str(&format!( ret.push_str(&format!(
"\ "\
@ -97,39 +43,27 @@ build $builddir/{name}/dep: mkdir
" "
)); ));
let sources = config_table
.get("sources")
.unwrap_or_else(|| panic!("fatal: must provide sources"));
let mut object_list: Vec<String> = Vec::new(); let mut object_list: Vec<String> = Vec::new();
match sources { for s in sources {
Value::Array(a) => { let new_file = s.replace("/", "-");
for elem in a { let obj_name = format!("$builddir/{name}/obj/{new_file}.o");
match elem { let dep_name = format!("$builddir/{name}/dep/{new_file}.o.d");
Value::String(s) => { ret.push_str(&format!(
let new_file = s.replace("/", "-"); "build {obj_name}: cc_{name} {s}\n dep = {dep_name}\n"
let obj_name = format!("$builddir/{name}/obj/{new_file}.o"); ));
let dep_name = format!("$builddir/{name}/dep/{new_file}.o.d"); object_list.push(obj_name);
ret.push_str(&format!( }
"build {obj_name}: cc_{name} {s}\n dep = {dep_name}\n"
));
object_list.push(obj_name);
}
_ => panic!(
"fatal: element in {name}.sources: invalid source type (must be a string)"
),
}
}
}
_ => panic!("fatal: {name}.sources: invalid sources type (must be a list of strings)"),
};
ret.push_str(&format!("\nbuild $builddir/{name}/{outfile}: link_{name} ")); ret.push_str(&format!("\nbuild $builddir/{name}/{outfile}: link_{name} "));
ret.push_str(&object_list.join(" ")); ret.push_str(&object_list.join(" "));
ret.push_str(&format!( ret.push_str(&format!(
" || $builddir/{name}/obj $builddir/{name}/dep\nbuild {name}: phony $builddir/{name}/{outfile}\n" " || $builddir/{name}/obj $builddir/{name}/dep\nbuild {name}: phony $builddir/{name}/{outfile}\n"
)); ));
if config.is_default {
ret.push_str(&format!("\ndefault {name}\n"));
}
ret.push_str(&format!("# END TARGET {name}\n")); ret.push_str(&format!("# END TARGET {name}\n"));
ret ret
@ -140,12 +74,20 @@ fn main() {
let output_file = &args.output_file; let output_file = &args.output_file;
let config_file_name = &args.config_file; let config_file_name = &args.config_file;
let conf_file_bytes = let config_file_raw =
read(config_file_name).unwrap_or_else(|e| panic!("fatal: could not read file: {e}")); read(config_file_name).unwrap_or_else(|e| panic!("fatal: could not read file: {e}"));
let conf_file = String::from_utf8_lossy(&conf_file_bytes); let config_file_string = String::from_utf8_lossy(&config_file_raw);
let conf = toml::from_str::<Table>(&conf_file) let parsed_config = toml::from_str::<Table>(&config_file_string)
.unwrap_or_else(|e| panic!("fatal: could not parse config file: {e}")); .unwrap_or_else(|e| panic!("fatal: could not parse config file: {e}"));
let mut targets: Vec<TargetConfig> = Vec::new();
targets.push(TargetConfig::new(&parsed_config, "noname", None));
for k in parsed_config.keys() {
if let Value::Table(t) = parsed_config.get(k).unwrap() {
targets.push(TargetConfig::new(&t, k, Some(&targets[0])));
}
}
let mut ninjafile = String::from( let mut ninjafile = String::from(
"\ "\
# Generated by ngen. Do not modify by hand. # Generated by ngen. Do not modify by hand.
@ -168,20 +110,8 @@ rule regen
"build {output_file}: regen {config_file_name}\n pool = console\n" "build {output_file}: regen {config_file_name}\n pool = console\n"
)); ));
for k in conf.keys() { for t in targets {
match conf.get(k) { ninjafile.push_str(&target_generator(&t));
Some(v) => match v {
Value::Table(t) => {
//print_table(&t, 0);
ninjafile.push_str(&target_generator(&k, &t));
}
Value::String(s) => {
println!("{k}: {s}");
}
_ => continue,
},
None => continue,
}
} }
write(output_file, ninjafile) write(output_file, ninjafile)

157
src/target_config.rs Normal file
View file

@ -0,0 +1,157 @@
use toml::{Table, Value};
#[derive(Debug, Clone)]
pub struct TargetConfig {
pub name: String,
pub outfile: String,
pub compiler: String,
pub compiler_flags: Vec<String>,
pub linker: String,
pub linker_flags: Vec<String>,
pub linker_libs: Vec<String>,
pub sources: Vec<String>,
pub sources_cmd: String,
pub is_default: bool,
}
impl TargetConfig {
fn get_default() -> Self {
TargetConfig {
name: String::from("noname"),
outfile: String::from("a.out"),
compiler: String::from("cc"),
compiler_flags: Vec::new(),
linker: String::from(""),
linker_flags: Vec::new(),
linker_libs: Vec::new(),
sources: Vec::new(),
sources_cmd: String::from(""),
is_default: false,
}
}
pub fn new(table: &Table, name: &str, global_conf: Option<&Self>) -> Self {
let mut ret: Self;
let mut linker_set = false;
if let Some(global) = global_conf {
ret = global.clone();
} else {
ret = Self::get_default();
}
ret.name = String::from(name);
ret.is_default = false;
for k in table.keys() {
match &*k.to_owned() {
"outfile" => {
if let Value::String(s) = table.get(k).unwrap() {
ret.outfile = s.to_owned();
} else {
panic!("fatal: {k} invalid type, must be a string.");
}
}
"compiler" => {
if let Value::String(s) = table.get(k).unwrap() {
ret.compiler = s.to_owned();
} else {
panic!("fatal: {k} invalid type, must be a string.");
}
}
"compiler_flags" => {
if let Value::Array(a) = table.get(k).unwrap() {
for element in a {
if let Value::String(s) = element {
ret.compiler_flags.push(s.to_owned());
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
"linker" => {
if let Value::String(s) = table.get(k).unwrap() {
ret.linker = s.to_owned();
linker_set = true;
} else {
panic!("fatal: {k} invalid type, must be a string.");
}
}
"linker_flags" => {
if let Value::Array(a) = table.get(k).unwrap() {
for element in a {
if let Value::String(s) = element {
ret.linker_flags.push(s.to_owned());
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
"linker_libs" => {
if let Value::Array(a) = table.get(k).unwrap() {
for element in a {
if let Value::String(s) = element {
ret.linker_libs.push(s.to_owned());
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
"sources" => {
if let Value::Array(a) = table.get(k).unwrap() {
for element in a {
if let Value::String(s) = element {
ret.sources.push(s.to_owned());
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("fatal: {k} invalid type, must be a array of strings.");
}
}
"sources_cmd" => {
if let Value::String(s) = table.get(k).unwrap() {
ret.sources_cmd = s.to_owned();
} else {
panic!("fatal: {k} invalid type, must be a string.");
}
}
"default" => {
if let Value::Boolean(b) = table.get(k).unwrap() {
ret.is_default = *b;
} else {
panic!("fatal: {k} invalid type, must be a boolean.");
}
}
_ => {
if let Value::Table(_) = table.get(k).unwrap() {
} else {
panic!("fatal: unrecognized key {k}.");
}
}
}
}
if !linker_set {
ret.linker = ret.compiler.clone();
}
if (ret.sources == Vec::<String>::new()) && (ret.sources_cmd == String::from("")) {
panic!("fatal: must specify EITHER sources OR sources_cmd, not both.");
}
if !(ret.sources == Vec::<String>::new()) && !(ret.sources_cmd == String::from("")) {
panic!("fatal: must specify EITHER sources OR sources_cmd");
}
ret
}
}