compile commands functionality added

This commit is contained in:
Noah Swerhun 2024-03-05 22:50:29 -06:00
parent d76e080841
commit 19be0d2a6a
2 changed files with 131 additions and 25 deletions

View file

@ -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}."),
}
}

View file

@ -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<String>,
}
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::<Table>(&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<Target> = 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<Target> = 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");
}