352 lines
12 KiB
Markdown
352 lines
12 KiB
Markdown
|
|
# Writing GN Templates
|
|||
|
|
GN and Ninja are documented here:
|
|||
|
|
* GN: https://gn.googlesource.com/gn/+/main/docs/
|
|||
|
|
* Ninja: https://ninja-build.org/manual.html
|
|||
|
|
|
|||
|
|
[TOC]
|
|||
|
|
|
|||
|
|
## Things to Consider When Writing Templates
|
|||
|
|
### Inputs and Depfiles
|
|||
|
|
List all files read (or executed) by an action as `inputs`.
|
|||
|
|
* It is not enough to have inputs listed by dependent targets. They must be
|
|||
|
|
listed directly by targets that use them, or added by a depfile.
|
|||
|
|
* Non-system Python imports are inputs! For scripts that import such modules,
|
|||
|
|
use [`action_with_pydeps`] to ensure all dependent Python files are captured
|
|||
|
|
as inputs.
|
|||
|
|
|
|||
|
|
[`action_with_pydeps`]: https://cs.chromium.org/chromium/src/build/config/python.gni?rcl=320ee4295eb7fabaa112f08d1aacc88efd1444e5&l=75
|
|||
|
|
|
|||
|
|
To understand *why* actions must list all inputs directly, you need to
|
|||
|
|
understand ninja's "restat" directive, which is used for all GN `action()`s.
|
|||
|
|
|
|||
|
|
From https://ninja-build.org/manual.html:
|
|||
|
|
|
|||
|
|
> if present, causes Ninja to re-stat the command’s outputs after execution of
|
|||
|
|
> the command. Each output whose modification time the command did not change
|
|||
|
|
> will be treated as though it had never needed to be built. This may cause the
|
|||
|
|
> output’s reverse dependencies to be removed from the list of pending build
|
|||
|
|
> actions.
|
|||
|
|
|
|||
|
|
So, if your action depends on target "X", and "X" does not change its outputs
|
|||
|
|
when rebuilt, then ninja will not bother to rebuild your target.
|
|||
|
|
|
|||
|
|
For action inputs that are not computable during "gn gen", actions can write
|
|||
|
|
depfiles (.d files) to add additional input files as dependencies for
|
|||
|
|
subsequent builds. They are relevant only for incremental builds since they
|
|||
|
|
won't exist for the initial build.
|
|||
|
|
* Depfiles should not list files that GN already lists as `inputs`.
|
|||
|
|
* Besides being redundant, listing them also makes it harder to remove
|
|||
|
|
inputs, since removing them from GN does not immediately remove them from
|
|||
|
|
depfiles.
|
|||
|
|
* Stale paths in depfiles can cause ninja to complain of circular
|
|||
|
|
dependencies [in some cases](https://bugs.chromium.org/p/chromium/issues/detail?id=639042).
|
|||
|
|
* Use [`action_helpers.write_depfile()`] to write these.
|
|||
|
|
|
|||
|
|
[`action_helpers.write_depfile()`]: https://source.chromium.org/chromium/chromium/src/+/main:build/action_helpers.py?q=symbol:%5Cbwrite_depfile
|
|||
|
|
|
|||
|
|
### Ensuring "gn analyze" Knows About your Inputs
|
|||
|
|
"gn analyze" is used by bots to run only affected tests and build only affected
|
|||
|
|
targets. Try it out locally via:
|
|||
|
|
```bash
|
|||
|
|
echo "compute_inputs_for_analyze = true" >> out/Debug/args.gn
|
|||
|
|
gn analyze //out/Debug <(echo '{
|
|||
|
|
"files": ["//BUILD.gn"],
|
|||
|
|
"test_targets": ["//base"],
|
|||
|
|
"additional_compile_targets":[]}') result.txt; cat result.txt
|
|||
|
|
```
|
|||
|
|
* For analyze to work properly, GN must know about all inputs.
|
|||
|
|
* Inputs added by depfiles are *not available* to "gn analyze".
|
|||
|
|
* When paths listed in a target's depfile are listed as `inputs` to a
|
|||
|
|
dependent target, analyze will be correct.
|
|||
|
|
* Example: An `AndroidManifest.xml` file is an input to an
|
|||
|
|
`android_library()` and is included in an `android_apk()`'s depfile.
|
|||
|
|
`gn analyze` will know that a change to the file will require the APK
|
|||
|
|
to be rebuilt, because the file is marked as an input to the library, and
|
|||
|
|
the library is a dep of the APK.
|
|||
|
|
* When paths listed in a target's depfile are *not* listed as `inputs` to a
|
|||
|
|
dependent target, a few options exist:
|
|||
|
|
* Rather than putting the inputs in a depfile, force users of your template
|
|||
|
|
to list them, and then have your action re-compute them and assert that
|
|||
|
|
they were correct.
|
|||
|
|
* `jinja_template()` does this.
|
|||
|
|
* Rather than putting the inputs in a depfile, compute them beforehand and
|
|||
|
|
save them to a text file. Have your template Use `read_file()` to read
|
|||
|
|
them in.
|
|||
|
|
* `action_with_pydeps()` does this.
|
|||
|
|
* Continue using a depfile, but use an `exec_script()` to compute them when
|
|||
|
|
[`compute_inputs_for_analyze`](https://cs.chromium.org/chromium/src/build/config/compute_inputs_for_analyze.gni)
|
|||
|
|
is set.
|
|||
|
|
* `grit()` does this.
|
|||
|
|
|
|||
|
|
### Outputs
|
|||
|
|
#### What to List as Outputs
|
|||
|
|
Do not list files as `outputs` unless they are important. Outputs are important
|
|||
|
|
if they are:
|
|||
|
|
* used as an input by another target, or
|
|||
|
|
* are roots in the dependency graph (e.g. binaries, apks, etc).
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
* An action runs a binary that creates an output as well as a log file. Do not
|
|||
|
|
list the log file as an output.
|
|||
|
|
|
|||
|
|
Rationale:
|
|||
|
|
* Inputs and outputs are a node's public API on the build graph. Not listing
|
|||
|
|
"implementation detail"-style outputs prevents other targets from depending on
|
|||
|
|
them as inputs.
|
|||
|
|
* Not listing them also helps to minimize the size of the build graph (although
|
|||
|
|
this would be noticeable only for frequently used templates).
|
|||
|
|
|
|||
|
|
#### Where to Place Outputs
|
|||
|
|
**Option 1:** To make outputs visible in codesearch (e.g. generated sources):
|
|||
|
|
* use `$target_gen_dir/$target_name.$EXTENSION`.
|
|||
|
|
|
|||
|
|
**Option 2:** Otherwise (for binary files):
|
|||
|
|
* use `$target_out_dir/$target_name.$EXTENSION`.
|
|||
|
|
|
|||
|
|
**Option 3:** For outputs that are required at runtime
|
|||
|
|
(e.g. [runtime_deps](https://gn.googlesource.com/gn/+/main/docs/reference.md#runtime_deps)),
|
|||
|
|
options 1 & 2 do not work because they are not archived in builder/tester bot
|
|||
|
|
configurations. In this case:
|
|||
|
|
* use `$root_out_dir/gen.runtime` or `$root_out_dir/obj.runtime`.
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
```python
|
|||
|
|
# This .json file is used at runtime and thus cannot go in target_gen_dir.
|
|||
|
|
_target_dir_name = rebase_path(get_label_info(":$target_name", "dir"), "//")
|
|||
|
|
_output_path = "$root_out_dir/gen.runtime/$_target_dir_name/$target_name.json"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Option 4:** For outputs that map 1:1 with executables, and whose paths cannot
|
|||
|
|
be derived at runtime:
|
|||
|
|
* use `$root_build_dir/YOUR_NAME_HERE/$target_name`.
|
|||
|
|
|
|||
|
|
Examples:
|
|||
|
|
```python
|
|||
|
|
# Wrapper scripts for apks:
|
|||
|
|
_output_path = "$root_build_dir/bin/$target_name"
|
|||
|
|
# Metadata for apks. Used by binary size tools.
|
|||
|
|
_output_path = "$root_build_dir/size-info/${invoker.name}.apk.jar.info"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Best Practices for Python Actions
|
|||
|
|
Outputs should be atomic and take advantage of `restat=1`.
|
|||
|
|
* Make outputs atomic by writing to temporary files and then moving them to
|
|||
|
|
their final location.
|
|||
|
|
* Rationale: An interrupted write can leave a file with an updated timestamp
|
|||
|
|
and corrupt contents. Ninja looks only at timestamps.
|
|||
|
|
* Do not overwrite an existing output with identical contents.
|
|||
|
|
* Rationale: `restat=1` is a ninja feature enabled for all actions that
|
|||
|
|
short-circuits a build when output timestamps do not change. This feature is
|
|||
|
|
the reason that the total number of build steps sometimes decreases when
|
|||
|
|
building..
|
|||
|
|
* Use [`action_helpers.atomic_output()`] to perform both of these techniques.
|
|||
|
|
|
|||
|
|
[`action_helpers.atomic_output()`]: https://source.chromium.org/chromium/chromium/src/+/main:build/action_helpers.py?q=symbol:%5Cbatomic_output
|
|||
|
|
|
|||
|
|
Actions should be deterministic in order to avoid hard-to-reproduce bugs.
|
|||
|
|
Given identical inputs, they should produce byte-for-byte identical outputs.
|
|||
|
|
* Some common mistakes:
|
|||
|
|
* Depending on filesystem iteration order.
|
|||
|
|
* Writing absolute paths in outputs.
|
|||
|
|
* Writing timestamps in files (or in zip entries).
|
|||
|
|
* Tip: Use [`zip_helpers.py`] when writing `.zip` files.
|
|||
|
|
|
|||
|
|
[`zip_helpers.py`]: https://source.chromium.org/chromium/chromium/src/+/main:build/zip_helpers.py
|
|||
|
|
|
|||
|
|
## Style Guide
|
|||
|
|
Chromium GN files follow
|
|||
|
|
[GN's Style Guide](https://gn.googlesource.com/gn/+/main/docs/style_guide.md)
|
|||
|
|
with a few additions.
|
|||
|
|
|
|||
|
|
### Action Granularity
|
|||
|
|
* Prefer writing new Python scripts that do what you want over
|
|||
|
|
composing multiple separate actions within a template.
|
|||
|
|
* Fewer targets makes for a simpler build graph.
|
|||
|
|
* GN logic and build logic winds up much simpler.
|
|||
|
|
|
|||
|
|
Bad:
|
|||
|
|
```python
|
|||
|
|
template("generate_zipped_sources") {
|
|||
|
|
generate_files("${target_name}__gen") {
|
|||
|
|
...
|
|||
|
|
outputs = [ "$target_gen_dir/$target_name.temp" ]
|
|||
|
|
}
|
|||
|
|
zip(target_name) {
|
|||
|
|
deps = [ ":${target_name}__gen" ]
|
|||
|
|
inputs = [ "$target_gen_dir/$target_name.temp" ]
|
|||
|
|
outputs = [ invoker.output_zip ]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Good:
|
|||
|
|
```python
|
|||
|
|
template("generate_zipped_sources") {
|
|||
|
|
action(target_name) {
|
|||
|
|
script = "generate_and_zip.py"
|
|||
|
|
...
|
|||
|
|
outputs = [ invoker.output_zip ]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Naming for Intermediate Targets
|
|||
|
|
Targets that are not relevant to users of your template should be named as:
|
|||
|
|
`${target_name}__$something`.
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
```python
|
|||
|
|
template("my_template") {
|
|||
|
|
action("${target_name}__helper") {
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
action(target_name) {
|
|||
|
|
deps = [ ":${target_name}__helper" ]
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This scheme ensures that subtargets defined in templates do not conflict with
|
|||
|
|
top-level targets.
|
|||
|
|
|
|||
|
|
### Visibility for Intermediate Targets
|
|||
|
|
|
|||
|
|
You can restrict what targets can depend on one another using [visibility].
|
|||
|
|
When writing templates, with multiple intermediate targets, `visibility` should
|
|||
|
|
only be applied to the final target (the one named `target_name`). Applying only
|
|||
|
|
to the final target ensures that the invoker-provided visibility does not
|
|||
|
|
prevent intermediate targets from depending on each other.
|
|||
|
|
|
|||
|
|
[visibility]: https://gn.googlesource.com/gn/+/main/docs/reference.md#var_visibility
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
```python
|
|||
|
|
template("my_template") {
|
|||
|
|
# Do not forward visibility here.
|
|||
|
|
action("${target_name}__helper") {
|
|||
|
|
# Do not forward visibility here.
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
action(target_name) {
|
|||
|
|
# Forward visibility here.
|
|||
|
|
forward_variables_from(invoker, [ "visibility" ])
|
|||
|
|
deps = [ ":${target_name}__helper" ]
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Variables
|
|||
|
|
Prefix variables within templates and targets with an underscore. For example:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
template("example") {
|
|||
|
|
_outer_sources = invoker.extra_sources
|
|||
|
|
|
|||
|
|
source_set(target_name) {
|
|||
|
|
_inner_sources = invoker.sources
|
|||
|
|
sources = _outer_sources + _inner_sources
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This convention conveys that `sources` is relevant to `source_set`, while
|
|||
|
|
`_outer_sources` and `_inner_sources` are not.
|
|||
|
|
|
|||
|
|
### Passing Arguments to Targets
|
|||
|
|
Pass arguments to targets by assigning them directly within target definitions.
|
|||
|
|
|
|||
|
|
When a GN template goes to resolve `invoker.FOO`, GN will look in all enclosing
|
|||
|
|
scopes of the target's definition. It is hard to figure out where `invoker.FOO`
|
|||
|
|
is coming from when it is not assigned directly within the target definition.
|
|||
|
|
|
|||
|
|
Bad:
|
|||
|
|
```python
|
|||
|
|
template("hello") {
|
|||
|
|
script = "..."
|
|||
|
|
action(target_name) {
|
|||
|
|
# This action will see "script" from the enclosing scope.
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Good:
|
|||
|
|
```python
|
|||
|
|
template("hello") {
|
|||
|
|
action(target_name) {
|
|||
|
|
script = "..." # This is equivalent, but much more clear.
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Exception:** `testonly` and `visibility` can be set in the outer scope so that
|
|||
|
|
they are implicitly passed to all targets within a template.
|
|||
|
|
|
|||
|
|
This is okay:
|
|||
|
|
```python
|
|||
|
|
template("hello") {
|
|||
|
|
testonly = true # Applies to all nested targets.
|
|||
|
|
action(target_name) {
|
|||
|
|
script = "..."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Using forward_variables_from()
|
|||
|
|
Using [forward_variables_from()] is encouraged, but special care needs to be
|
|||
|
|
taken when forwarding `"*"`. The variables `testonly` and `visibility` should
|
|||
|
|
always be listed explicitly in case they are assigned in an enclosing
|
|||
|
|
scope.
|
|||
|
|
See [this bug] for more a full example.
|
|||
|
|
|
|||
|
|
To make this easier, `//build/config/BUILDCONFIG.gn` defines:
|
|||
|
|
```python
|
|||
|
|
TESTONLY_AND_VISIBILITY = [ "testonly", "visibility" ]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Example usage:
|
|||
|
|
```python
|
|||
|
|
template("action_wrapper") {
|
|||
|
|
action(target_name) {
|
|||
|
|
forward_variables_from(invoker, "*", TESTONLY_AND_VISIBILITY)
|
|||
|
|
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
If your template defines multiple targets, be careful to apply `testonly` to
|
|||
|
|
both, but `visibility` only to the primary one (so that the primary one is not
|
|||
|
|
prevented from depending on the other ones).
|
|||
|
|
|
|||
|
|
Example:
|
|||
|
|
```python
|
|||
|
|
template("template_with_multiple_targets") {
|
|||
|
|
action("${target_name}__helper") {
|
|||
|
|
forward_variables_from(invoker, [ "testonly" ])
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
action(target_name) {
|
|||
|
|
forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
An alternative would be to explicitly set `visibility` on all inner targets,
|
|||
|
|
but doing so tends to be tedious and has little benefit.
|
|||
|
|
|
|||
|
|
[this bug]: https://bugs.chromium.org/p/chromium/issues/detail?id=862232
|
|||
|
|
[forward_variables_from]: https://gn.googlesource.com/gn/+/main/docs/reference.md#func_forward_variables_from
|
|||
|
|
|
|||
|
|
## Useful Ninja Flags
|
|||
|
|
Useful ninja flags when developing build rules:
|
|||
|
|
* `ninja -v` - log the full command-line of every target.
|
|||
|
|
* `ninja -v -n` - log the full command-line of every target without having
|
|||
|
|
to wait for a build.
|
|||
|
|
* `ninja -w dupbuild=err` - fail if multiple targets have the same output.
|
|||
|
|
* `ninja -d keeprsp` - prevent ninja from deleting response files.
|
|||
|
|
* `ninja -n -d explain` - print why ninja thinks a target is dirty.
|
|||
|
|
* `ninja -j1` - execute only one command at a time.
|