blob: c58eda3be8093becffe65d4e4ae8028b7155012b [file] [log] [blame] [view]
# Introduction to GN
This is an introduction to GN's terms and way of thinking. This should be
sufficient background to get your bearings in GN and how it's used in Zircon.
GN (and the Zircon build) are more complicated than the below will discuss, but
the average developer will not need to understand most of it on a deeper level.
The GN documentation pages [QuickStart] and [Language] give more detailed
background on GN, and [Reference] has the full language documentation. Use
the `gn help` command to print out the reference interactively for individual
topics. [Ninja] has its own documentation as well.
In the Zircon checkout after running `scripts/download-prebuilt`,
`scripts/gn` and `scripts/ninja` provide access to the prebuilt binaries.
[Ninja]: https://ninja-build.org/manual.html
[QuickStart]: https://gn.googlesource.com/gn/+/master/docs/quick_start.md
[Language]: https://gn.googlesource.com/gn/+/master/docs/language.md
[Reference]: https://gn.googlesource.com/gn/+/master/docs/reference.md
## Two-phase operation: `gn` and `ninja`
Unlike `make`, `gn` is only ever half the story. It's in the name: GN stands
for Generate [Ninja]. There's a division of responsibilities between the
tools that corresponds to a separation of running the build into two steps:
1. `gn gen` takes all the configuration choices and makes all the decisions.
All it really does is generate the `.ninja` files in the build directory.
This step only has to be done by hand when you change the configuration or
completely nuke the build directory. In general, it only needs to be done
when the GN files change, and in incremental builds it happens
automatically if the GN files or configuration change.
1. `ninja` runs the commands to compile and link, etc. It handles incremental
builds and parallelism. This is the step you do every time you've changed
a source file, like running `make`. GN automatically emits rules to
re-generate the Ninja files by running gn gen again when a relevant
`BUILD.gn` file (or some other relevant files) has changed, so for most
changes after the first time you've built, `ninja` does it all.
Ninja is very simple compared to something like GNU `make`. It just compares
times and runs commands and its input files are written by machines, not
humans. However, it builds in some useful things that we bend over backward
to accomplish in `make`:
- Rebuild each file when the command line changes. Command lines will only
really change when GN runs again. But after that, Ninja is smart about
incremental builds re-doing commands for files that have changed and not
re-running commands that haven't changed.
- Handle compiler-generated dependency files. Ninja knows about the makefile
subset that compilers emit in `.d` files and consumes them directly when
directed to by GN.
- Run with `-j$(getconf _NPROCESSORS_ONLN)` by default. You can pass `-j1`
to serialize or `-j1024` when using Goma, but out of the box it does the
parallelism you usually want.
- Prevent interleaved `stdout`/`stderr` output from parallel jobs. Ninja
buffers the output so that error messages don't get garbled by spew from
multiple processes.
- Support terse/verbose command output. By default, Ninja emits short
`Kbuild`-style messages for each command it runs, in a wordy-progress-meter
style. The -v switch is like V=1 in `Kbuild`, to show each actual command.
GN was developed as part of the Chromium project to replace older build
systems. Fuchsia inherited it from them, and it is now used across the tree as
the primary build system.
## Build directories and `args.gn`
Ninja always runs in the build directory. All commands Ninja runs are run from
the root of the build directory. The common thing is `ninja -C build-dir`.
Neither GN nor Ninja cares what build directory you use. It's common practice
to use a subdirectory of the source directory, and since file paths are
usually rebased to be relative to the build directory, the file names given to
the compiler will have a whole lot of `../` in them if you put your build
directory elsewhere; but it should work. It's long been common practice in
Chromium (predating GN itself) to use `out/_something_` in the source
directory, and Fuchsia inherited that default. But nothing cares what build
directory names you choose, though the `build-*` subdirectory name pattern is
in the `.gitignore` for Zircon and the `out` subdirectory is in the top-level
`.gitignore` file for Fuchsia.
The basic command is `gn gen build-dir`. This creates `build-dir/` if needed,
and populates it with Ninja files for the current configuration. If
`build-dir/args.gn` exists, then `gn gen` will read that file to set GN build
arguments (see below). `args.gn` is a file in GN syntax that can assign values
to GN build arguments that override any hardcoded defaults. This means just
repeating `gn gen build-dir` preserves what you did last time.
You can also add `--args=...` to gn gen or use the `gn args` command to
configure your build arguments. The `gn args` command gives you a way to run
your $EDITOR on the `args.gn` file, and upon exiting the editor the command
will re-run `gn gen` for you with the new arguments. You can also just edit
`args.gn` any time, and the next Ninja run will re-generate the build files.
Args can also be set using the `fx set` command which invokes `gn gen`. Note
that Zircon args are set differently than args for the rest of Fuchsia. For
example, to set `foxtrot` in Fuchsia and `zulu` in Zircon both to `true` via
`fx set`:
```sh
$ fx set <your configuration> --args 'foxtrot = true' \
--args 'zircon_extra_args = { zulu = true }'
```
See [GN Build Arguments](/docs/gen/build_arguments.md), particularly
[zircon_args](/docs/gen/build_arguments.md#zircon_args) and
[zircon_extra_args](/docs/gen/build_arguments.md#zircon_extra_args), for
details.
## GN syntax and formatting
GN syntax is whitespace-insensitive. `x=1 y=2` is the same as:
```gn
x = 1
y = 2
```
However, there is *one true indentation and formatting style* for GN code. The
`gn format` command reformats syntactically valid GN code into the canonical
style. There is editor syntax support for Emacs and Vim. Canonical formatting
will be enforced by Tricium and mass reformatting will be done. If you don't
like the formatting, file bugs or make a change in upstream GN and if it lands
we'll mass reformat everyone to conform to the new one truth.
## Source paths and GN labels
GN uses POSIX-style paths (always in represented as strings) both for files and
to refer to GN-defined entities. Paths can be relative, which means relative
to the directory containing the `BUILD.gn` file where the path string appears.
They can also be "source-absolute", meaning relative to the root of the source
tree. Source-absolute paths begin with `//` in GN.
**TODO(BLD-353): _This is a hold-over from the "layer cake" design and will
probably change soon:_** Generically, an absolute path will look like
`//path/to/dir`. However, in order to maintain both the standalone Zircon
build and the integrated Fuchsia build, *all absolute paths in Zircon will look
like `$zx/path/to/dir`*. This allows us to use expand `$zx` to `//` in the
case of the standalone build and `//zircon/` in the case of the integrated
build.
When source paths are eventually used in commands, they are translated into
OS-appropriate paths that are either absolute or relative to the build
directory (where commands run).
Predefined variables are used in source path contexts to locate parts of the
build directory:
- `$root_build_dir` is the build directory itself
- `$root_out_dir` is the subdirectory for the current toolchain (see below)
- This is where all "top-level" targets go. In many GN builds, all
executables and libraries go here. But it is not used directly very
often in the Zircon build.
- `$target_out_dir` is the subdirectory of `$root_out_dir` for files built by
targets in the current `BUILD.gn` file. This is where the object files go.
In the Zircon build, this is where executables and libraries go as well.
- `$target_gen_dir` is a corresponding place recommended to put generated code
- `$root_gen_dir` is a place for generated code needed outside this
subdirectory
GN labels are how we refer to things defined in a `BUILD.gn` file. They are
based on source paths, and always appear inside GN strings. The full syntax of
a GN label is `"dir:name"` where the `dir` part is a source path that names the
particular `BUILD.gn` file. The `name` refers to a target defined in that file
with `target_type("name") { ... }`. As a shorthand, you can define a target
with the same name as its directory. The label `"//path/to/dir"` with no `:`
part is a shorthand for `"//path/to/dir:dir"`. This is the most common case.
## Dependency graph and `BUILD.gn` files
Everything in GN is rooted in the dependency graph. There is one root
`BUILD.gn` file. The only way other `BUILD.gn` files are even read is if there
is a dependency on a label in that directory.
There are no wildcards. Every target must be named as a dependency of some
other target to get built. You can give individual targets on the `ninja`
command line to explicitly get them built. Otherwise they must be in the graph
from the `//:default` target (named `default` in the root `BUILD.gn` file).
There is a generic meta-target type called `group()` that doesn't correspond to
a file produced by the build but is rather a way to structure your dependency
graph nicely. Top-level targets like `default` are usually groups. You can
have a group for all the drivers for a piece of hardware, a group for all the
binaries in a use case, etc.
When some code uses something at runtime (a data file, another executable,
etc.) but doesn't use it as a direct input at build time, that file belongs in
the `data_deps` list of target that uses it. In the Zircon build, that will be
enough to get the thing into the BOOTFS image at its appointed place.
Targets can also be labeled with `testonly = true` to indicate that the target
contains tests. GN prevents targets that are not `testonly` from depending on
targets that are, allowing for some level of control over where test binaries
end up.
The whole Zircon build is driven from one or more `zbi()` targets. This will
make a ZBI by building and using the ZBI host tool. Targets can be placed in
this image by existing within its dependency graph, and so you can give it
dependencies on the kernel and any drivers or executables you want in the
image.
Note that getting targets defined in Ninja files is at the granularity of
`BUILD.gn` files, though the dependency graph from default or any other target
is at the granularity of an individual target. So having some target in the
`BUILD.gn` file in the graph from default makes all targets in that file (and
toolchain, see below) available as targets on the Ninja command line even
though they are not built by default.
## More Advanced Concepts
### GN expression language and GN scopes
GN is a simple, dynamically-typed, imperative language whose sole purpose at
the end of the day is to produce declarative Ninja rules. Everything revolves
around scopes, which is both the lexical binding construct of the language and
a data type.
GN values can take any of several types:
- Boolean, either `true` or `false`
- Integer, signed with normal decimal syntax; not used much
- String, always in "double-quotes" (note below about `$` expansion)
- Scope, in curly braces: `{ ... }`; see below.
- List of values, in square brackets: `[ 1, true, "foo", { x=1 y=2 } ]` is a
list of four elements.
Values are dynamically-typed and there is no kind of implicit type coercion,
but there is never type-checking as such. Values of different types never
compare as equal, but it's not an error to compare them.
String literals expand simple `$var` or `${var}` expressions inside the
double-quotes. This is an immediate expansion: `x${var}y` is the same as `x +
var + y` when var is a string. In this way, any value can be rendered as a
pretty-printed string.
Identifiers made up of alphanumerics and underscores can populate a scope via
assignment operators. Imperative assignment with `=` and modification via `+=`
are really all the GN language does (there are also some special ways to have
side effects like `print()`, used for debugging; and `write_file()`, used
sparingly).
Each file is internally represented as a scope, and there is no global scope.
Shared "globals" can be defined in a `.gni` file and imported where they are
used (`import("//path/to/something.gni")`). Each `.gni file is processed once
per toolchain (see below for information about toolchains), and the resulting
scope is copied into the importing file scope.
Target declarations introduce a sub-scope:
```gn
foo = true
executable("target") {
foo = 12
}
# Outside the target, foo == true
```
GN is very strict in diagnosing errors when a variable is defined but never
used within a scope. The scope inside a target acts like a keyword argument
list for the target with checking that the argument names were spelled
correctly. The target-defining code can also use `assert()` to diagnose an
error if a required argument was omitted.
A value can also be a scope. Then it's acting like a struct when you use it:
`value.member`. But a scope is always a block of GN code that executes to
yield its set of names and values:
```gn
foo = {
x = global_tuning + 42
If (some_global && other_thing == "foobar") {
y = 2
}
}
```
This always defines `foo.x` but only sometimes defines `foo.y`.
### GN toolchains
GN has a concept called a "toolchain". This will all be happening behind the
scenes and Zircon hackers shouldn't need to deal with it directly, but it helps
to understand the mechanism.
This is what encapsulates the compilers and default compilation switches. It's
also the only real way to get the same things compiled twice in different
ways. In Zircon there will be several toolchains:
- Host
- Vanilla userland (compiled with default `-fPIE`)
- Shared libraries in userland (compiled with `-fPIC`)
- `userboot`
- Kernel
- Kernel physical-address mode for ARM64 (compiled with `-mstrict-align`)
- Multiboot for x86 (compiled with `-m32`)
- UEFI for Gigaboot
- Toolchains are also used in the ["variants"
scheme](/docs/gen/zircon_build_arguments.md#variants) that is how we allow selectively
enabling ASan or the like for parts of userland.
Each toolchain is identified by a GN label. The full syntax for target labels
is actually `//path/to/dir:name(//path/to/toolchain/label)`. Usually the
toolchain is omitted and this is expanded to `label($current_toolchain)`,
i.e. label references are usually within the same toolchain.
All the GN files are instantiated separately in each toolchain. Each toolchain
can set global variables differently, so GN code can use tests like `if
(is_kernel)` or `if (current_toolchain == some_toolchain)` to behave
differently in different contexts. This way the GN code stays with the source
it describes, but it can still do different subsets of shared sources for
kernel and user, etc.