diff --git a/src/config.rs b/src/config.rs index 555bbe4..003ad9c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ use toml::{Table, Value}; pub struct Config { pub build_dir: String, pub compile_commands: bool, + pub compile_commands_target: String, } impl Config { @@ -13,6 +14,7 @@ impl Config { Config { build_dir: String::from("build"), compile_commands: false, + compile_commands_target: String::from(""), } } pub fn set(&mut self, table: &Table) { @@ -27,12 +29,19 @@ impl Config { } "compile_commands" => { if let Value::Boolean(b) = table.get(k).unwrap() { - eprintln!("warn: config.compile_commands has no functionality (yet)"); + //eprintln!("warn: config.compile_commands has no functionality (yet)"); self.compile_commands = *b; } else { panic!("fatal: {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!("fatal: {k} invalid type, must be a string."); + } + } _ => panic!("fatal: unrecognized key {k}."), } } diff --git a/src/main.rs b/src/main.rs index d5c7afe..f929d1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ use clap::Parser; use core::panic; -use std::fs::{read, write}; +use std::{ + fs::{read, write}, + path::Path, +}; use toml::{Table, Value}; mod target; @@ -16,19 +19,23 @@ struct Args { #[arg(short, long, default_value = "build.ninja")] output_file: String, + + #[arg(long)] + write_compile_commands: Option, } -fn gen_ninja_code(config: &Target) -> String { +fn gen_ninja_code(target: &Target) -> String { let mut ret = String::from("\n"); - let name = &config.name; - let outfile = &config.outfile; - let compiler = &config.compiler; - let compiler_flags = &config.compiler_flags.join(" "); - let linker = &config.linker; - let linker_flags = config.linker_flags.join(" "); - let linker_libs = config.linker_libs.join(" "); - let sources = &config.sources; + // necessary because you cannot access struct members in a format string :( + let name = &target.name; + let outfile = &target.outfile; + let compiler = &target.compiler; + let compiler_flags = &target.compiler_flags.join(" "); + let linker = &target.linker; + let linker_flags = &target.linker_flags.join(" "); + let linker_libs = &target.linker_libs.join(" "); + let sources = &target.sources; ret.push_str(&format!( "\ @@ -65,7 +72,7 @@ build $builddir/{name}/dep: mkdir ret.push_str(&format!( " || $builddir/{name}/obj $builddir/{name}/dep\nbuild {name}: phony $builddir/{name}/{outfile}\n" )); - if config.is_default { + if target.is_default { ret.push_str(&format!("\ndefault {name}\n")); } ret.push_str(&format!("# END TARGET {name}\n")); @@ -73,6 +80,41 @@ build $builddir/{name}/dep: mkdir ret } +fn gen_compile_commands(target: &Target, build_dir: &str) -> String { + let mut ret = String::from("[\n"); + + let name = &target.name; + let compiler = &target.compiler; + let compiler_flags = &target.compiler_flags.join(" "); + + let pwd = Path::new("."); + let pwd = pwd + .canonicalize() + .expect("cwd is non canonical (something is very wrong)"); + let pwd = pwd.to_str().unwrap(); + + for (i, s) in target.sources.iter().enumerate() { + let new_file = s.replace("/", "-"); + let obj_name = format!("{build_dir}/{name}/obj/{new_file}.o"); + + ret.push_str(&format!( + " {{ + \"directory\": \"{pwd}\", + \"command\": \"{compiler} {compiler_flags} -o {obj_name} -c {s}\", + \"file\": \"{s}\" + }}" + )); + if i != target.sources.len() - 1 { + ret.push(','); + } + ret.push('\n'); + } + + ret.push_str("]\n"); + + ret +} + fn main() { let args = Args::parse(); let output_file = &args.output_file; @@ -84,23 +126,61 @@ fn main() { let parsed_config = toml::from_str::(&config_file_string) .unwrap_or_else(|e| panic!("fatal: could not parse config file: {e}")); + // get config let mut config = Config::get_default(); - - let mut targets: Vec = Vec::new(); - targets.push(Target::new(&parsed_config, "main", None)); for k in parsed_config.keys() { if let Value::Table(t) = parsed_config.get(k).unwrap() { if k == "config" { config.set(&t); - } else { - targets.push(Target::new(&t, k, Some(&targets[0]))); + break; + } + } + } + //---- + + // parse the main target first + let mut targets: Vec = Vec::new(); + 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 + for k in parsed_config.keys() { + if let Value::Table(t) = parsed_config.get(k).unwrap() { + 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; + } } } } - let build_dir = config.build_dir; - let _compile_commands = config.compile_commands; + if let Some(file) = args.write_compile_commands { + 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), + ) + .expect("fatal: could not write {file}"); + } + let build_dir = &config.build_dir; let mut ninjafile = format!( "\ # Generated by ngen. Do not modify by hand. @@ -111,7 +191,7 @@ rule mkdir command = mkdir -p $out description = Creating directory $out -rule regen +rule regen_ninjafile command = ngen -c $in -o $out generator = 1 description = Regenerating $out @@ -119,14 +199,31 @@ rule regen ", ); - ninjafile.push_str(&format!( - "build {output_file}: regen {config_file_name}\n pool = console\n" - )); + 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!("fatal: could not write ninja file: {e}")); + write(output_file, ninjafile).expect("fatal: could not write ninja file"); }