Simplified Build Scripts with the nob.h Header Library
nob.h is a tiny, header-only C library that lets you write build scripts in C instead of traditional tools like Makefiles or scripting languages like Python or Bash. For a software engineer, this offers a number of key benefits
You're Already a C Developer
You don't need to learn a new syntax. You can use a language you're already familiar with to manage your project's build process. This reduces cognitive overhead and makes it easier to jump into a new project.
Faster and Simpler Builds
Since the build script is a compiled C program, it can be much faster than a large Bash or Python script, especially for complex projects.
Strong Typing and Autocomplete
Unlike a shell script, your build script benefits from a C compiler's type checking. This helps you catch errors early. Modern IDEs also provide autocomplete and better navigation for C code.
Cross-Platform Portability
C is one of the most portable languages. By writing your build script in C, you can more easily create build recipes that work on Linux, macOS, and Windows without relying on platform-specific tools.
No External Dependencies
Since nob.h is a single header file, you just need to include it in your C file. There's nothing to install or configure, making it incredibly simple to integrate into any project.
Think of it as a minimalistic, C-native alternative to Make or CMake. It's perfect for small-to-medium-sized projects where you want a simple yet powerful build system without the complexity of a full-blown build toolchain.
Getting nob.h up and running is super easy. Just follow these steps
Download the Header
Go to the official GitHub repository and download the nob.h file. Place it in your project's root directory.
Create Your Build File
Create a new C file for your build script, for example, build.c.
Include the Header
At the top of your build.c file, add the following line
#define NOB_IMPLEMENTATION
#include "nob.h"
The #define NOB_IMPLEMENTATION is a special preprocessor directive that tells the compiler to include the function definitions, not just the declarations. You should only use this in one C file.
Write Your Build Logic
In the main function of build.c, you can now use all the functions provided by nob.h to define your build steps. Common functions include nob_mkdir(), nob_cmd_append(), nob_log(), and nob_cmd_run_sync().
Compile and Run
To compile your build script, you can simply use a C compiler. For example
cc -o build build.c
Now you have a compiled build executable. You can run it to trigger your build process!
./build
That's it! No weird configuration files or complex setup.
Here's a basic build.c file that demonstrates how you might compile a simple C project.
Let's assume you have a src directory with a main.c file and an inc directory with myheader.h.
#define NOB_IMPLEMENTATION
#include "nob.h"
void build_my_project(void) {
// Let's create a 'build' directory to put our build artifacts.
// If it already exists, nob_mkdir will just succeed.
nob_log(NOB_INFO, "Creating build directory...");
if (!nob_mkdir_if_not_exists("build")) return;
// Define the output executable path.
const char *executable_path = "build/my_app";
// Set up the compiler command.
Nob_Cmd cmd = {0};
nob_log(NOB_INFO, "Compiling the project...");
nob_cmd_append(&cmd, "cc");
nob_cmd_append(&cmd, "-Wall");
nob_cmd_append(&cmd, "-Wextra");
nob_cmd_append(&cmd, "-o", executable_path);
nob_cmd_append(&cmd, "src/main.c");
nob_cmd_append(&cmd, "-I", "inc"); // Include our header directory.
// Run the command and check for errors.
if (!nob_cmd_run_sync(cmd)) {
nob_log(NOB_ERROR, "Failed to compile the project.");
return;
}
nob_log(NOB_INFO, "Successfully compiled: %s", executable_path);
}
int main(int argc, char **argv) {
NOB_GO_HOME_IF_NOB_H();
// The main function where you can define different build targets.
// For now, we'll just run our main build function.
build_my_project();
return 0;
}
Here's a slightly more advanced example that adds a "clean" and "run" target, similar to what you'd see in a Makefile.
#define NOB_IMPLEMENTATION
#include "nob.h"
// Function to clean up the build directory.
void clean_project(void) {
nob_log(NOB_INFO, "Cleaning up build directory...");
if (!nob_rmdir("build")) {
nob_log(NOB_ERROR, "Failed to remove build directory.");
} else {
nob_log(NOB_INFO, "Cleanup successful!");
}
}
// Function to compile the project (same as before).
void build_project(void) {
nob_log(NOB_INFO, "Building the project...");
if (!nob_mkdir_if_not_exists("build")) return;
Nob_Cmd cmd = {0};
nob_cmd_append(&cmd, "cc", "-o", "build/my_app", "src/main.c", "-Wall", "-Wextra");
if (!nob_cmd_run_sync(cmd)) {
nob_log(NOB_ERROR, "Build failed.");
return;
}
nob_log(NOB_INFO, "Build successful!");
}
// Function to run the compiled executable.
void run_project(void) {
Nob_Cmd cmd = {0};
nob_log(NOB_INFO, "Running the application...");
nob_cmd_append(&cmd, "build/my_app");
nob_cmd_run_sync(cmd);
}
int main(int argc, char **argv) {
NOB_GO_HOME_IF_NOB_H();
// Check command line arguments to determine the action.
if (argc < 2) {
nob_log(NOB_ERROR, "Usage: %s <clean|build|run>", argv[0]);
return 1;
}
const char *action = argv[1];
if (strcmp(action, "clean") == 0) {
clean_project();
} else if (strcmp(action, "build") == 0) {
build_project();
} else if (strcmp(action, "run") == 0) {
build_project(); // Always build before running.
run_project();
} else {
nob_log(NOB_ERROR, "Unknown action: %s", action);
return 1;
}
return 0;
}