The Role of BUILD.gn in Chromium's Module Dependencies
In the massive codebase of the Chromium web browser, maintaining
architectural integrity across thousands of modules is a critical
challenge. This article provides an overview of how
BUILD.gn files—the configuration files for Chromium's
meta-build system, GN (Generate Ninja)—do much more than just define how
code is compiled. Specifically, it examines how these files act as
architectural gatekeepers by enforcing strict target visibility rules
and executing automated C++ header dependency checks, ensuring that
modules only interact in explicitly approved ways.
Understanding BUILD.gn in Chromium
Chromium uses GN to generate build files for Ninja, the actual build
system. Every module or directory in Chromium contains a
BUILD.gn file that defines build targets, such as
executables, shared libraries, or source sets. Because C++ lacks a
native module system in the standards historically used by Chromium, the
project relies on BUILD.gn files to define module
boundaries, encapsulate internal logic, and control how different parts
of the browser communicate.
Enforcing Visibility Rules
By default, any target defined in a BUILD.gn file is
public, meaning any other target in the Chromium repository can depend
on it. To prevent tightly coupled "spaghetti code" and enforce
encapsulation, BUILD.gn provides the
visibility attribute.
The visibility attribute is a list of label patterns
that explicitly defines which other targets are allowed to depend on the
current target. If Target A attempts to add Target B to its
dependencies, GN checks Target B's visibility list. If
Target A is not on that list, the build configuration step fails
immediately.
Common ways Chromium uses visibility include:
- File-Private Targets: Setting
visibility = [ ":*" ]restricts a target so that only other targets within the exact sameBUILD.gnfile can depend on it. This is commonly used for internal helper libraries or split targets. - Directory-Level Encapsulation: Setting
visibility = [ "//content/browser/*" ]allows any target within the//content/browser/directory tree to use the target, while blocking access from the rest of the browser, such as//chrome/or//ui/. - Strict Allowlisting: A target can specify exact
targets, such as
visibility = [ "//chrome/browser:browser_process" ], ensuring only one specific module in the entire codebase can utilize it.
Through these rules, component owners can expose a clean public API target while hiding the messy implementation targets behind strict visibility walls.
Header Dependency Checks
In C++, it is easy for a developer to write an #include
directive for a header file that happens to be available on the
compiler's include path, without actually declaring a build dependency
on the module that owns that header. This leads to fragile builds that
break when compilation order changes, as well as violations of layered
architecture.
BUILD.gn files prevent this through header dependency
checking, which is executed via the gn check command (and
run automatically on Chromium's continuous integration servers).
Controlling Header
Exposure with public
Within a BUILD.gn target, source files are listed in the
sources array. By default, if a header file
(.h) is listed in sources, GN considers it
publicly includable by any direct dependent.
However, to enforce stricter boundaries, a target can define a
public array alongside sources.
- Headers listed in
publicare the official API of the module. - Headers listed only in
sourcesbecome private to that target.
If an external module attempts to #include a header that
is in sources but not in public,
gn check will throw an error, even if the external module
properly depends on the target.
Validating the Include Graph
When gn check runs, it parses the C++ source code to
find every #include statement and cross-references them
against the dependency graph defined in the BUILD.gn files.
For an #include to be valid, two conditions must be
met:
- The target containing the
#includemust have a declared dependency path to the target that owns the header. - The included header must be marked as
publicin the owning target.
The Role of deps
vs. public_deps
Header checks rely heavily on the distinction between
deps and public_deps in BUILD.gn
files.
deps(Private Dependencies): If Target A lists Target B in itsdeps, Target A can include Target B's public headers in its own.ccfiles. However, if Target C depends on Target A, Target C cannot include Target B's headers. The dependency is not transitive.public_deps(Public Dependencies): If Target A includes one of Target B's headers inside Target A's own public header file, Target A must list Target B inpublic_deps. This tells GN to forward Target B's public headers to anyone who depends on Target A.
If a developer uses deps instead of
public_deps when exposing a dependency's types in a public
header, gn check will detect the missing transitive link
and fail the build.
Handling Exceptions
In rare cases, legacy code or platform-specific configurations cannot
strictly adhere to GN's header rules. BUILD.gn provides two
main escape hatches for these scenarios:
check_includes = false: Added to a target inBUILD.gnto completely disable header checking for that specific target.// nogncheck: A comment placed on the same line as an#includedirective in a C++ file, instructing GN to ignore that specific inclusion during verification (often used for conditional includes guarded by preprocessor macros that GN cannot evaluate).
By combining visibility rules with public
header definitions and gn check, BUILD.gn
files transform Chromium's build system into an active static analysis
tool that preserves modularity at scale.