mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2025-04-12 21:51:01 +00:00
351 lines
12 KiB
Markdown
351 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.
|