How Ninja Achieves Fast Incremental Builds for Chromium

This article explores the architectural choices that enable the Ninja build system to execute rapid incremental compilations for the massive Chromium web browser project. By offloading complex build configuration to meta-build systems, utilizing simple manifest files, optimizing C++ header dependency tracking into a single binary log, and minimizing file system queries, Ninja eliminates the pre-compilation overhead that slows down traditional build tools.

Offloading Logic to Meta-Build Systems

Traditional build systems like Make must evaluate variables, conditionals, and string manipulations every time a developer initiates a build. In a project the size of Chromium, which contains tens of thousands of source files, evaluating this logic takes a significant amount of time before a single file is even compiled.

Ninja solves this by acting like an assembler rather than a high-level programming language. It strips away all conditional logic, loops, and platform checks. Instead, Chromium uses a meta-build system called GN (Generate Ninja) to handle the complex configuration logic once. GN generates flat, static .ninja manifest files. Because these files require zero evaluation, Ninja can lex and parse them into memory in milliseconds.

Centralizing Header Dependencies

In C++ projects, modifying a single header file requires recompiling every source file that includes it. Compilers track this by outputting .d (dependency) files during compilation, which list all the headers used by a specific source file.

On an incremental build, a standard build system must open and read tens of thousands of individual .d text files scattered across the hard drive to build its dependency graph. This causes a massive disk I/O bottleneck.

Ninja avoids this bottleneck by processing a .d file immediately after a file is compiled and storing that information in a single, centralized binary file named .ninja_deps. The original .d file is then ignored. When a Chromium developer runs an incremental build, Ninja reads this single, compact binary file sequentially into memory. Reading one large binary file is orders of magnitude faster than opening and parsing thousands of small text files.

Minimizing File System Calls

To determine which files have changed since the last build, a build system must query the operating system for file modification timestamps using the stat() system call. When building Chromium, checking the timestamps of over 100,000 input, header, and output files is one of the most time-consuming operations, especially on operating systems with slower file systems like Windows.

Ninja structures its internal dependency graph so that it never calls stat() on the same file more than once per build. It resolves paths to canonical strings in memory and maps them directly to graph nodes, avoiding expensive operating system calls to resolve relative file paths.

The Build Log

Alongside the dependency database, Ninja maintains a file called .ninja_log. This file records the exact command line used to generate every output file, along with the timestamps of the last build.

During an incremental build, Ninja compares the current build manifest against the .ninja_log. This allows Ninja to instantly detect not only if a source file has changed, but also if a compiler flag or build rule was modified, without needing to invoke external shell scripts or helper processes to verify the build state.

Low-Overhead Execution

Once Ninja parses its manifests, loads the .ninja_deps and .ninja_log files, and checks the file timestamps, it immediately begins executing the necessary compilation commands. Unlike older build systems that require manual flags to enable multithreading, Ninja automatically detects the number of available CPU cores on the host machine and runs commands in parallel by default.

By reducing the decision-making phase of the build to less than a second, Ninja ensures that when a Chromium developer changes a single C++ file, the compiler starts working almost instantaneously.