major rewrite #2, dependency functionality

This commit is contained in:
Noah Swerhun 2024-03-15 08:33:28 -05:00
parent e20beb9bf0
commit 8d05da5209
18 changed files with 645 additions and 370 deletions

View file

@ -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(())
}

View file

@ -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>;

View file

@ -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::*;

View 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(())
}

View file

@ -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>,
}

View file

@ -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));
}

View 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
View 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
View 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(),
}
}
}

View 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>>);

View 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![],
}
}
}

View 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
}
}

View file

@ -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)
}
}

View file

@ -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::*;

View file

@ -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)
}
}

View file

@ -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
View 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
View 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: [],
},
},
},
)