From 338cc3c79498129a1f9d5b4ed73d75149324e9a9 Mon Sep 17 00:00:00 2001 From: Noah Swerhun Date: Tue, 5 Mar 2024 23:54:20 -0600 Subject: [PATCH] improved error messages and began documentation --- Cargo.lock | 128 ------------------------------------------- Cargo.toml | 2 +- README.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/config.rs | 10 ++-- src/main.rs | 26 +++++---- src/target.rs | 28 +++++----- 6 files changed, 175 insertions(+), 167 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3d2134..09076b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,54 +2,12 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "anstream" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "utf8parse", -] - [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" -[[package]] -name = "anstyle-parse" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" -dependencies = [ - "anstyle", - "windows-sys", -] - [[package]] name = "clap" version = "4.5.1" @@ -66,10 +24,8 @@ version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ - "anstream", "anstyle", "clap_lex", - "strsim", ] [[package]] @@ -90,12 +46,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - [[package]] name = "equivalent" version = "1.0.1" @@ -185,12 +135,6 @@ dependencies = [ "serde", ] -[[package]] -name = "strsim" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" - [[package]] name = "syn" version = "2.0.52" @@ -242,78 +186,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" - [[package]] name = "winnow" version = "0.6.5" diff --git a/Cargo.toml b/Cargo.toml index 745b6a8..482c5ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "4.5.1", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive", "std", "help", "usage"], default-features = false } toml = "0.8.10" diff --git a/README.md b/README.md index 1dfa353..696bc73 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,14 @@ that any experienced programmer should be familiar with. In doing so, ngen fills the gap between writing your own makefile and wrangling with CMakeLists.txt. -ngen generates files for the [Ninja](https://ninja-build.org/) build system. -Ninja is a small, modern build system that uses a bare-bones configuration -language that is easy to both read and generate (but not necessarily easy to -write by hand). "Where other build systems are high-level languages Ninja aims -to be an assembler," according to Ninja's website. It can be thought of as a +ngen generates files for the small and modern [Ninja](https://ninja-build.org/) +build system. "Where other build systems are high-level languages Ninja aims to +be an assembler," according to Ninja's website. It can be thought of as a simpler, faster replacement for the classic `make`. It is used by default by -Meson; CMake can also be configured to use ninja as a backend. +Meson; CMake can also be configured to use ninja as a backend. Ninja is used by +used by Google to build Chromium, v8, etc., and it is also used to build LLVM. +(All this to say, Ninja is commonly used and it is likely installed on your +system already.) ## Building @@ -36,3 +37,138 @@ Build: `cargo build --release` Install: `sudo sh install.sh` Uninstall: `sudo sh uninstall.sh` + +## Usage +What follows is a tutorial of how to set up ngen for an existing executable +project. If you are looking for a reference, look . + +The first thing you will need is an `ngen.toml` file. This is what will specify +all of the build parameters, such as compilation flags, source files to track, +etc. Start by creating this file and opening it in your editor. + +The simplest valid `ngen.toml` just lists the source files you want to track. +Lets say you have a executable project that looks like this: + +```txt +example +├── ngen.toml +└── src + ├── foobar.c + ├── functions.c + ├── include + │   ├── foobar.h + │   ├── functions.h + │   ├── main.h + │   └── util.h + ├── main.c + └── util.c +``` + +In your `ngen.toml`, write the following: + +```toml +sources = [ + "src/main.c", + "src/util.c", + "src/functions.c", + "src/foobar.c", +] +``` + +**The `sources` key is a *list* of *strings*, each specifying a single source +file name.** + +Now run `ngen`. This will generate a `build.ninja` file in the current +working directory. You won't ever have to touch file; thats what ngen is for. You +also won't ever have to run `ngen` yourself again (unless your `build.ninja` +gets deleted); Ninja will take care of regenerating the build file if +`ngen.toml` changes. + +With your `build.ninja` generated, run `ninja` on the command line. Thats it! +Your project is now built, you will find it in `build/main/a.out`. Remember, you +can also freely add and remove files from this list without running `ngen` +again: Ninja will regenerate the `build.ninja` for you. + +Now, while this is functional, it isn't very useful. It is very likely that you +will want to specify a compiler (gcc/clang), pass some flags, link some +libraries into your final exectuable, and definitly name your program something +other than "a.out." ngen makes these things dead simple, too. + +**The `outfile` key is a *string* that specifies the name of the file produced +by the `linker` (see below).** + +Lets set this to "example." + +```toml +outfile = "example" +``` + +**The `compiler` key is a *string* that specifies the program that will be used +to turn .c files into .o files.** + +By default, if not specified, `compiler` is set to "cc," which on Linux systems +should be a C compiler. It should be noted that the compiler you choose must +support the `-MD` and `-MF` flags to generate dependency files (both gcc and +clang support this). Lets say we want to use "gcc." Add the following line to +your `ngen.toml`: + +```toml +compiler = "gcc" +``` + +**The `compiler_flags` key is a *list* of *strings* that contains the arguments +to be passed to the `compiler` during the compilation of each `source` file.** + +It is not necessary to add the `-c` or `-o outfile` flags, ngen will take care +of this for you. For example, add the following to your `ngen.toml`: + +```toml +compiler_flags = ["-Wall", "-Wextra -O2"] +``` + +**The `linker` key is a *string* that specifies the program that will be used +to combine the .o files into the final `outfile`.** + +If the `linker` key is not found, it will be set to the value of `compiler`. For +this example, we don't have to change anything here. + +**The `linker_flags` key is a *list* of *strings* that contains the arguments +to be passed to the `linker` during the linking of the `outfile`.** + +Library flags (`-lm`, `lyourlib`) should NOT be included here. This is for +linker options, not libraries. There is nothing we have to set here; the sytax +works the same as `compiler_flags`. + +**The `linker_libs` key is a *list* of *strings* that contains the link library +arguments to be linked to the `outfile`.** + +THIS is where library flags (`-lm`, `lyourlib`) go. Lets say our example project +needs the math library: + +```toml +linker_libs = ["-lm"] +``` + +Now, our `ngen.toml` looks like this: + +```toml +outfile = "example" +compiler = "gcc" +compiler_flags = ["-Wall", "-Wextra -O2"] +linker_libs = ["-lm"] +sources = [ + "src/main.c", + "src/util.c", + "src/functions.c", + "src/foobar.c", +] +``` + +This is a much more realistic looking project. Once again, any changes to any of +these values will be automatically picked up by Ninja and accounted for in the +build. Running `ninja -v` should show that the options you set were recognized, +and your files were rebuilt accordingly. + +TODO: explain +- seperate targets +- config table diff --git a/src/config.rs b/src/config.rs index 003ad9c..ef90d0e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,3 @@ -use core::panic; - use toml::{Table, Value}; #[derive(Debug, Clone)] @@ -24,7 +22,7 @@ impl Config { if let Value::String(s) = table.get(k).unwrap() { self.build_dir = s.to_owned(); } else { - panic!("fatal: {k} invalid type, must be a string."); + panic!("error parsing config table: {k} invalid type, must be a string."); } } "compile_commands" => { @@ -32,17 +30,17 @@ impl Config { //eprintln!("warn: config.compile_commands has no functionality (yet)"); self.compile_commands = *b; } else { - panic!("fatal: {k} invalid type, must be a boolean."); + panic!("error parsing config table: {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!("error parsing config table: {k} invalid type, must be a string."); } } - _ => panic!("fatal: unrecognized key {k}."), + _ => panic!("error parsing config table: unrecognized key {k}."), } } } diff --git a/src/main.rs b/src/main.rs index f929d1e..b52e03b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use clap::Parser; -use core::panic; use std::{ - fs::{read, write}, + fs::{read_to_string, write}, path::Path, }; use toml::{Table, Value}; @@ -12,15 +11,18 @@ use target::*; mod config; use config::*; +/// ngen is a build file generator for the ninja build system. #[derive(Parser)] struct Args { + /// Path to the toml configuration file #[arg(short, long, default_value = "ngen.toml")] config_file: String, + /// Path to generated ninja file #[arg(short, long, default_value = "build.ninja")] output_file: String, - #[arg(long)] + #[arg(long, hide = true)] write_compile_commands: Option, } @@ -90,7 +92,7 @@ fn gen_compile_commands(target: &Target, build_dir: &str) -> String { let pwd = Path::new("."); let pwd = pwd .canonicalize() - .expect("cwd is non canonical (something is very wrong)"); + .unwrap_or_else(|e| panic!("cwd is non canonical (something is very wrong): {e}")); let pwd = pwd.to_str().unwrap(); for (i, s) in target.sources.iter().enumerate() { @@ -120,11 +122,10 @@ fn main() { let output_file = &args.output_file; let config_file_name = &args.config_file; - let config_file_raw = - read(config_file_name).unwrap_or_else(|e| panic!("fatal: could not read file: {e}")); - let config_file_string = String::from_utf8_lossy(&config_file_raw); - let parsed_config = toml::from_str::(&config_file_string) - .unwrap_or_else(|e| panic!("fatal: could not parse config file: {e}")); + let config_file = read_to_string(config_file_name) + .unwrap_or_else(|e| panic!("could not read config file {config_file_name}: {e}")); + let parsed_config = toml::from_str::
(&config_file) + .unwrap_or_else(|e| panic!("error parsing toml file {config_file_name}: {e}")); // get config let mut config = Config::get_default(); @@ -174,10 +175,10 @@ fn main() { ); } write( - file, + &file, &gen_compile_commands(&target_for_comp_cmds, &config.build_dir), ) - .expect("fatal: could not write {file}"); + .unwrap_or_else(|e| panic!("fatal: could not write {file}: {e}")); } let build_dir = &config.build_dir; @@ -225,5 +226,6 @@ build {output_file}: regen_ninjafile {config_file_name} || $builddir/compile_com ninjafile.push_str(&gen_ninja_code(&t)); } - write(output_file, ninjafile).expect("fatal: could not write ninja file"); + write(output_file, &ninjafile) + .unwrap_or_else(|e| panic!("could not write ninja file {ninjafile}: {e}")); } diff --git a/src/target.rs b/src/target.rs index 469350a..a42817d 100644 --- a/src/target.rs +++ b/src/target.rs @@ -46,14 +46,14 @@ impl Target { if let Value::String(s) = table.get(k).unwrap() { ret.outfile = s.to_owned(); } else { - panic!("fatal: {k} invalid type, must be a string."); + panic!("error parsing target {name}: {k} invalid type, must be a string."); } } "compiler" => { if let Value::String(s) = table.get(k).unwrap() { ret.compiler = s.to_owned(); } else { - panic!("fatal: {k} invalid type, must be a string."); + panic!("error parsing target {name}: {k} invalid type, must be a string."); } } "compiler_flags" => { @@ -62,11 +62,11 @@ impl Target { if let Value::String(s) = element { ret.compiler_flags.push(s.to_owned()); } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } "linker" => { @@ -74,7 +74,7 @@ impl Target { ret.linker = s.to_owned(); linker_set = true; } else { - panic!("fatal: {k} invalid type, must be a string."); + panic!("error parsing target {name}: {k} invalid type, must be a string."); } } "linker_flags" => { @@ -83,11 +83,11 @@ impl Target { if let Value::String(s) = element { ret.linker_flags.push(s.to_owned()); } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } "linker_libs" => { @@ -96,11 +96,11 @@ impl Target { if let Value::String(s) = element { ret.linker_libs.push(s.to_owned()); } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } "sources" => { @@ -109,24 +109,24 @@ impl Target { if let Value::String(s) = element { ret.sources.push(s.to_owned()); } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } } else { - panic!("fatal: {k} invalid type, must be a array of strings."); + panic!("error parsing target {name}: {k} invalid type, must be a array of strings."); } } "default" => { if let Value::Boolean(b) = table.get(k).unwrap() { ret.is_default = *b; } else { - panic!("fatal: {k} invalid type, must be a boolean."); + panic!("error parsing target {name}: {k} invalid type, must be a boolean."); } } _ => { if let Value::Table(_) = table.get(k).unwrap() { } else { - panic!("fatal: unrecognized key {k}."); + panic!("error parsing target {name}: unrecognized key {k}."); } } } @@ -137,7 +137,7 @@ impl Target { } if ret.sources == Vec::::new() { - panic!("fatal: you MUST specify at least one source"); + panic!("error parsing target {name}: you MUST specify at least one source"); } ret