major rewrite #2, dependency functionality
This commit is contained in:
parent
e20beb9bf0
commit
8d05da5209
18 changed files with 645 additions and 370 deletions
|
@ -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<String, ContentGenerationError> {
|
||||
config: &Config,
|
||||
) -> Result<String> {
|
||||
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(())
|
||||
}
|
|
@ -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<T> = std::result::Result<T, Error>;
|
|
@ -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::*;
|
|
@ -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<String> {
|
||||
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<String, ContentGenerationError> {
|
||||
pub fn gen_ninja_target(name: &str, target: &Target) -> Result<String> {
|
||||
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<String> {
|
||||
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(())
|
||||
}
|
|
@ -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<String>,
|
||||
pub compiler: Option<String>,
|
||||
pub compiler_flags: Option<Vec<String>>,
|
||||
|
@ -11,28 +11,31 @@ pub struct ParsedTarget {
|
|||
pub linker_flags: Option<Vec<String>>,
|
||||
pub linker_libs: Option<Vec<String>>,
|
||||
pub sources: Option<Vec<String>>,
|
||||
pub opts: Option<ParsedTargetOptions>,
|
||||
pub opts: Option<DeserTargetOptions>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ParsedTargetOptions {
|
||||
pub struct DeserTargetOptions {
|
||||
pub inherit: Option<bool>,
|
||||
pub inherit_from: Option<String>,
|
||||
pub depend_on: Option<Vec<String>>,
|
||||
pub default: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ParsedConfigTable {
|
||||
pub struct DeserConfig {
|
||||
pub build_dir: Option<String>,
|
||||
pub compile_commands: Option<bool>,
|
||||
pub compile_commands_target: Option<String>,
|
||||
}
|
||||
|
||||
pub type DeserTargetList = HashMap<String, DeserTarget>;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NgenToml {
|
||||
pub config: Option<ParsedConfigTable>,
|
||||
pub targets: Option<HashMap<String, ParsedTarget>>,
|
||||
pub struct DeserNgenToml {
|
||||
pub config: Option<DeserConfig>,
|
||||
pub targets: Option<DeserTargetList>,
|
||||
}
|
176
src/main.rs
176
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<String, ParsedTarget> = 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));
|
||||
}
|
||||
|
|
|
@ -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<Self, ConfigurationError> {
|
||||
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)
|
||||
}
|
||||
}
|
11
src/parse_deser/mod.rs
Normal file
11
src/parse_deser/mod.rs
Normal file
|
@ -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::*;
|
99
src/parse_deser/target.rs
Normal file
99
src/parse_deser/target.rs
Normal file
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
6
src/parse_deser/target_list.rs
Normal file
6
src/parse_deser/target_list.rs
Normal file
|
@ -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>>);
|
46
src/parse_deser/target_options.rs
Normal file
46
src/parse_deser/target_options.rs
Normal file
|
@ -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![],
|
||||
}
|
||||
}
|
||||
}
|
22
src/parse_deser/target_options_list.rs
Normal file
22
src/parse_deser/target_options_list.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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<String, ParsedTarget>,
|
||||
all_targets: &HashMap<&'a str, Target<'a>>,
|
||||
inheritance_chain: &mut HashMap<&'a str, &'a str>
|
||||
) -> Result<HashMap<&'a str, Target<'a>>, 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)
|
||||
}
|
||||
}
|
|
@ -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<Self, ConfigurationError> {
|
||||
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)
|
||||
}
|
||||
}
|
188
src/validation.rs
Normal file
188
src/validation.rs
Normal file
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
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<Vec<Vec<String>>> {
|
||||
let mut ret: Vec<Vec<String>> = 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<String> = 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
|
||||
),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
92
test.txt
Normal file
92
test.txt
Normal file
|
@ -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: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
Loading…
Reference in a new issue