Ninja Build System
Ninja build system — concepts, hand-written build.ninja, CMake + Ninja backend, incremental builds, compile_commands.json, and speed tips.
TL;DR
Ninja is a small, fast build system designed to be generated by higher-level tools like CMake. It beats Make on large C++ projects because it uses a simpler dependency model and runs with maximum parallelism by default.
# Use Ninja as CMake's build backend
cmake -G Ninja -B build
cmake --build build # parallel by defaultCore Concepts
Ninja has four primitives:
| Primitive | Purpose |
|---|---|
rule | Named build recipe (how to compile/link) |
build | Edge: outputs = rule(inputs) |
variable | String substitution |
pool | Limit concurrency for expensive steps |
Everything else (.d dependency files, implicit outputs, phony targets) builds on these.
Hand-Written build.ninja
# Variables
cxx = g++
cflags = -std=c++23 -O2 -Wall
# Rules
rule compile
command = $cxx $cflags -MMD -MF $out.d -c $in -o $out
depfile = $out.d
deps = gcc
description = Compile $in
rule link
command = $cxx $in -o $out
description = Link $out
# Build edges
build obj/main.o: compile src/main.cpp
build obj/engine.o: compile src/engine.cpp
build obj/renderer.o: compile src/renderer.cpp
# Link
build app: link obj/main.o obj/engine.o obj/renderer.o
# Default target
default appKey variables in rules
| Variable | Meaning |
|---|---|
$in | Space-separated list of input files |
$out | Space-separated list of output files |
$depfile | Path to GCC-format dependency file |
Automatic Dependency Tracking
The -MMD -MF $out.d flags make the compiler write a .d file listing all #included headers. Ninja reads these with deps = gcc and rebuilds correctly when any header changes — no manual dependency listing needed.
rule cxx_compile
command = g++ -std=c++23 -MMD -MF $out.d -c $in -o $out
depfile = $out.d
deps = gcc # tells ninja to read and cache the depfilePhony Targets and Pools
# Phony target (like .PHONY in Make)
build clean: phony
command = rm -rf obj/ app
build all: phony app tests
# Pool: limit link parallelism (linking is memory-heavy)
pool link_pool
depth = 2
rule link
command = $cxx $in -o $out
pool = link_pool # at most 2 link jobs at onceUsing Ninja with CMake
# Configure with Ninja generator
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release
# Build (Ninja auto-detects CPU count for -j)
cmake --build build
# Build with explicit parallelism
cmake --build build -- -j16
# Clean
cmake --build build --target clean
# List all targets
cmake --build build --target helpCMakeLists.txt — nothing special needed
CMake generates build.ninja automatically. All standard CMake features (targets, dependencies, install) work identically with the Ninja generator.
compile_commands.json
Ninja + CMake generates compile_commands.json for clangd (LSP), clang-tidy, and other tools:
cmake -G Ninja -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# creates build/compile_commands.json
# Symlink to project root for clangd to find it
ln -sf build/compile_commands.json compile_commands.jsonUseful ninja Commands
ninja # build default target
ninja -j8 # limit to 8 parallel jobs
ninja app tests # build specific targets
ninja -n # dry run — show commands without running
ninja -v # verbose: print full commands
ninja -C build # run from a different directory
# Diagnostics
ninja -t list # list all targets
ninja -t deps app # show dependency chain for 'app'
ninja -t compdb # print compilation database (JSON)
ninja -t graph app | dot -Tpng > deps.png # visualize dependency graph
ninja -t clean # remove built outputs
ninja -t recompact # compact the .ninja_deps databaseIncremental Builds
Ninja tracks two kinds of state:
- Timestamps — if an output is older than its inputs, rebuild
- Dependency database (
.ninja_deps) — discovered header dependencies from depfiles
On large projects this makes a "no-op" build (nothing changed) extremely fast — Ninja just stat()s files and exits.
# Force rebuild of a specific target
ninja -t clean app && ninja app
# Show what would be rebuilt
ninja -n
# Check why something is being rebuilt
ninja -d explain app 2>&1 | head -20Speed Tips
# Use Ninja instead of Make (2-5x faster on large projects)
cmake -G Ninja ...
# Enable link-time parallelism with lld
add_link_options(-fuse-ld=lld)
# Split compilation with unity/jumbo builds (fewer TUs)
set_target_properties(mylib PROPERTIES UNITY_BUILD ON)
# Precompiled headers
target_precompile_headers(myapp PRIVATE src/pch.h)
# Use ccache with Ninja
cmake -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ...
# Distributed builds with distcc or icecc
set(CMAKE_CXX_COMPILER_LAUNCHER "distcc")Comparison: Ninja vs Make vs Meson
| Ninja | Make | Meson | |
|---|---|---|---|
| Role | Build runner (generated) | Build runner (hand-written or generated) | Meta-build (generates Ninja) |
| Parallelism | Default max | Manual -j$(nproc) | Default max (via Ninja) |
| Dependency tracking | Depfiles, deps = gcc | .d files (manual setup) | Automatic |
| Incremental accuracy | High | Medium (timestamp only) | High |
| Human-writable | Barely | Yes | Yes |
| Speed (no-op build) | Very fast | Slow (recursive make) | Very fast (via Ninja) |
compile_commands.json | Yes (via CMake flag) | With Bear/CMake | Built-in |
Typical workflow: write CMakeLists.txt or meson.build → generate Ninja files → Ninja executes the build.