| # Best practices for writing GN templates |
| |
| ## Overview {#overview} |
| |
| In GN, templates provide a way to add on to GN’s built-in target types. Basically, |
| templates are GN’s 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, it’s 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 what’s 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 template’s 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 people’s builds and tests to test your template. |
| Having your own tests makes your template more maintainable, since it’s faster |
| to validate future changes to your template and it’s 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 they’re defined. |
| |
| If a user forgets to specify a required parameter, and there’s no assert defined, |
| they won’t 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 doesn’t forward `testonly` to inner targets then: |
| |
| 1. Your inner targets might fail to build, because your users might pass you `testonly` dependencies. |
| 2. You’ll 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, "*")` won’t 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 can’t 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 don’t 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 won’t always be able to use the name that they |
| want both for the target and the output. |
| |
| It’s 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 you’ll 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} |
| |
| It’s 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" |
| } |
| ``` |
| |
| Here’s 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, it’s 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 won’t |
| 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 that’s |
| already in your scope - that’s 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. You’ll 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/+/HEAD/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. |