Ninja
Ninja is a small build system with one goal: to be fast. Unlike Make, it is not meant to be written by hand. Higher-level tools — CMake, Meson, GN (used by Chromium), and others — generate build.ninja files describing every build step, and Ninja executes that graph as quickly as possible, running independent steps in parallel and rebuilding only what changed.
Ninja's speed matters most for large projects: a no-op build in a codebase with tens of thousands of files completes in a fraction of a second, so developers get fast feedback after small edits. It is the default or recommended backend for most modern C and C++ build generators and many other ecosystems, so understanding how it decides what to rebuild — and how to ask it why — pays off whenever builds are slow or rebuild too much.
TL;DR
- Ninja executes build graphs that other tools generate; you rarely write
build.ninjayourself. - Files contain rules (how to run a command) and build statements (which outputs come from which inputs).
- Header dependencies come from compiler depfiles, stored in
.ninja_depswithdeps = gccordeps = msvc. - Parallelism is automatic; pools limit expensive steps such as linking.
- Diagnose unexpected rebuilds with
ninja -d explain; list and inspect targets withninja -ttools. - Use it via
cmake -G Ninjaor Meson, and profile builds from.ninja_log.
Quick Example
A small hand-written build.ninja, to see what generators produce:
Using Ninja with CMake and investigating a build:
Core Concepts
Rules and Build Statements
Ninja has no wildcards, conditionals, or functions — generators handle that logic, and Ninja only executes. That simplicity is why it can load and check huge graphs quickly.
Deciding What to Rebuild
An output is rebuilt when it is missing, older than any input, or built with a different command line (Ninja records commands in .ninja_log). Header dependencies are not known in advance, so compilers write depfiles listing every header they read; with deps = gcc (or deps = msvc for /showIncludes), Ninja stores them compactly in .ninja_deps and deletes the depfile.
restat = 1 tells Ninja to check whether an output actually changed after a command runs; if a code generator rewrites a file with identical content and preserves its timestamp, downstream steps are skipped.
Parallelism and Pools
Ninja runs as many jobs as the machine has CPUs (plus a small margin) by default; override with -j. Pools cap specific work, such as a link_pool with depth 2 to avoid running out of memory when linking large binaries in parallel. The built-in console pool gives a job direct terminal access, for interactive or progress-printing steps.
Generators
Diagnostics and Tooling
Build profiling: .ninja_log records start and end times of every step. Tools such as ninjatracing convert it into a Chrome trace to show which steps dominate build time and where parallelism is lost.
Best Practices
Let a Generator Write Ninja Files
Describe the build in CMake, Meson, or GN; hand-written Ninja files are for learning and very small tools.
Use Depfiles for Every Compile Step
Without depfiles, header changes do not trigger rebuilds, causing stale binaries and confusing bugs.
Limit Memory-Heavy Steps With Pools
Put large links or heavy code generation into a pool so parallel builds do not exhaust memory in CI.
Investigate Rebuilds Instead of Cleaning
When builds rebuild too much or too little, use -d explain and -t missingdeps to find the cause rather than running a clean build.
Common Mistakes
Editing Generated build.ninja Files
Missing Dependencies on Generated Headers
Unbounded Parallel Links in CI
FAQ
What is Ninja used for?
Executing build graphs generated by higher-level build systems such as CMake, Meson, and GN, with a focus on fast incremental and no-op builds.
Is Ninja faster than Make?
For large projects, typically yes — especially for incremental and no-op builds — because Ninja loads a precomputed graph with minimal logic, while Make evaluates rules, patterns, and functions at build time.
Should I write build.ninja files by hand?
Rarely. Ninja intentionally lacks the features needed for maintainable hand-written builds; use a generator and treat Ninja files as build output.
How do I find out why Ninja rebuilds something?
Run ninja -d explain with the target to see which inputs, commands, or missing files make it dirty, and ninja -t query to see its inputs and outputs.
How do I get compile_commands.json with Ninja?
Use ninja -t compdb, or set CMAKE_EXPORT_COMPILE_COMMANDS=ON with CMake; Meson writes one automatically in the build directory.
Related Topics
- Build Tools — Build systems and their tradeoffs
- CMake — Generates Ninja builds for C and C++
- Meson — Uses Ninja as its default backend
- GNU Make — The classic alternative executor
- C++ — A language whose large builds benefit most
- CI/CD — Running fast builds in pipelines