compile commands functionality added
This commit is contained in:
parent
d76e080841
commit
19be0d2a6a
2 changed files with 131 additions and 25 deletions
|
@ -6,6 +6,7 @@ use toml::{Table, Value};
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub build_dir: String,
|
pub build_dir: String,
|
||||||
pub compile_commands: bool,
|
pub compile_commands: bool,
|
||||||
|
pub compile_commands_target: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -13,6 +14,7 @@ impl Config {
|
||||||
Config {
|
Config {
|
||||||
build_dir: String::from("build"),
|
build_dir: String::from("build"),
|
||||||
compile_commands: false,
|
compile_commands: false,
|
||||||
|
compile_commands_target: String::from(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set(&mut self, table: &Table) {
|
pub fn set(&mut self, table: &Table) {
|
||||||
|
@ -27,12 +29,19 @@ impl Config {
|
||||||
}
|
}
|
||||||
"compile_commands" => {
|
"compile_commands" => {
|
||||||
if let Value::Boolean(b) = table.get(k).unwrap() {
|
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;
|
self.compile_commands = *b;
|
||||||
} else {
|
} else {
|
||||||
panic!("fatal: {k} invalid type, must be a boolean.");
|
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}."),
|
_ => panic!("fatal: unrecognized key {k}."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
145
src/main.rs
145
src/main.rs
|
@ -1,6 +1,9 @@
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use std::fs::{read, write};
|
use std::{
|
||||||
|
fs::{read, write},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
use toml::{Table, Value};
|
use toml::{Table, Value};
|
||||||
|
|
||||||
mod target;
|
mod target;
|
||||||
|
@ -16,19 +19,23 @@ struct Args {
|
||||||
|
|
||||||
#[arg(short, long, default_value = "build.ninja")]
|
#[arg(short, long, default_value = "build.ninja")]
|
||||||
output_file: String,
|
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 mut ret = String::from("\n");
|
||||||
|
|
||||||
let name = &config.name;
|
// necessary because you cannot access struct members in a format string :(
|
||||||
let outfile = &config.outfile;
|
let name = &target.name;
|
||||||
let compiler = &config.compiler;
|
let outfile = &target.outfile;
|
||||||
let compiler_flags = &config.compiler_flags.join(" ");
|
let compiler = &target.compiler;
|
||||||
let linker = &config.linker;
|
let compiler_flags = &target.compiler_flags.join(" ");
|
||||||
let linker_flags = config.linker_flags.join(" ");
|
let linker = &target.linker;
|
||||||
let linker_libs = config.linker_libs.join(" ");
|
let linker_flags = &target.linker_flags.join(" ");
|
||||||
let sources = &config.sources;
|
let linker_libs = &target.linker_libs.join(" ");
|
||||||
|
let sources = &target.sources;
|
||||||
|
|
||||||
ret.push_str(&format!(
|
ret.push_str(&format!(
|
||||||
"\
|
"\
|
||||||
|
@ -65,7 +72,7 @@ build $builddir/{name}/dep: mkdir
|
||||||
ret.push_str(&format!(
|
ret.push_str(&format!(
|
||||||
" || $builddir/{name}/obj $builddir/{name}/dep\nbuild {name}: phony $builddir/{name}/{outfile}\n"
|
" || $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!("\ndefault {name}\n"));
|
||||||
}
|
}
|
||||||
ret.push_str(&format!("# END TARGET {name}\n"));
|
ret.push_str(&format!("# END TARGET {name}\n"));
|
||||||
|
@ -73,6 +80,41 @@ build $builddir/{name}/dep: mkdir
|
||||||
ret
|
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() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let output_file = &args.output_file;
|
let output_file = &args.output_file;
|
||||||
|
@ -84,23 +126,61 @@ fn main() {
|
||||||
let parsed_config = toml::from_str::<Table>(&config_file_string)
|
let parsed_config = toml::from_str::<Table>(&config_file_string)
|
||||||
.unwrap_or_else(|e| panic!("fatal: could not parse config file: {e}"));
|
.unwrap_or_else(|e| panic!("fatal: could not parse config file: {e}"));
|
||||||
|
|
||||||
|
// get config
|
||||||
let mut config = Config::get_default();
|
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() {
|
for k in parsed_config.keys() {
|
||||||
if let Value::Table(t) = parsed_config.get(k).unwrap() {
|
if let Value::Table(t) = parsed_config.get(k).unwrap() {
|
||||||
if k == "config" {
|
if k == "config" {
|
||||||
config.set(&t);
|
config.set(&t);
|
||||||
} else {
|
break;
|
||||||
targets.push(Target::new(&t, k, Some(&targets[0])));
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//----
|
||||||
|
|
||||||
|
// 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;
|
if let Some(file) = args.write_compile_commands {
|
||||||
let _compile_commands = config.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!(
|
let mut ninjafile = format!(
|
||||||
"\
|
"\
|
||||||
# Generated by ngen. Do not modify by hand.
|
# Generated by ngen. Do not modify by hand.
|
||||||
|
@ -111,7 +191,7 @@ rule mkdir
|
||||||
command = mkdir -p $out
|
command = mkdir -p $out
|
||||||
description = Creating directory $out
|
description = Creating directory $out
|
||||||
|
|
||||||
rule regen
|
rule regen_ninjafile
|
||||||
command = ngen -c $in -o $out
|
command = ngen -c $in -o $out
|
||||||
generator = 1
|
generator = 1
|
||||||
description = Regenerating $out
|
description = Regenerating $out
|
||||||
|
@ -119,14 +199,31 @@ rule regen
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
|
|
||||||
ninjafile.push_str(&format!(
|
if config.compile_commands {
|
||||||
"build {output_file}: regen {config_file_name}\n pool = console\n"
|
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 {
|
for t in targets {
|
||||||
ninjafile.push_str(&gen_ninja_code(&t));
|
ninjafile.push_str(&gen_ninja_code(&t));
|
||||||
}
|
}
|
||||||
|
|
||||||
write(output_file, ninjafile)
|
write(output_file, ninjafile).expect("fatal: could not write ninja file");
|
||||||
.unwrap_or_else(|e| panic!("fatal: could not write ninja file: {e}"));
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue