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}."),
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										141
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								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
 | 
				
			||||||
",
 | 
					",
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if config.compile_commands {
 | 
				
			||||||
        ninjafile.push_str(&format!(
 | 
					        ninjafile.push_str(&format!(
 | 
				
			||||||
        "build {output_file}: regen {config_file_name}\n  pool = console\n"
 | 
					            "\
 | 
				
			||||||
 | 
					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