groundwork laid (not functional yet)

This commit is contained in:
Noah Swerhun 2024-03-10 23:02:48 -05:00
parent abec4c2ba0
commit 001a9735ee
6 changed files with 370 additions and 267 deletions

View file

@ -1,47 +0,0 @@
use toml::{Table, Value};
#[derive(Debug, Clone)]
pub struct Config {
pub build_dir: String,
pub compile_commands: bool,
pub compile_commands_target: String,
}
impl Config {
pub fn get_default() -> Self {
Config {
build_dir: String::from("build"),
compile_commands: false,
compile_commands_target: String::from(""),
}
}
pub fn set(&mut self, table: &Table) {
for k in table.keys() {
match &*k.to_owned() {
"build_dir" => {
if let Value::String(s) = table.get(k).unwrap() {
self.build_dir = s.to_owned();
} else {
panic!("error parsing config table: {k} invalid type, must be a string.");
}
}
"compile_commands" => {
if let Value::Boolean(b) = table.get(k).unwrap() {
//eprintln!("warn: config.compile_commands has no functionality (yet)");
self.compile_commands = *b;
} else {
panic!("error parsing config table: {k} invalid type, must be a boolean.");
}
}
"compile_commands_target" => {
if let Value::String(s) = table.get(k).unwrap() {
self.compile_commands_target = s.to_owned();
} else {
panic!("error parsing config table: {k} invalid type, must be a string.");
}
}
_ => panic!("error parsing config table: unrecognized key {k}."),
}
}
}
}

106
src/config_file_parsing.rs Normal file
View file

@ -0,0 +1,106 @@
use std::fmt::Display;
use toml::{Table, Value};
#[derive(Debug)]
pub struct ConfigFileError<'a> {
pub key_path: String,
pub message: &'a str,
}
impl Display for ConfigFileError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"config file error: key {}: {}.",
self.key_path, self.message
)
}
}
pub fn get_key_as_str<'a>(
table: &'a Table,
key: &str,
key_path: String,
) -> Result<Option<&'a str>, ConfigFileError<'a>> {
if let Some(v) = table.get(key) {
if let Value::String(s) = v {
Ok(Some(s))
} else {
Err(ConfigFileError {
key_path,
message: "invalid type, should be string",
})
}
} else {
Ok(None)
}
}
pub fn get_key_as_table<'a>(
table: &'a Table,
key: &str,
key_path: String,
) -> Result<Option<&'a Table>, ConfigFileError<'a>> {
if let Some(v) = table.get(key) {
if let Value::Table(s) = v {
Ok(Some(s))
} else {
Err(ConfigFileError {
key_path,
message: "invalid type, should be a table of key-value pairs",
})
}
} else {
Ok(None)
}
}
pub fn get_key_as_bool<'a>(
table: &'a Table,
key: &str,
key_path: String,
) -> Result<Option<bool>, ConfigFileError<'a>> {
if let Some(v) = table.get(key) {
if let Value::Boolean(s) = v {
Ok(Some(*s))
} else {
Err(ConfigFileError {
key_path,
message: "invalid type, should be boolean",
})
}
} else {
Ok(None)
}
}
pub fn get_key_as_vec_str<'a>(
table: &'a Table,
key: &str,
key_path: String,
) -> Result<Option<Vec<&'a str>>, ConfigFileError<'a>> {
if let Some(v) = table.get(key) {
if let Value::Array(a) = v {
let mut ret: Vec<&'a str> = vec![];
for elem in a {
if let Value::String(s) = elem {
ret.push(&s);
} else {
return Err(ConfigFileError {
key_path: format!("{key_path} elemement '{elem}'"),
message: "invalid type, should be string",
});
}
}
Ok(Some(ret))
} else {
Err(ConfigFileError {
key_path,
message: "invalid type, should be array of strings",
})
}
} else {
Ok(None)
}
}

45
src/config_table.rs Normal file
View file

@ -0,0 +1,45 @@
use toml::Table;
use crate::config_file_parsing::*;
#[derive(Debug, Clone)]
pub struct ConfigTable<'a> {
pub build_dir: &'a str,
pub compile_commands: bool,
pub compile_commands_target: &'a str,
}
impl<'a> ConfigTable<'a> {
pub fn get_default() -> Self {
ConfigTable {
build_dir: "build",
compile_commands: false,
compile_commands_target: "main",
}
}
pub fn from_table(table: &'a Table, key_path: String) -> Result<Self, ConfigFileError<'a>> {
let mut ret = Self::get_default();
if let Some(v) = get_key_as_str(table, "build_dir", format!("{key_path}.build_dir"))? {
ret.build_dir = v;
}
if let Some(v) = get_key_as_bool(
table,
"compile_commands",
format!("{key_path}.compile_commands"),
)? {
ret.compile_commands = v;
}
if let Some(v) = get_key_as_str(
table,
"compile_commands_target",
format!("{key_path}.compile_commands_target"),
)? {
ret.compile_commands_target = v;
}
Ok(ret)
}
}

View file

@ -1,4 +1,6 @@
use clap::Parser; use clap::Parser;
use config_file_parsing::get_key_as_table;
use core::{panic, str};
use std::{ use std::{
fs::{read_to_string, write}, fs::{read_to_string, write},
path::Path, path::Path,
@ -9,8 +11,11 @@ use toml::{Table, Value};
mod target; mod target;
use target::*; use target::*;
mod config; mod config_table;
use config::*; use config_table::*;
mod config_file_parsing;
mod target_options;
/// ngen is a build file generator for the ninja build system. /// ngen is a build file generator for the ninja build system.
#[derive(Parser)] #[derive(Parser)]
@ -125,109 +130,128 @@ fn main() {
let config_file = read_to_string(config_file_name) let config_file = read_to_string(config_file_name)
.unwrap_or_else(|e| panic!("could not read config file {config_file_name}: {e}")); .unwrap_or_else(|e| panic!("could not read config file {config_file_name}: {e}"));
let parsed_config = toml::from_str::<Table>(&config_file) let toml_config = toml::from_str::<Table>(&config_file)
.unwrap_or_else(|e| panic!("error parsing toml file {config_file_name}: {e}")); .unwrap_or_else(|e| panic!("error parsing toml file {config_file_name}: {e}"));
// get config // get config
let mut config = Config::get_default(); let config_tbl: ConfigTable;
if let Some(v) = parsed_config.get("config") { match get_key_as_table(&toml_config, "config", "".to_string()) {
if let Value::Table(t) = v { Ok(v) => match v {
config.set(&t); Some(c) => {
} else { config_tbl = ConfigTable::from_table(&c, ".config".to_string())
panic!("error parsing config table: config invalid type, must be a table."); .unwrap_or_else(|e| panic!("{e}"))
} }
None => config_tbl = ConfigTable::get_default(),
},
Err(e) => panic!("{e}"),
} }
println!("{:?}", config_tbl);
//let mut config = Config::get_default();
//if let Some(v) = parsed_config.get("config") {
// if let Value::Table(t) = v {
// config.set(&t);
// } else {
// panic!("error parsing config table: config invalid type, must be a table.");
// }
//}
//---- //----
// parse the main target first // parse the main target first
let mut targets: Vec<Target> = Vec::new(); //let mut targets: Vec<Target> = vec![];
let main_target = Target::new(&parsed_config, "main", None);
targets.push(main_target.clone());
let mut target_for_comp_cmds = main_target;
let mut target_for_comp_cmds_changed = false;
// parse other targets match Target::from_table(&toml_config, "main", "".to_string()) {
for k in parsed_config.keys() { Ok(v) => println!("{:?}", v),
if let Value::Table(t) = parsed_config.get(k).unwrap() { Err(e) => panic!("{e}"),
if k == "config" {
continue; // ignore config
}
let target = Target::new(&t, k, Some(&targets[0]));
targets.push(target);
if let Some(_) = args.write_compile_commands {
if *k == config.compile_commands_target {
target_for_comp_cmds = targets[targets.len() - 1].clone();
target_for_comp_cmds_changed = true;
}
}
}
} }
if let Some(file) = args.write_compile_commands { exit(10);
if config.compile_commands_target == "" {
eprintln!(
"ngen: warn: no compile_commands target found, using 'main' target by default."
);
} else if !target_for_comp_cmds_changed && config.compile_commands_target != "main" {
let c = &config.compile_commands_target;
eprintln!(
"ngen: warn: compile_commands target {c} not found, using 'main' target instead."
);
}
write(
&file,
&gen_compile_commands(&target_for_comp_cmds, &config.build_dir),
)
.unwrap_or_else(|e| panic!("fatal: could not write {file}: {e}"));
// no need to write ninja file
exit(0);
}
let build_dir = &config.build_dir; // targets.push(main_target.clone());
let mut ninjafile = format!( // let mut target_for_comp_cmds = main_target;
"\ // let mut target_for_comp_cmds_changed = false;
# Generated by ngen. Do not modify by hand. //
// // parse other targets
builddir = {build_dir} // for k in parsed_config.keys() {
// if let Value::Table(t) = parsed_config.get(k).unwrap() {
rule mkdir // if k == "config" {
command = mkdir -p $out // continue; // ignore config
description = Creating directory $out // }
// let target = Target::new(&t, k, Some(&targets[0]));
rule regen_ninjafile // targets.push(target);
command = ngen -c $in -o $out // if let Some(_) = args.write_compile_commands {
generator = 1 // if *k == config.compile_commands_target {
description = Regenerating $out // target_for_comp_cmds = targets[targets.len() - 1].clone();
// target_for_comp_cmds_changed = true;
", // }
); // }
// }
if config.compile_commands { // }
ninjafile.push_str(&format!( //
"\ // if let Some(file) = args.write_compile_commands {
rule regen_compile_commands // if config.compile_commands_target == "" {
command = ngen -c $in --write-compile-commands $out // eprintln!(
description = Regenerating $out // "ngen: warn: no compile_commands target found, using 'main' target by default."
// );
build $builddir: mkdir // } else if !target_for_comp_cmds_changed && config.compile_commands_target != "main" {
// let c = &config.compile_commands_target;
build $builddir/compile_commands.json: regen_compile_commands {config_file_name} || $builddir // eprintln!(
pool = console // "ngen: warn: compile_commands target {c} not found, using 'main' target instead."
// );
build {output_file}: regen_ninjafile {config_file_name} || $builddir/compile_commands.json // }
pool = console // write(
" // &file,
)); // &gen_compile_commands(&target_for_comp_cmds, &config.build_dir),
} else { // )
ninjafile.push_str(&format!( // .unwrap_or_else(|e| panic!("fatal: could not write {file}: {e}"));
"build {output_file}: regen_ninjafile {config_file_name}\n pool = console\n" // // no need to write ninja file
)); // exit(0);
} // }
//
for t in targets { // let build_dir = &config.build_dir;
ninjafile.push_str(&gen_ninja_code(&t)); // let mut ninjafile = format!(
} // "\
//# Generated by ngen. Do not modify by hand.
write(output_file, &ninjafile) //
.unwrap_or_else(|e| panic!("could not write ninja file {ninjafile}: {e}")); //builddir = {build_dir}
//
//rule mkdir
// command = mkdir -p $out
// description = Creating directory $out
//
//rule regen_ninjafile
// command = ngen -c $in -o $out
// generator = 1
// description = Regenerating $out
//
//",
// );
//
// if config.compile_commands {
// ninjafile.push_str(&format!(
// "\
//rule regen_compile_commands
// command = ngen -c $in --write-compile-commands $out
// description = Regenerating $out
//
//build $builddir: mkdir
//
//build $builddir/compile_commands.json: regen_compile_commands {config_file_name} || $builddir
// pool = console
//
//build {output_file}: regen_ninjafile {config_file_name} || $builddir/compile_commands.json
// pool = console
//"
// ));
// } else {
// ninjafile.push_str(&format!(
// "build {output_file}: regen_ninjafile {config_file_name}\n pool = console\n"
// ));
// }
//
// for t in targets {
// ninjafile.push_str(&gen_ninja_code(&t));
// }
//
// write(output_file, &ninjafile)
// .unwrap_or_else(|e| panic!("could not write ninja file {ninjafile}: {e}"));
} }

View file

@ -1,145 +1,90 @@
use toml::{Table, Value}; use toml::Table;
use crate::config_file_parsing::*;
use crate::target_options::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Target { pub struct Target<'a> {
pub name: String, pub name: &'a str,
pub outfile: String, pub outfile: &'a str,
pub compiler: String, pub compiler: &'a str,
pub compiler_flags: Vec<String>, pub compiler_flags: Vec<&'a str>,
pub linker: String, pub linker: &'a str,
pub linker_flags: Vec<String>, pub linker_flags: Vec<&'a str>,
pub linker_libs: Vec<String>, pub linker_libs: Vec<&'a str>,
pub sources: Vec<String>, pub sources: Vec<&'a str>,
pub is_default: bool, pub is_default: bool,
pub opts: TargetOptions<'a>,
} }
impl Target { impl<'a> Target<'a> {
fn get_default() -> Self { pub fn get_default() -> Self {
Target { Target {
name: String::from(""), name: "",
outfile: String::from("a.out"), outfile: "a.out",
compiler: String::from("cc"), compiler: "cc",
compiler_flags: Vec::new(), compiler_flags: vec![""],
linker: String::from(""), linker: "cc",
linker_flags: Vec::new(), linker_flags: vec![""],
linker_libs: Vec::new(), linker_libs: vec![""],
sources: Vec::new(), sources: vec![""],
is_default: false, is_default: false,
opts: TargetOptions::get_default(),
} }
} }
pub fn new(table: &Table, name: &str, inherit_from: Option<&Self>) -> Self { pub fn from_table(
let mut ret: Self; table: &'a Table,
let mut linker_set = false; name: &'a str,
key_path: String,
) -> Result<Self, ConfigFileError<'a>> {
let mut ret = Self::get_default();
if let Some(t) = inherit_from { if let Some(v) = get_key_as_table(table, "opts", format!("{key_path}.opts"))? {
ret = t.clone(); ret.opts = TargetOptions::from_table(&v, format!("{key_path}.opts"))?;
} else {
ret = Self::get_default();
} }
ret.name = String::from(name); ret.name = name;
ret.is_default = false;
for k in table.keys() { if let Some(v) = get_key_as_str(table, "outfile", format!("{key_path}.outfile"))? {
match &*k.to_owned() { ret.outfile = v;
"outfile" => {
if let Value::String(s) = table.get(k).unwrap() {
ret.outfile = s.to_owned();
} else {
panic!("error parsing target {name}: {k} invalid type, must be a string.");
}
}
"compiler" => {
if let Value::String(s) = table.get(k).unwrap() {
ret.compiler = s.to_owned();
} else {
panic!("error parsing target {name}: {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!("error parsing target {name}: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("error parsing target {name}: {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!("error parsing target {name}: {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!("error parsing target {name}: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("error parsing target {name}: {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!("error parsing target {name}: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("error parsing target {name}: {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!("error parsing target {name}: {k} invalid type, must be a array of strings.");
}
}
} else {
panic!("error parsing target {name}: {k} invalid type, must be a array of strings.");
}
}
"default" => {
if let Value::Boolean(b) = table.get(k).unwrap() {
ret.is_default = *b;
} else {
panic!("error parsing target {name}: {k} invalid type, must be a boolean.");
}
}
_ => {
if let Value::Table(_) = table.get(k).unwrap() {
} else {
panic!("error parsing target {name}: unrecognized key {k}.");
}
}
}
} }
if !linker_set { if let Some(v) = get_key_as_str(table, "compiler", format!("{key_path}.compiler"))? {
ret.linker = ret.compiler.clone(); ret.compiler = v;
} }
if ret.sources == Vec::<String>::new() { if let Some(v) = get_key_as_vec_str(
panic!("error parsing target {name}: you MUST specify at least one source"); table,
"compiler_flags",
format!("{key_path}.compiler_flags"),
)? {
ret.compiler_flags = v;
} }
ret if let Some(v) = get_key_as_str(table, "linker", format!("{key_path}.linker"))? {
ret.linker = v;
}
if let Some(v) =
get_key_as_vec_str(table, "linker_flags", format!("{key_path}.linker_flags"))?
{
ret.linker_flags = v;
}
if let Some(v) =
get_key_as_vec_str(table, "linker_libs", format!("{key_path}.linker_libs"))?
{
ret.linker_libs = v;
}
if let Some(v) = get_key_as_vec_str(table, "sources", format!("{key_path}.sources"))? {
ret.sources = v;
}
if let Some(v) = get_key_as_bool(table, "is_default", format!("{key_path}.is_default"))? {
ret.is_default = v;
}
Ok(ret)
} }
} }

30
src/target_options.rs Normal file
View file

@ -0,0 +1,30 @@
use toml::{Table, Value};
use crate::config_file_parsing::*;
#[derive(Debug, Clone)]
pub struct TargetOptions<'a> {
pub inherit: bool,
pub inherit_from: &'a str,
}
impl<'a> TargetOptions<'a> {
pub fn get_default() -> Self {
TargetOptions {
inherit: true,
inherit_from: "main",
}
}
pub fn from_table(table: &'a Table, key_path: String) -> Result<Self, ConfigFileError<'a>> {
let mut ret = Self::get_default();
if let Some(v) = get_key_as_bool(table, "inherit", format!("{key_path}.inherit"))? {
ret.inherit = v;
}
if let Some(v) = get_key_as_str(table, "inherit_from", format!("{key_path}.inherit_from"))?
{
ret.inherit_from = v;
}
Ok(ret)
}
}