blob: 3e43115b129fa54f226ff15e6f49703beb179120 [file] [log] [blame] [view]
# Best practices for writing GN templates
## Overview {#overview}
In GN, templates provide a way to add on to GNs built-in target types. Basically,
templates are GNs primary way to build reusable functions. Template definitions go
in `.gni` (GN import) files that can be imported into target `.gn` files.
This document details the best practices for creating GN templates, and each best
practice includes an example. These best practices are in addition to the best
practices outlined in [Fuchsia build system policies](policies.md).
Run `gn help template` for more information and more complete examples, and
[GN Language and Operation](https://gn.googlesource.com/gn/+/HEAD/docs/language.md#templates)
for more information on GN features.
## Templates {#templates}
### Define templates in `.gni`, targets in `BUILD.gn` {#define-templates-in-gni-targets-in-build-gn}
Technically, its possible to import both `.gni` and `BUILD.gn` files. The best
practice, however, is to define templates in `.gni` files, and
targets in `.gn` files. This makes it clear to users whats a template. Users
want to import templates so they can use them, and never want to import targets.
### Document templates and args {#document-templates-and-args}
Document both your templates and args, including:
* A general explanation of the templates purpose and concepts introduced. A practical usage example is recommended.
* All parameters should be documented. Parameters that are common and simply forwarded (such as `deps` or `visibility`), where the meaning is consistent with their meaning on built-in GN rules, can be listed with no additional information.
* If a template generates `metadata,` then `data_keys` should be listed.
To document your template, insert a comment block in front of your template definition
to specify your public contract.
```gn
declare_args() {
# The amount of bytes to allocate when creating a disk image.
disk_image_size_bytes = 1024
}
# Defines a disk image file.
#
# Disk image files are used to boot the bar virtual machine.
#
# Example:
# ```
# disk_image("my_image") {
# sources = [ "boot.img", "kernel.img" ]
# sdk = false
# }
# ```
#
# Parameters
#
# sources (required)
# List of source files to include in the image.
# Type: list(path)
#
# sdk (optional)
# This image is exported to the SDK.
# Type: bool
# Default: false
#
# data_deps
# deps
# public_deps
# testonly
# visibility
#
# Metadata
#
# files
# Filenames present in this image.
template("disk_image") {
...
}
```
### Wrap tools with a single action template {#wrap-tools-with-a-single-action-template}
For every tool, have a canonical template that wraps it with an `action`.
This template’s job is to turn GN parameters into `args` for the tool, and
that’s it. This sets an encapsulation boundary around the tool for details
such as translating parameters to args.
Note that in this example we define the `executable()` in one file and the
`template()` in another, because
[templates and targets should be separated](#define-templates-in-gni-targets-in-build-gn).
```gn
# //src/developer_tools/BUILD.gn
executable("copy_to_target_bin") {
...
}
# //src/developer_tools/cli.gni
template("copy_to_target") {
compiled_action(target_name) {
forward_variables_from(invoker, [
"data_deps",
"deps",
"public_deps",
"testonly",
"visibility"
])
assert(defined(invoker.sources), "Must specify sources")
assert(defined(invoker.destinations), "Must specify destinations")
tool = "//src/developer_tools:copy_to_target_bin"
args = [ "--sources" ]
foreach(source, sources) {
args += [ rebase_path(source, root_build_dir) ]
}
args += [ "--destinations" ]
foreach(destination, destinations) {
args += [ rebase_path(destination, root_build_dir) ]
}
}
}
```
### Consider making templates private {#consider-making-templates-private}
Templates and variables whose name begins with an underscore (e.g. `template("_private")`)
are considered private and won’t be visible to other files that `import()` them, but can be
used in the same file that they’re defined. This is useful for internal helper templates or
“local global variables” that you might define for instance to share logic between two templates,
where the helper is not useful to the user.
```gn
template("coffee") {
# Take coffee parameters like roast and sugar
...
_beverage(target_name) {
# Express in beverage terms like ingredients and temperature
...
}
}
template("tea") {
# Take tea parameters like loose leaf and cream
...
_beverage(target_name) {
# Express in beverage terms like ingredients and temperature
...
}
}
# We don't want people directly defining new beverages.
# For instance they might add both sugar and salt to the ingredients list.
template("_beverage") {
...
}
```
Sometimes you can’t make a template private because it actually needs to be used
from different files, but you’d still like to hide it because it’s not meant to
be used directly. In situations like this you can swap enforcement for signaling, by
putting your template in a file under a path such as `//build/internal/`.
### Test your templates {#test-your-templates}
Write tests that use your templates to build, or use files generated by your
templates in the course of the test.
You should not rely on other peoples builds and tests to test your template.
Having your own tests makes your template more maintainable, since its faster
to validate future changes to your template and its easier to isolate faults.
```gn
# //src/drinks/coffee.gni
template("coffee") {
...
}
# //src/drinks/tests/BUILD.gni
coffee("coffee_for_test") {
...
}
test("coffee_test") {
sources = [ "taste_coffee.cc" ]
data_deps = [ ":coffee_for_test" ]
...
}
```
## Parameters {#parameters}
### Assert on required parameters {#assert-on-required-parameters}
If you have required parameters in your template, `assert` that theyre defined.
If a user forgets to specify a required parameter, and theres no assert defined,
they wont get a clear explanation for their error. Using an assert allows you to
provide a useful error message.
```gn
template("my_template") {
forward_variables_from(invoker, [ "sources", "testonly", "visibility" ])
assert(defined(sources),
"A `sources` argument was missing when calling my_template($target_name)")
}
template("my_other_template") {
forward_variables_from(invoker, [ "inputs", "testonly", "visibility" ])
assert(defined(inputs) && inputs != [],
"An `input` argument must be present and non-empty " +
"when calling my_template($target_name)")
}
```
### Always forward `testonly` {#always-forward-testonly}
Setting `testonly` on a target guards it against being used by non-test targets.
If your template doesnt forward `testonly` to inner targets then:
1. Your inner targets might fail to build, because your users might pass you `testonly` dependencies.
2. Youll surprise your users when they find that their `testonly` artifacts end up in production artifacts.
The following example shows how to forward `testonly`:
```gn
template("my_template") {
action(target_name) {
forward_variables_from(invoker, [ "testonly", "deps" ])
...
}
}
my_template("my_target") {
visibility = [ ... ]
testonly = true
...
}
```
Note that if the parent scope for the inner action defines `testonly`
then `forward_variables_from(invoker, "*")` wont forward it, as it
avoids clobbering variables. Here are some patterns to work around this:
```gn
# Broken, doesn't forward `testonly`
template("my_template") {
testonly = ...
action(target_name) {
forward_variables_from(invoker, "*")
...
}
}
# Works
template("my_template") {
testonly = ...
action(target_name) {
forward_variables_from(invoker, "*")
testonly = testonly
...
}
}
# Works
template("my_template") {
testonly = ...
action(target_name) {
forward_variables_from(invoker, "*", [ "testonly" ])
forward_variables_from(invoker, [ "testonly" ])
...
}
}
```
The one exception to this are templates that hard-code `testonly = true` because
they should never be used in production targets. For example:
```gn
template("a_test_template") {
testonly = true
...
}
```
### Forward `visibility` to the main target and hide inner targets {#forward-visibility-to-the-main-target-hide-inner-targets}
GN users expect to be able to set `visibility` on any target.
This advice is similar to [always forward testonly](#heading=h.fk6w1as9tkpx), except that
it only applies to the main target (the target named `target_name`). Other targets should
have their `visibility` restricted, so that your users cant depend on your inner targets
that are not part of your contract.
```gn
template("my_template") {
action("${target_name}_helper") {
forward_variables_from(invoker, [ "testonly", "deps" ])
visibility = [ ":*" ]
...
}
action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
deps = [ ":${target_name}_helper" ]
...
}
}
```
### If forwarding `deps`, also forward `public_deps` and `data_deps` {#if-forwarding-deps-also-forward-public_deps-and-data_deps}
All built-in rules that take `deps` take `public_deps` and `data_deps`.
Some built-in rules dont differentiate between types of deps (e.g. `action()`
treats `deps` and `public_deps` equally). But dependants on your generated
targets might (e.g. an `executable()` that deps on your generated `action()`
treats transitive `deps` and `public_deps` differently).
```gn
template("my_template") {
action(target_name) {
forward_variables_from(invoker, [
"data_deps",
"deps",
"public_deps",
"testonly",
"Visibility"
])
...
}
}
```
## Target Names {#target-names}
### Define an inner target named `target_name` {#define-an-inner-target-named-target_name}
Your template should define at least one target that is named `target_name`.
This allows your users to invoke your template with a name, and then use that
name in their deps.
```gn
# //build/image.gni
template("image") {
action(target_name) {
...
}
}
# //src/some/project/BUILD.gn
import("//build/image.gni")
image("my_image") {
...
}
group("images") {
deps = [ ":my_image", ... ]
}
```
### `target_name` is a good default for an output name, but offer an override {#target_name-is-a-good-default-for-an-output-name-but-offer-an-override}
If your template produces a single output then using the target name to select
the output name is good default behavior. However, target names must be unique
in a directory, so your users wont always be able to use the name that they
want both for the target and the output.
Its a good best practice to offer users an override:
```gn
template("image") {
forward_variables_from(invoker, [ "output_name", ... ])
if (!defined(output_name)) {
output_name = target_name
}
...
}
```
### Prefix internal target names with `$target_name` {#prefix-internal-target-names-with-$target_name}
GN labels must be unique, or else youll get a gen-time error. If everyone on
the same project follows the same naming convention then collisions are less
likely to happen and it becomes easier to associate internal target names
with the targets that created them.
```gn
template("boot_image") {
generate_boot_manifest_action = "${target_name}_generate_boot_manifest"
action(generate_boot_manifest_action) {
...
}
image(target_name) {
...
deps += [ ":$generate_boot_manifest_action" ]
}
}
```
### Do not infer output names from target labels {#do-not-infer-output-names-from-target-labels}
Its tempting to assume a relationship between target names and output names.
For instance, the following example will work:
```gn
executable("bin") {
...
}
template("bin_runner") {
compiled_action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
assert(defined(invoker.bin), "Must specify bin")
deps = [ invoker.bin ]
tool = root_out_dir + "/" + get_label_info(invoker.foo, "name")
...
}
}
bin_runner("this_will_work") {
bin = ":bin"
}
```
However this example will product a gen-time error:
```gn
executable("bin") {
output_name = "my_binary"
...
}
template("bin_runner") {
compiled_action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
assert(defined(invoker.bin), "Must specify bin")
tool = root_out_dir + "/" + get_label_info(invoker.bin, "name")
...
}
}
# This will produce a gen-time error saying that a file ".../bin" is needed
# by ":this_will_fail" with no rule to generate it.
bin_runner("this_will_fail") {
bin = ":bin"
}
```
Heres one way of fixing this problem:
```gn
executable("bin") {
output_name = "my_binary"
...
}
template("bin_runner") {
compiled_action(target_name) {
forward_variables_from(invoker, [ "testonly", "visibility" ])
assert(defined(invoker.bin), "Must specify bin")
tool = bin
...
}
}
bin_runner("this_will_work") {
bin = "$root_out_dir/my_binary"
}
```
## GN functions and generation {#gn-functions-and-generation}
### Only use `read_file()` with source files {#only-use-read_file-with-source-files}
`read_file()` occurs during generation and can not be safely used to read from generated
files or build outputs. It can be used to read source files, for example to read
a manifest file or a json file with which to populate build dependencies.
Notably `read_file()` can not be used with `generated_file()` or `write_file()`.
### Prefer `generated_file()` over `write_file()` {#prefer-generated_file-over-write_file}
In general, its recommended that you use `generated_file()` over `write_file()`.
`generated_file()` provides additional features and addresses some of the challenges
of `write_file()`. For instance, `generated_file()` can be executed in parallel,
while `write_file()` is done serially at gen time.
The structure of both commands is very similar. For instance, you can turn
this instance of `write_file()`:
```gn
write_file("my_file", "My file contents")
```
Into this instance of `generated_file()`:
```gn
generated_file("my_file") {
outputs = [ "my_file" ]
contents = "My file contents"
}
```
## Patterns and anti-patterns {#patterns-and-anti-patterns}
### Target outputs {#target-outputs}
When working with `get_target_outputs()` to extract a single element, GN wont
let you subscript a list before assignment. To work around this issue,
you can use the less than elegant workaround below:
```gn
# Appending to a list is elegant
deps += get_target_outputs(":some_target")
# Extracting a single element to use in variable substitution - ugly but reliable
_outputs = get_target_outputs(":other_target")
output = _outputs[0]
message = "My favorite output is $output"
# This expression is invalid: `output = get_target_outputs(":other_target")[0]`
# GN won't let you subscript an rvalue.
```
### Set operations {#set-operations}
GN offers lists and scopes as aggregate data types, but not associative
types like maps or sets. Sometimes lists are used instead of sets. The
example below has a list of build variants, and checks if one of them
is the profile variant:
```gn
if (variants + [ "profile" ] - [ "profile" ] != variants) {
# Do something special for profile builds
...
}
```
This is an anti-pattern. Rather, variants could be defined as follows:
```gn
variants = {
profile = true
asan = false
...
}
if (variants.profile) {
# Do something special for profile builds
...
}
```
### Forwarding `"*"` {#forwarding-*}
`forward_variables_from()` copies specified variables to the current
scope from the given scope _or any enclosing scope_. Unless you
specify `"*"`, in which case it will only directly copy variables
from the given scope. And it will never clobber a variable thats
already in your scope - thats a gen-time error.
Sometimes you want to copy everything from the invoker, except for
a particular variable that you want to copy from any enclosing
scope. Youll encounter this pattern:
```gn
forward_variables_from(invoker, "*", [ "visibility" ])
forward_variables_from(invoker, [ "visibility" ])
```
### `exec_script()` {#exec-script}
GN's built-in function
[exec_script](https://gn.googlesource.com/gn/+/master/docs/reference.md#func_exec_script)
is a powerful tool for augmenting GN's abilities. Like `action()`,
`exec_script()` can invoke an external tool. Unlike `action()`, `exec_script()`
can invoke the tool **synchronously** with build generation, meaning that you
can use the output of the tool in your `BUILD.gn` logic.
Since this creates a performance bottleneck in gen time (i.e. `fx set` takes
longer), this feature must be used with care.
For more information, refer to
[this writeup](https://chromium.googlesource.com/chromium/src/+/ab1c69b1814d3c905fdab7b0d177b478eecf40a3/.gn#291)
by the Chromium team.
An allowlist has been set up in `//.gn`. Please consult `OWNERS` for changes
made to this allowlist.