diff --git a/src/file_writing/compile_commands.rs b/src/content_generation/compile_commands.rs similarity index 65% rename from src/file_writing/compile_commands.rs rename to src/content_generation/compile_commands.rs index 3c5e979..65b3383 100644 --- a/src/file_writing/compile_commands.rs +++ b/src/content_generation/compile_commands.rs @@ -1,14 +1,18 @@ -use std::path::Path; +use std::{ + fs::OpenOptions, + io::{BufWriter, Write}, + path::Path, +}; -use crate::parsed_config_processing::*; +use crate::parse_deser::*; use super::*; -pub fn gen_compile_commands( +pub fn create_compile_commands_content( name: &str, target: &Target, - config: &ConfigTable, -) -> Result { + config: &Config, +) -> Result { let mut ret = "[\n".to_string(); let compiler_flags = target.compiler_flags.join(" "); @@ -18,7 +22,8 @@ pub fn gen_compile_commands( let pwd = pwd.to_str().unwrap(); if target.sources.len() == 0 { - return Err(ContentGenerationError { + return Err(Error { + kind: ErrorKind::NoSources, message: format!( "compile_commands_target is set to `{0}`, however `{0}` has no sources.", name @@ -48,3 +53,15 @@ pub fn gen_compile_commands( Ok(ret) } + +pub fn write_compile_commands(filename: &str, content: &[u8]) -> std::io::Result<()> { + let file = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(filename)?; + + let mut writer = BufWriter::new(file); + writer.write(content)?; + Ok(()) +} diff --git a/src/file_writing/content_generation_error.rs b/src/content_generation/error.rs similarity index 55% rename from src/file_writing/content_generation_error.rs rename to src/content_generation/error.rs index 1e55f34..fd497a8 100644 --- a/src/file_writing/content_generation_error.rs +++ b/src/content_generation/error.rs @@ -1,11 +1,18 @@ use std::fmt::Display; -pub struct ContentGenerationError { +pub enum ErrorKind { + NoSources, +} + +pub struct Error { + pub kind: ErrorKind, pub message: String, } -impl Display for ContentGenerationError { +impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "content generation error: {}", self.message) } } + +pub type Result = std::result::Result; diff --git a/src/file_writing/mod.rs b/src/content_generation/mod.rs similarity index 58% rename from src/file_writing/mod.rs rename to src/content_generation/mod.rs index f306caa..251573a 100644 --- a/src/file_writing/mod.rs +++ b/src/content_generation/mod.rs @@ -1,7 +1,7 @@ pub mod compile_commands; -pub mod content_generation_error; +pub mod error; pub mod ninja_file; pub use compile_commands::*; -pub use content_generation_error::*; +pub use error::*; pub use ninja_file::*; diff --git a/src/file_writing/ninja_file.rs b/src/content_generation/ninja_file.rs similarity index 65% rename from src/file_writing/ninja_file.rs rename to src/content_generation/ninja_file.rs index 984958b..60d8a08 100644 --- a/src/file_writing/ninja_file.rs +++ b/src/content_generation/ninja_file.rs @@ -1,8 +1,12 @@ +use std::fs::OpenOptions; +use std::io::BufWriter; +use std::io::Write; + use super::*; -use crate::parsed_config_processing::*; +use crate::parse_deser::*; use crate::Args; -pub fn gen_ninja_header(config: &ConfigTable, cmdline_args: &Args) -> String { +pub fn gen_ninja_header(config: &Config, cmdline_args: &Args) -> Result { let mut ret = "".to_string(); ret.push_str(&format!( "## Generated by ngen @@ -45,12 +49,13 @@ build {0}: regen_ninjafile {1} || $builddir/compile_commands.json cmdline_args.output_file, cmdline_args.config_file )); } - ret + Ok(ret) } -pub fn gen_ninja_target(name: &str, target: &Target) -> Result { +pub fn gen_ninja_target(name: &str, target: &Target) -> Result { if target.sources.len() == 0 { - return Err(ContentGenerationError { + return Err(Error { + kind: ErrorKind::NoSources, message: format!("target `{}` has no sources", name), }); } @@ -97,9 +102,10 @@ build $builddir/{0}/dep: mkdir name, target.outfile )); ret.push_str(&object_list.join(" ")); + let deps = &target.opts.depend_on.join(" "); ret.push_str(&format!( - " || $builddir/{0}/obj $builddir/{0}/dep\nbuild {0}: phony $builddir/{0}/{1}\n", - name, target.outfile + " | {2} || $builddir/{0}/obj $builddir/{0}/dep\nbuild {0}: phony $builddir/{0}/{1}\n", + name, target.outfile, deps )); if target.opts.default { ret.push_str(&format!("\ndefault {}\n", name)); @@ -107,3 +113,39 @@ build $builddir/{0}/dep: mkdir ret.push_str(&format!("# END TARGET {}\n", name)); Ok(ret) } + +pub fn create_ninja_file_content( + target_list: &TargetList, + config: &Config, + args: &Args, +) -> Result { + let mut ret = String::new(); + ret.push_str(&gen_ninja_header(&config, &args)?); + for (name, target) in &target_list.0 { + let content = gen_ninja_target(name, target); + match content { + Ok(v) => ret.push_str(&v), + Err(e) => match e.kind { + ErrorKind::NoSources => eprintln!("WARNING: {e}. Skipping `{}`", name), + #[allow(unreachable_patterns)] + _ => { + return Err(e); + } + }, + } + } + + Ok(ret) +} + +pub fn write_ninja_file(filename: &str, content: &[u8]) -> std::io::Result<()> { + let file = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(filename)?; + + let mut writer = BufWriter::new(file); + writer.write(content)?; + Ok(()) +} diff --git a/src/ngen_toml.rs b/src/deser.rs similarity index 71% rename from src/ngen_toml.rs rename to src/deser.rs index f608c73..13fe916 100644 --- a/src/ngen_toml.rs +++ b/src/deser.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] -pub struct ParsedTarget { +pub struct DeserTarget { pub outfile: Option, pub compiler: Option, pub compiler_flags: Option>, @@ -11,28 +11,31 @@ pub struct ParsedTarget { pub linker_flags: Option>, pub linker_libs: Option>, pub sources: Option>, - pub opts: Option, + pub opts: Option, } #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] -pub struct ParsedTargetOptions { +pub struct DeserTargetOptions { pub inherit: Option, pub inherit_from: Option, + pub depend_on: Option>, pub default: Option, } #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] -pub struct ParsedConfigTable { +pub struct DeserConfig { pub build_dir: Option, pub compile_commands: Option, pub compile_commands_target: Option, } +pub type DeserTargetList = HashMap; + #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] -pub struct NgenToml { - pub config: Option, - pub targets: Option>, +pub struct DeserNgenToml { + pub config: Option, + pub targets: Option, } diff --git a/src/main.rs b/src/main.rs index f199109..adc00e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,16 @@ -use clap::Parser; use core::panic; -use std::collections::HashMap; -use std::fs::{read_to_string, OpenOptions}; -use std::io::{BufWriter, Write}; -use std::process::exit; +use std::{collections::HashMap, env::args, fs::read_to_string, process::exit}; -mod ngen_toml; -use ngen_toml::*; +use clap::Parser; -mod parsed_config_processing; -use parsed_config_processing::*; +mod content_generation; +mod deser; +mod parse_deser; +mod validation; -mod file_writing; -use file_writing::*; +use content_generation::*; +use deser::*; +use parse_deser::*; /// ngen is a build file generator for the ninja build system. #[derive(Parser)] @@ -32,118 +30,72 @@ struct Args { fn main() { let args = Args::parse(); - let config_file = read_to_string(&args.config_file) + let ngen_toml = read_to_string(&args.config_file) .unwrap_or_else(|e| panic!("error reading config file `{}`: {e}", args.config_file)); - let toml_config: NgenToml = toml::from_str(&config_file) + let deser_ngen_toml: DeserNgenToml = toml::from_str(&ngen_toml) .unwrap_or_else(|e| panic!("error parsing config file `{}`: {e}", args.config_file)); - let config_table: ConfigTable = match &toml_config.config { - Some(v) => ConfigTable::from_parsed_config(&v).unwrap_or_else(|e| panic!("{e}")), - None => ConfigTable::get_default(), + let target_option_list: TargetOptionList; + let deser_target_list: &DeserTargetList; + match &deser_ngen_toml.targets { + Some(v) => { + target_option_list = TargetOptionList::from(v); + deser_target_list = &v; + } + None => panic!("no targets provided, nothing to do. Exiting"), }; - let all_parsed_targets: &HashMap = match &toml_config.targets { - Some(v) => v, - None => panic!( - "{}", - ConfigurationError { - message: "no targets found, nothing to do. Exiting".to_string() + target_option_list + .check_for_dependency_cycle() + .unwrap_or_else(|e| panic!("{e}. cannont continue. Exiting")); + + let inheritance_order = target_option_list + .get_inheritance_order() + .unwrap_or_else(|e| panic!("{e}. cannot continue. Exiting")); + + let mut target_list: TargetList = TargetList(HashMap::new()); + for order in &inheritance_order { + for name in order.into_iter().rev() { + let opts = target_option_list.0.get(name as &str).unwrap(); + let deser = deser_target_list.get(name as &str).unwrap(); + + let parent: Option<&Target>; + if opts.inherit && (name != opts.inherit_from) { + parent = Some(target_list.0.get(opts.inherit_from as &str).unwrap()); + } else { + parent = None; } - ), + + target_list.0.insert(name, Target::new(opts, deser, parent)); + } + } + + target_list + .check_if_any_sources_are_provided() + .unwrap_or_else(|e| panic!("{e}, nothing to do. Exiting")); + + let config = match &deser_ngen_toml.config { + Some(v) => Config::from(v), + None => Config::get_default(), }; - let mut sources_found = false; - for target in all_parsed_targets.values() { - if let Some(_) = target.sources { - sources_found = true; - break; - } - } - if !sources_found { - panic!( - "{}", - ConfigurationError { - message: "no targets have any sources, nothing to do. Exiting".to_string() - } - ); - } - - let mut targets: HashMap<&str, Target> = HashMap::new(); - for (name, parsed_target) in all_parsed_targets { - if let Some(_) = targets.get(name as &str) { - continue; - } - let mut inheritance_chain: HashMap<&str, &str> = HashMap::new(); - match Target::from_parsed_target( - parsed_target, - name, - all_parsed_targets, - &targets, - &mut inheritance_chain, - ) { - Ok(v) => { - targets.extend(v.into_iter()); - } - Err(e) => panic!("{e}"), - } - } + config + .check_compile_commands_target(&target_list) + .unwrap_or_else(|e| panic!("{e}")); if let Some(filename) = &args.write_compile_commands { - let target = match targets.get(config_table.compile_commands_target) { - Some(v) => v, - None => panic!( - "{}", - ContentGenerationError { - message: format!( - "compile_commands_target is set to `{0}`, however target `{0}` does not exist.", - config_table.compile_commands_target - ) - } - ), - }; - let compile_commands = match gen_compile_commands( - config_table.compile_commands_target, - &target, - &config_table, - ) { - Ok(v) => v, - Err(e) => panic!("{e}"), - }; - let mut comp_cmds_file = OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(&filename) - .unwrap_or_else(|e| panic!("error opening compile commands file `{}`: {e}", filename)); - comp_cmds_file - .write_all(&compile_commands.as_bytes()) - .unwrap_or_else(|e| { - panic!("error writing to compile commands file `{}`: {e}", filename) - }); - - // no need to generate the ninja file + let name = config.compile_commands_target; + let target = target_list.0.get(name).unwrap(); + let content = create_compile_commands_content(name, target, &config) + .unwrap_or_else(|e| panic!("{e}")); + write_compile_commands(filename, content.as_bytes()).unwrap_or_else(|e| { + panic!("could not write compile commands file `{}`: {e}", filename) + }); exit(0); } - let ninjafile = OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(&args.output_file) - .unwrap_or_else(|e| panic!("error opening ninja file `{}`: {e}", args.output_file)); - - let mut ninjafile_writer = BufWriter::new(ninjafile); - ninjafile_writer - .write(&gen_ninja_header(&config_table, &args).as_bytes()) - .unwrap_or_else(|e| panic!("error writing to ninja file `{}`: {e}", args.output_file)); - for (name, target) in &targets { - match gen_ninja_target(name, target) { - Ok(v) => { - ninjafile_writer.write(&v.as_bytes()).unwrap_or_else(|e| { - panic!("error writing to ninja file `{}`: {e}", args.output_file) - }); - } - Err(e) => eprintln!("WARNING: {e}. `{}` will NOT be generated", name), - } - } + let ninja_file_content = + create_ninja_file_content(&target_list, &config, &args).unwrap_or_else(|e| panic!("{e}")); + write_ninja_file(&args.output_file, ninja_file_content.as_bytes()) + .unwrap_or_else(|e| panic!("could not write ninja file `{}`: {e}", args.output_file)); } diff --git a/src/parsed_config_processing/config_table.rs b/src/parse_deser/config.rs similarity index 52% rename from src/parsed_config_processing/config_table.rs rename to src/parse_deser/config.rs index a06856b..4e99d21 100644 --- a/src/parsed_config_processing/config_table.rs +++ b/src/parse_deser/config.rs @@ -1,39 +1,38 @@ -use super::*; -use crate::ngen_toml::*; +use crate::deser::*; #[derive(Debug, Clone)] -pub struct ConfigTable<'a> { +pub struct Config<'a> { pub build_dir: &'a str, pub compile_commands: bool, pub compile_commands_target: &'a str, } -impl<'a> ConfigTable<'a> { +impl<'a> From<&'a DeserConfig> for Config<'a> { + fn from(value: &'a DeserConfig) -> Self { + let mut ret = Self::get_default(); + + if let Some(v) = &value.build_dir { + ret.build_dir = v; + } + + if let Some(v) = &value.compile_commands { + ret.compile_commands = *v; + } + + if let Some(v) = &value.compile_commands_target { + ret.compile_commands_target = v; + } + + ret + } +} + +impl<'a> Config<'a> { pub fn get_default() -> Self { - ConfigTable { + Self { build_dir: "build", compile_commands: false, compile_commands_target: "main", } } - - pub fn from_parsed_config( - parsed_config_table: &'a ParsedConfigTable, - ) -> Result { - let mut ret = Self::get_default(); - - if let Some(v) = &parsed_config_table.build_dir { - ret.build_dir = v; - } - - if let Some(v) = &parsed_config_table.compile_commands { - ret.compile_commands = *v; - } - - if let Some(v) = &parsed_config_table.compile_commands_target { - ret.compile_commands_target = v; - } - - Ok(ret) - } } diff --git a/src/parse_deser/mod.rs b/src/parse_deser/mod.rs new file mode 100644 index 0000000..7815e9a --- /dev/null +++ b/src/parse_deser/mod.rs @@ -0,0 +1,11 @@ +pub mod config; +pub mod target; +pub mod target_list; +pub mod target_options; +pub mod target_options_list; + +pub use config::*; +pub use target::*; +pub use target_list::*; +pub use target_options::*; +pub use target_options_list::*; diff --git a/src/parse_deser/target.rs b/src/parse_deser/target.rs new file mode 100644 index 0000000..63a5c52 --- /dev/null +++ b/src/parse_deser/target.rs @@ -0,0 +1,99 @@ +use super::*; +use crate::deser::*; + +#[derive(Debug, Clone)] +pub struct Target<'a> { + pub outfile: &'a str, + pub compiler: &'a str, + pub compiler_flags: Vec<&'a str>, + pub linker: &'a str, + pub linker_flags: Vec<&'a str>, + pub linker_libs: Vec<&'a str>, + pub sources: Vec<&'a str>, + pub opts: TargetOptions<'a>, +} + +impl<'a> Target<'a> { + pub fn new( + opts: &TargetOptions<'a>, + deser: &'a DeserTarget, + parent: Option<&Target<'a>>, + ) -> Self { + let mut ret = Self::get_default(); + + ret.opts = opts.clone(); + + if let Some(parent) = parent { + if let None = deser.outfile { + ret.outfile = parent.outfile; + } + if let None = deser.compiler { + ret.compiler = parent.compiler; + } + if let None = deser.linker { + ret.linker = parent.linker; + } + for string in parent.compiler_flags.clone() { + ret.compiler_flags.push(string); + } + for string in parent.linker_flags.clone() { + ret.linker_flags.push(string); + } + for string in parent.linker_libs.clone() { + ret.linker_libs.push(string); + } + for string in parent.sources.clone() { + ret.sources.push(string); + } + } + + if let Some(v) = &deser.outfile { + ret.outfile = v; + } + if let Some(v) = &deser.compiler { + ret.compiler = v; + } + if let Some(v) = &deser.compiler_flags { + for string in v { + ret.compiler_flags.push(string); + } + } + if let Some(v) = &deser.linker { + ret.linker = v; + } else { + if let Some(v) = &deser.compiler { + ret.linker = v; + } + } + if let Some(v) = &deser.linker_flags { + for string in v { + ret.linker_flags.push(string); + } + } + if let Some(v) = &deser.linker_libs { + for string in v { + ret.linker_libs.push(string); + } + } + if let Some(v) = &deser.sources { + for string in v { + ret.sources.push(string); + } + } + + ret + } + + pub fn get_default() -> Self { + Target { + outfile: "a.out", + compiler: "cc", + compiler_flags: vec![], + linker: "cc", + linker_flags: vec![], + linker_libs: vec![], + sources: vec![], + opts: TargetOptions::get_default(), + } + } +} diff --git a/src/parse_deser/target_list.rs b/src/parse_deser/target_list.rs new file mode 100644 index 0000000..a0f319a --- /dev/null +++ b/src/parse_deser/target_list.rs @@ -0,0 +1,6 @@ +use super::*; +use crate::deser::*; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct TargetList<'a>(pub HashMap<&'a str, Target<'a>>); diff --git a/src/parse_deser/target_options.rs b/src/parse_deser/target_options.rs new file mode 100644 index 0000000..588180f --- /dev/null +++ b/src/parse_deser/target_options.rs @@ -0,0 +1,46 @@ +use crate::deser::*; + +#[derive(Debug, Clone)] +pub struct TargetOptions<'a> { + pub inherit: bool, + pub inherit_from: &'a str, + pub default: bool, + pub depend_on: Vec<&'a str>, +} + +impl<'a> From<&'a DeserTargetOptions> for TargetOptions<'a> { + fn from(value: &'a DeserTargetOptions) -> Self { + let mut ret = Self::get_default(); + + if let Some(v) = &value.inherit { + ret.inherit = *v; // dereference the bool to copy it + } + + if let Some(v) = &value.inherit_from { + ret.inherit_from = v; + } + + if let Some(v) = &value.default { + ret.default = *v; // dereference the bool to copy it + } + + if let Some(v) = &value.depend_on { + for string in v { + ret.depend_on.push(string); + } + } + + ret + } +} + +impl<'a> TargetOptions<'a> { + pub fn get_default() -> Self { + Self { + inherit: true, + inherit_from: "main", + default: false, + depend_on: vec![], + } + } +} diff --git a/src/parse_deser/target_options_list.rs b/src/parse_deser/target_options_list.rs new file mode 100644 index 0000000..38fee23 --- /dev/null +++ b/src/parse_deser/target_options_list.rs @@ -0,0 +1,22 @@ +use super::*; +use crate::deser::*; +use std::collections::HashMap; + +#[derive(Debug, Clone)] +pub struct TargetOptionList<'a>(pub HashMap<&'a str, TargetOptions<'a>>); + +impl<'a> From<&'a DeserTargetList> for TargetOptionList<'a> { + fn from(value: &'a DeserTargetList) -> Self { + let mut ret: Self = TargetOptionList(HashMap::new()); + + for (name, target) in value { + if let Some(v) = &target.opts { + ret.0.insert(name, TargetOptions::from(v)); + } else { + ret.0.insert(name, TargetOptions::get_default()); + } + } + + ret + } +} diff --git a/src/parsed_config_processing/configuration_error.rs b/src/parsed_config_processing/configuration_error.rs deleted file mode 100644 index 7d18680..0000000 --- a/src/parsed_config_processing/configuration_error.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::fmt::Display; - -#[derive(Debug)] -pub struct ConfigurationError { - pub message: String, -} - -impl Display for ConfigurationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "configuration error: {}", self.message) - } -} diff --git a/src/parsed_config_processing/mod.rs b/src/parsed_config_processing/mod.rs deleted file mode 100644 index 9053796..0000000 --- a/src/parsed_config_processing/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod config_table; -pub mod configuration_error; -pub mod target; -pub mod target_options; - -pub use config_table::*; -pub use configuration_error::*; -pub use target::*; -pub use target_options::*; diff --git a/src/parsed_config_processing/target.rs b/src/parsed_config_processing/target.rs deleted file mode 100644 index f17d15c..0000000 --- a/src/parsed_config_processing/target.rs +++ /dev/null @@ -1,150 +0,0 @@ -use core::str; -use std::collections::HashMap; - -use super::*; -use crate::ngen_toml::*; - -#[derive(Debug, Clone)] -pub struct Target<'a> { - pub outfile: &'a str, - pub compiler: &'a str, - pub compiler_flags: Vec<&'a str>, - pub linker: &'a str, - pub linker_flags: Vec<&'a str>, - pub linker_libs: Vec<&'a str>, - pub sources: Vec<&'a str>, - pub opts: TargetOptions<'a>, -} - -impl<'a> Target<'a> { - pub fn get_default( - ) -> Self { - Target { - outfile: "a.out", - compiler: "cc", - compiler_flags: vec![], - linker: "cc", - linker_flags: vec![], - linker_libs: vec![], - sources: vec![], - opts: TargetOptions::get_default(), - } - } - pub fn from_parsed_target( - parsed_target: &'a ParsedTarget, - name: &'a str, - all_parsed_targets: &'a HashMap, - all_targets: &HashMap<&'a str, Target<'a>>, - inheritance_chain: &mut HashMap<&'a str, &'a str> - ) -> Result>, ConfigurationError> { - let mut retmap: HashMap<&str, Target> = HashMap::new(); - let mut ret = Self::get_default(); - - if ret.opts.inherit_from == name { - ret.opts.inherit = false; - } - - if let Some(v) = &parsed_target.opts { - ret.opts = TargetOptions::from_parsed_target_options(v)?; - } - - if ret.opts.inherit { - let inherit_target: Target; - - match all_targets.get(ret.opts.inherit_from) { - Some(v) => { - inherit_target = v.clone() - } - None => { - match all_parsed_targets.get(ret.opts.inherit_from) { - Some(v) => { - let inherited_targets: HashMap<&str, Target>; - if let Some(_) = inheritance_chain.get(ret.opts.inherit_from) { - let mut message = format!("inheritance dependency cylce detected: "); - for (targ_name, depends_on) in inheritance_chain { - message.push_str(&format!("`{}` depends on `{}`, ", targ_name, depends_on)); - } - message.push_str(&format!("and `{}` depends on `{}`", name, ret.opts.inherit_from)); - return Err(ConfigurationError { message }); - } - inheritance_chain.insert(name, ret.opts.inherit_from); - inherited_targets = Self::from_parsed_target(v, ret.opts.inherit_from, all_parsed_targets, all_targets, inheritance_chain)?; - inherit_target = inherited_targets.get(ret.opts.inherit_from).unwrap().clone(); - retmap.extend(inherited_targets.into_iter()); - } - None => { - return Err(ConfigurationError { - message: - format!("target `{0}` trying to inherit from target `{1}`, but target `{1}` does not exist", - name, - ret.opts.inherit_from) - }); - } - } - } - } - - - if let None = parsed_target.outfile { - ret.outfile = inherit_target.outfile; - } - if let None = parsed_target.compiler { - ret.compiler = inherit_target.compiler; - } - if let None = parsed_target.linker { - ret.linker = inherit_target.linker; - } - - for string in inherit_target.compiler_flags { - ret.compiler_flags.push(string); - } - for string in inherit_target.linker_flags { - ret.linker_flags.push(string); - } - for string in inherit_target.linker_libs { - ret.linker_libs.push(string); - } - for string in inherit_target.sources { - ret.sources.push(string); - } - } - - if let Some(v) = &parsed_target.outfile { - ret.outfile = v; - } - if let Some(v) = &parsed_target.compiler { - ret.compiler = v; - } - if let Some(v) = &parsed_target.compiler_flags { - for string in v { - ret.compiler_flags.push(string); - } - } - if let Some(v) = &parsed_target.linker { - ret.linker = v; - } else { - if let Some(v) = &parsed_target.compiler { - ret.linker = v; - } - } - if let Some(v) = &parsed_target.linker_flags { - for string in v { - ret.linker_flags.push(string); - } - } - if let Some(v) = &parsed_target.linker_libs { - for string in v { - ret.linker_libs.push(string); - } - } - if let Some(v) = &parsed_target.sources { - for string in v { - ret.sources.push(string); - } - } - - retmap.insert(name, ret); - - Ok(retmap) - } -} diff --git a/src/parsed_config_processing/target_options.rs b/src/parsed_config_processing/target_options.rs deleted file mode 100644 index f487a80..0000000 --- a/src/parsed_config_processing/target_options.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::*; -use crate::ngen_toml::*; - -#[derive(Debug, Clone)] -pub struct TargetOptions<'a> { - pub inherit: bool, - pub inherit_from: &'a str, - pub default: bool, -} - -impl<'a> TargetOptions<'a> { - pub fn get_default() -> Self { - TargetOptions { - inherit: true, - inherit_from: "main", - default: false, - } - } - pub fn from_parsed_target_options( - parsed_target_options: &'a ParsedTargetOptions, - ) -> Result { - let mut ret = Self::get_default(); - - if let Some(v) = &parsed_target_options.inherit { - ret.inherit = *v; - } - - if let Some(v) = &parsed_target_options.inherit_from { - ret.inherit_from = v; - } - - if let Some(v) = &parsed_target_options.default { - ret.default = *v; - } - - Ok(ret) - } -} diff --git a/src/validation.rs b/src/validation.rs new file mode 100644 index 0000000..9a1755d --- /dev/null +++ b/src/validation.rs @@ -0,0 +1,188 @@ +use std::{collections::HashMap, fmt::Display}; + +use crate::parse_deser::*; + +pub enum ErrorKind { + NoSources, + DependencyDoesNotExist, + DependencyCycle, + ParentDoesNotExist, + InheritanceCycle, + CompileCmdsTargetDoesNotExist, +} + +pub struct Error { + pub kind: ErrorKind, + pub message: String, +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "configuration error: {}", self.message) + } +} + +type Result = std::result::Result; + +type Chain<'a> = HashMap<&'a str, &'a str>; + +fn print_chain(chain: &Chain, start: &str, connector: &str) -> String { + let mut ret = "target".to_string(); + let mut i = 0; + let mut start = start; + while i < chain.len() { + let dep = chain.get(start).unwrap(); + if i == chain.len() - 1 { + ret.push_str(&format!(" but target `{}` {} `{}`", start, connector, dep)); + break; + } + ret.push_str(&format!(" `{}` {} `{}`,", start, connector, dep)); + start = dep; + i += 1; + } + + ret +} + +impl<'a> TargetList<'a> { + pub fn check_if_any_sources_are_provided(&self) -> Result<()> { + let mut sources_found = false; + for target in self.0.values() { + if !target.sources.is_empty() { + sources_found = true; + break; + } + } + if sources_found { + Ok(()) + } else { + Err(Error { + kind: ErrorKind::NoSources, + message: "no targets have any sources".to_string(), + }) + } + } +} + +impl<'a> TargetOptionList<'a> { + fn check_target_dep( + &self, + opts: &TargetOptions<'a>, + name: &'a str, + dep_chain: &mut Chain<'a>, + ) -> Result<()> { + for dep in opts.depend_on.clone() { + match self.0.get(dep) { + Some(v) => { + dep_chain.insert(name, dep); + if let Some(_) = dep_chain.get(dep) { + return Err(Error { + kind: ErrorKind::DependencyCycle, + message: format!( + "dependency cycle detected: {}", + print_chain(dep_chain, name, "depends on") + ), + }); + } + self.check_target_dep(v, dep, dep_chain)?; + let _ = dep_chain.remove(name); + } + None => { + return Err(Error { + kind: ErrorKind::DependencyDoesNotExist, + message: format!( + "target `{0}` depends on `{1}`, but target `{1}` does not exist", + name, dep + ), + }); + } + } + } + Ok(()) + } + + pub fn check_for_dependency_cycle(&self) -> Result<()> { + for (name, opts) in &self.0 { + let mut dep_chain: Chain = HashMap::new(); + self.check_target_dep(opts, name, &mut dep_chain)?; + } + Ok(()) + } + + fn check_target_parent( + &self, + opts: &TargetOptions<'a>, + name: &'a str, + dep_chain: &mut Chain<'a>, + ) -> Result<()> { + let inherit = opts.inherit_from; + if name == inherit { + return Ok(()); + } + match self.0.get(inherit) { + Some(v) => { + dep_chain.insert(name, inherit); + if let Some(_) = dep_chain.get(inherit) { + return Err(Error { + kind: ErrorKind::InheritanceCycle, + message: format!( + "inheritance cycle detected: {}", + print_chain(dep_chain, name, "inherits from") + ), + }); + } + self.check_target_parent(v, inherit, dep_chain)?; + } + None => { + return Err(Error { + kind: ErrorKind::ParentDoesNotExist, + message: format!( + "target `{0}` inherits from `{1}`, but target `{1}` does not exist", + name, inherit + ), + }); + } + } + Ok(()) + } + + pub fn get_inheritance_order(&self) -> Result>> { + let mut ret: Vec> = vec![]; + for (name, opts) in &self.0 { + if !opts.inherit { + ret.push(vec![name.to_string()]); + continue; + } + let mut dep_chain: Chain = HashMap::new(); + + self.check_target_parent(opts, name, &mut dep_chain)?; + + let mut order: Vec = vec![name.to_string()]; + let mut i = 0; + while let Some(v) = dep_chain.get(&order[i] as &str) { + order.push(v.to_string()); + i += 1; + } + ret.push(order); + } + Ok(ret) + } +} + +impl<'a> Config<'a> { + pub fn check_compile_commands_target(&self, target_list: &TargetList) -> Result<()> { + if !self.compile_commands { + return Ok(()); + } + match target_list.0.get(self.compile_commands_target) { + Some(_) => Ok(()), + None => Err(Error { + kind: ErrorKind::CompileCmdsTargetDoesNotExist, + message: format!( + "compile commands target `{}` does not exist", + self.compile_commands_target + ), + }), + } + } +} diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..b765468 --- /dev/null +++ b/test.txt @@ -0,0 +1,92 @@ +TargetList( + { + "three": Target { + outfile: "foobar", + compiler: "gcc", + compiler_flags: [ + "-Wall", + "-Wextra", + ], + linker: "gcc", + linker_flags: [], + linker_libs: [], + sources: [ + "main.c", + ], + opts: TargetOptions { + inherit: true, + inherit_from: "two", + default: false, + depend_on: [], + }, + }, + "one": Target { + outfile: "foobar", + compiler: "gcc", + compiler_flags: [], + linker: "gcc", + linker_flags: [], + linker_libs: [], + sources: [ + "main.c", + "main.c", + "main.c", + "main.c", + "main.c", + "main.c", + ], + opts: TargetOptions { + inherit: true, + inherit_from: "main", + default: false, + depend_on: [], + }, + }, + "two": Target { + outfile: "foobar", + compiler: "gcc", + compiler_flags: [ + "-Wall", + "-Wall", + ], + linker: "gcc", + linker_flags: [], + linker_libs: [], + sources: [ + "main.c", + "main.c", + "main.c", + "main.c", + "main.c", + "main.c", + "main.c", + ], + opts: TargetOptions { + inherit: true, + inherit_from: "one", + default: false, + depend_on: [], + }, + }, + "main": Target { + outfile: "foobar", + compiler: "cc", + compiler_flags: [], + linker: "cc", + linker_flags: [], + linker_libs: [], + sources: [ + "main.c", + "main.c", + "main.c", + "main.c", + ], + opts: TargetOptions { + inherit: false, + inherit_from: "main", + default: false, + depend_on: [], + }, + }, + }, +)