| --- |
| layout: documentation |
| title: Configurable Build Attributes |
| --- |
| |
| # Configurable Build Attributes |
| |
| ### Contents |
| * [Example](#example) |
| * [Configuration Conditions](#configuration-conditions) |
| * [Defaults](#defaults) |
| * [Custom Keys](#custom-keys) |
| * [Platforms](#platforms) |
| * [Short Keys](#short-keys) |
| * [Multiple Selects](#multiple-selects) |
| * [OR Chaining](#or-chaining) |
| * [Custom Error Messages](#custom-error-messages) |
| * [Starlark Compatibility](#starlark) |
| * [Bazel Query and Cquery](#query) |
| * [FAQ](#faq) |
| * [Why doesn't select() work in macros](#starlark-macros-select) |
| * [Why does select() always return true?](#boolean-select) |
| * [Can I read select() like a dict?](#inspectable-select) |
| |
| |
| |
| **_Configurable attributes_**, commonly known as [`select()`] |
| (be/functions.html#select), is a Bazel feature that lets users toggle the values |
| of BUILD rule attributes at the command line. |
| |
| This can be used, for example, to write multiplatform libraries that |
| automatically choose the right implementation or feature-configurable binaries |
| that can be custom assembled at build time. |
| |
| ## Example |
| |
| ```sh |
| //myapp/BUILD: |
| cc_binary( |
| name = "mybinary", |
| srcs = ["main.cc"], |
| deps = select({ |
| ":arm_build": [":arm_lib"], |
| ":x86_debug_build": [":x86_dev_lib"], |
| "//conditions:default": [":generic_lib"] |
| }) |
| ) |
| |
| config_setting( |
| name = "arm_build", |
| values = { "cpu": "arm" } |
| ) |
| |
| config_setting( |
| name = "x86_debug_build", |
| values = { |
| "cpu": "x86", |
| "compilation_mode": "dbg" |
| } |
| ) |
| ``` |
| |
| This declares a `cc_binary` that "chooses" its deps based on the flags at the |
| command line. Specficially, `deps` becomes: |
| |
| <table> |
| <tr style="background: #E9E9E9; font-weight: bold"> |
| <td>Command</td> |
| <td>deps =</td> |
| </tr> |
| <tr> |
| <td><code>bazel build //myapp:mybinary --cpu=arm</code></td> |
| <td><code>[":arm_lib"]</code></td> |
| </tr> |
| <tr> |
| <td><code>bazel build //myapp:mybinary --c dbg --cpu=x86</code></td> |
| <td><code>[":x86_dev_lib"]</code></td> |
| </tr> |
| <tr> |
| <td><code>bazel build //myapp:mybinary --cpu=ppc</code></td> |
| <td><code>[":generic_lib"]</code></td> |
| </tr> |
| <tr> |
| <td><code>bazel build //myapp:mybinary -c dbg --cpu=ppc</code></td> |
| <td><code>[":generic_lib"]</code></td> |
| </tr> |
| </table> |
| |
| |
| `select()` turns any attribute into a dictionary that maps _configuration |
| conditions_ to desired values. Configuration conditions are build labels that |
| reference [`config_setting`](be/general.html#config_setting) rules. Values are |
| any value the attribute can normally take. |
| |
| Matches must be unambiguous: either exactly one condition must match or, if |
| multiple conditions match, one's `values` must be a strict superset of all |
| others' (for example, `values = {"cpu": "x86", "compilation_mode": "dbg"}` is an |
| unambiguous specialization of `values = {"cpu": "x86"}`). The built-in condition |
| [`//conditions:default`](#defaults) automatically matches when nothing else |
| does. |
| |
| This example uses `deps`. But `select()` works just as well on `srcs`, |
| `resources`, `cmd`, or practically any other attribute. Only a small number of |
| attributes are *non-configurable*, and those are clearly [annotated] |
| (be/general.html#config_setting.values). |
| |
| ## Configuration Conditions |
| |
| Each key in a configurable attribute is a label reference to a |
| [`config_setting`](be/general.html#config_setting) rule. This is just a |
| collection of expected command line flag settings. By encapsulating these in a |
| rule, it's easy to maintain "standard" conditions that can be referenced across |
| rules and BUILD files. |
| |
| The core `config_setting` syntax is: |
| |
| ```sh |
| config_setting( |
| name = "meaningful_condition_name", |
| values = { |
| "flag1": "expected_value1", |
| "flag2": "expected_value2", |
| ... |
| } |
| ) |
| ``` |
| |
| `flagN` is an arbitrary Bazel command line flag. `value` is the expected value |
| for that flag. A `config_setting` matches when *all* of its flags match. |
| |
| `values` entries use the same parsing logic as at the actual command line. This |
| means: |
| |
| * `values = { "compilation_mode": "opt" }` matches `bazel build -c opt ...` |
| * `values = { "java_header_compilation": "true" }` matches `bazel build |
| --java_header_compilation=1 ...` |
| * `values = { "java_header_compilation": "0" }` matches `bazel build |
| --nojava_header_compilation ...` |
| |
| `config_setting` only works with flags that affect build rule output. For |
| example, [`--show_progress`](user-manual.html#flag--show_progress) isn't allowed |
| because this only affects how Bazel reports progress to the user. |
| |
| `config_setting` semantics are intentionally simple. For example, there's no |
| direct support for `OR` chaining (although a |
| [convenience function](#or-chaining) provides this). Consider writing |
| macros for complicated flag logic. |
| |
| ## Defaults |
| |
| The built-in condition `//conditions:default` matches when no other condition |
| matches. |
| |
| Because of the "exactly one match" rule, a configurable attribute with no match |
| and no default condition triggers a `"no matching conditions"` error. This can |
| protect against silent failures from unexpected build flags: |
| |
| ```sh |
| //foo: |
| config_setting( |
| name = "foobar", |
| values = { "define": "foo=bar" } |
| ) |
| |
| cc_library( |
| name = "my_lib", |
| srcs = select({ |
| ":foobar": ["foobar_lib.cc"], |
| }) |
| ) |
| ``` |
| |
| ```sh |
| $ bazel build //foo:my_lib --define foo=baz |
| ERROR: Configurable attribute "srcs" doesn't match this configuration (would |
| a default condition help?). |
| Conditions checked: |
| //foo:foobar |
| ``` |
| |
| `select()` can include a [`no_match_error`](#custom-error-messages) for custom |
| failure messages. |
| |
| ## Custom Keys |
| |
| Since `config_setting` currently only supports built-in Bazel flags, the level |
| of custom conditioning it can support is limited. For example, there's no Bazel |
| flag for `IncludeSpecialProjectFeatureX`. |
| |
| Plans for [truly custom flags] |
| (https://docs.google.com/document/d/1vc8v-kXjvgZOdQdnxPTaV0rrLxtP2XwnD2tAZlYJOqw/edit?usp=sharing) |
| are underway. In the meantime, [`--define`](user-manual.html#flag--define) is |
| the best approach for these purposes. |
| `--define` is a bit awkward to use and wasn't originally designed for this |
| purpose. We recommend using it sparingly until true custom flags are available. |
| For example, don't use `--define` to specify multiple variants of top-level |
| binary. Just use multiple rules instead. |
| |
| To trigger an arbitrary condition with `--define`, write |
| |
| ```sh |
| config_setting( |
| name = "bar", |
| values = { "define": "foo=bar" } |
| ) |
| |
| config_setting( |
| name = "baz", |
| values = { "define": "foo=baz" } |
| ) |
| ``` |
| |
| and run `$ bazel build //my:target --define foo=baz`. |
| |
| The `values` attribute can't contain multiple `define`s. This is |
| because each instance has the same dictionary key. To solve this, use |
| `define_values`: |
| |
| ```sh |
| config_setting( |
| name = "bar_and_baz", |
| define_values = { |
| "foo": "bar", # matches --define foo=bar |
| "baz": "bat", # matches --define baz=bat |
| } |
| ) |
| ``` |
| |
| When `define`s appear in both `values` and `define_values`, all must match for |
| the `config_setting` to match. |
| |
| ## Platforms |
| |
| While the ability to specify multiple flags on the command line provides |
| flexibility, it can also be burdensome to individually set each `--cpu`, |
| `-crosstool_top`, etc. flag every time you want to build a target. [Platforms] |
| (https://docs.bazel.build/versions/master/platforms.html) allow you to |
| consolidate these into simple bundles. |
| |
| ```sh |
| sh_binary( |
| name = "my_rocks_rule", |
| srcs = select({ |
| ":basalt" : ["pyroxene.sh"], |
| ":marble" : ["calcite.sh"], |
| "//conditions:default": ["feldspar.sh"] |
| }) |
| ) |
| |
| config_setting( |
| name = "basalt", |
| constraint_values = [ |
| ":igneous", |
| ":black" |
| ] |
| ) |
| |
| config_setting( |
| name = "marble", |
| constraint_values = [ |
| ":white", |
| ":metamorphic" |
| ":smooth" |
| ] |
| ) |
| |
| constraint_setting(name = "color") |
| constraint_value(name = "black", constraint_setting = "color") |
| constraint_value(name = "white", constraint_setting = "color") |
| constraint_setting(name = "texture") |
| constraint_value(name = "smooth", constraint_setting = "texture") |
| constraint_setting(name = "type") |
| constraint_value(name = "igneous", constraint_setting = "type") |
| constraint_value(name = "metamorphic", constraint_setting = "type") |
| |
| platform( |
| name = "basalt_platform", |
| constraint_values = [ |
| ":black", |
| ":igneous"] |
| ) |
| |
| platform( |
| name = "marble_platform", |
| constraint_values = [ |
| ":white", |
| ":smooth" |
| ":metamorphic" |
| ] |
| ) |
| ``` |
| |
| The `platform` specified on the command line matches a `config_setting` that |
| contains the same set (or a superset) of `constraint_values` and triggers |
| that `config_setting` as a match in the `select()` statement. |
| |
| For example, in order to set the `srcs` attribute of `my_rocks_rule` to |
| `calcite.sh`, simply run |
| |
| ```sh |
| bazel build my_app:my_rocks_rule --platforms=marble_platform |
| ``` |
| |
| Without platforms, this might look something like |
| |
| ```sh |
| bazel build my_app:my_rocks_rule --define color=light --define texture=smooth --define type=metamorphic |
| ``` |
| Platforms are still under development. See the [documentation] |
| (https://docs.bazel.build/versions/master/platforms.html) and [roadmap] |
| (https://docs.google.com/document/d/1_clxJHyUylwYjmQ9jQfWr-eZeLLTUZL6onfvPV7CMoI/edit?usp=sharing) |
| for details. |
| |
| ## Short Keys |
| |
| Since configuration keys are rule labels, their names can get long and unwieldy. |
| This can be mitigated with local variable definitions: |
| |
| Before: |
| |
| ```sh |
| sh_binary( |
| name = "my_rule", |
| srcs = select({ |
| "//my/project/my/team/configs:config1": ["my_rule_1.sh"], |
| "//my/project/my/team/configs:config2": ["my_rule_2.sh"], |
| }) |
| ) |
| ``` |
| |
| After: |
| |
| ```sh |
| CONFIG1="//my/project/my/team/configs:config1" |
| CONFIG2="//my/project/my/team/configs:config2" |
| |
| sh_binary( |
| name = "my_rule", |
| srcs = select({ |
| CONFIG1: ["my_rule_1.sh"], |
| CONFIG2: ["my_rule_2.sh"], |
| }) |
| ) |
| ``` |
| |
| |
| For more complex expressions, use [macros](skylark/macros.md): |
| |
| Before: |
| |
| ```sh |
| //foo/BUILD |
| genrule( |
| name = "my_rule", |
| srcs = [], |
| outs = ["my_rule.out"], |
| cmd = select({ |
| "//my/project/my/team/configs/config1": "echo custom val: this > $@", |
| "//my/project/my/team/configs/config2": "echo custom val: that > $@", |
| "//conditions:default": "echo default output > $@" |
| }) |
| ) |
| ``` |
| |
| After: |
| |
| ```sh |
| //foo/genrule_select.bzl: |
| def select_echo(input_dict): |
| echo_cmd = "echo %s > $@" |
| out_dict = {"//conditions:default": echo_cmd % "default output" } |
| for (key, val) in input_dict.items(): |
| cmd = echo_cmd % ("custom val: " + val) |
| out_dict["//my/project/my/team/configs/config" + key] = cmd |
| return select(out_dict) |
| ``` |
| |
| ```sh |
| //foo/BUILD: |
| load("//foo:genrule_select.bzl", "select_echo") |
| genrule( |
| name = "my_rule", |
| srcs = [], |
| outs = ["my_rule.out"], |
| cmd = select_echo({ |
| "1": "this", |
| "2": "that", |
| }) |
| ) |
| ``` |
| |
| ## Multiple Selects |
| |
| `select` can appear multiple times in the same attribute: |
| |
| ```sh |
| sh_binary( |
| name = "my_rule", |
| srcs = ["always_include.sh"] |
| + select({ |
| ":armeabi_mode": ["armeabi_src.sh"], |
| ":x86_mode": ["x86_src.sh"], |
| }) |
| + select({ |
| ":opt_mode": ["opt_extras.sh"], |
| ":dbg_mode": ["dbg_extras.sh"], |
| }) |
| ) |
| ``` |
| |
| `select` cannot appear inside another `select` (i.e. *`AND` chaining*). If you |
| need to `AND` selects together, either use an intermediate rule: |
| |
| ```sh |
| sh_binary |
| name = "my_rule", |
| srcs = ["always_include.sh"], |
| deps = select({ |
| ":armeabi_mode": [":armeabi_lib"], |
| ... |
| }) |
| ) |
| |
| sh_library( |
| name = "armeabi_lib", |
| srcs = select({ |
| ":opt_mode": ["armeabi_with_opt.sh"], |
| ... |
| }) |
| ) |
| ``` |
| |
| or write a [macro](skylark/macros.md) to do the same thing |
| automatically. |
| |
| This approach doesn't work for non-deps attributes (like |
| [genrule:cmd](be/general.html#genrule.cmd)). For these, extra `config_settings` |
| may be necessary: |
| |
| ```sh |
| config_setting( |
| name = "armeabi_and_opt", |
| values = { |
| "cpu": "armeabi", |
| "compilation_mode": "opt" |
| } |
| ) |
| ``` |
| |
| |
| ## OR Chaining |
| |
| Consider the following: |
| |
| ```sh |
| sh_binary |
| name = "my_rule", |
| srcs = ["always_include.sh"], |
| deps = select({ |
| ":config1": [":standard_lib"], |
| ":config2": [":standard_lib"], |
| ":config3": [":standard_lib"], |
| ":config4": [":special_lib"], |
| }) |
| ) |
| ``` |
| |
| Most conditions evaluate to the same dep. But this syntax is verbose, hard to |
| maintain, and refactoring-unfriendly. It would be nice to not have to repeat |
| `[":standard_lib"]` over and over. |
| |
| One option is to predefine the declaration as a BUILD variable: |
| |
| ```python |
| STANDARD_DEP = [":standard_lib"] |
| |
| sh_binary |
| name = "my_rule", |
| srcs = ["always_include.sh"], |
| deps = select({ |
| ":config1": STANDARD_DEP, |
| ":config2": STANDARD_DEP, |
| ":config3": STANDARD_DEP, |
| ":config4": [":special_lib"], |
| }) |
| ) |
| ``` |
| |
| This makes it easier to manage the dependency. But it still adds unnecessary |
| duplication. |
| |
| `select()` doesn't support native syntax for `OR`ed conditions. For this, use |
| the [Skylib](https://github.com/bazelbuild/bazel-skylib) utility [`selects`] |
| (https://github.com/bazelbuild/bazel-skylib/blob/master/lib/selects.bzl). |
| |
| ```sh |
| load("@bazel_skylib//:lib.bzl", "selects") |
| ``` |
| |
| ```sh |
| sh_binary |
| name = "my_rule", |
| srcs = ["always_include.sh"], |
| deps = selects.with_or({ |
| (":config1", ":config2", ":config3"): [":standard_lib"], |
| ":config4": [":special_lib"], |
| }) |
| ) |
| ``` |
| |
| This automatically expands the `select` to the original syntax above. |
| |
| For `AND` chaining, see [here](#multiple-selects). |
| |
| ## Custom Error Messages |
| |
| By default, when no condition matches, the owning rule fails with the error: |
| |
| ```sh |
| ERROR: Configurable attribute "deps" doesn't match this configuration (would |
| a default condition help?). |
| Conditions checked: |
| //tools/cc_target_os:darwin |
| //tools/cc_target_os:android |
| ``` |
| |
| This can be customized with [`no_match_error`](be/functions.html#select): |
| |
| ```sh |
| cc_library( |
| name = "my_lib", |
| deps = select({ |
| "//tools/cc_target_os:android": [":android_deps"], |
| "//tools/cc_target_os:windows": [":windows_deps"], |
| }, no_match_error = "Please build with an Android or Windows toolchain" |
| ) |
| ) |
| ``` |
| |
| ```sh |
| $ bazel build //foo:my_lib |
| ERROR: Configurable attribute "deps" doesn't match this configuration: Please |
| build with an Android or Windows toolchain |
| ``` |
| |
| ## <a name="starlark"></a>Starlark Compatibility |
| Starlark is compatible with configurable attributes in limited form. |
| |
| Rule implementations receive the *resolved values* of configurable |
| attributes. For example, given: |
| |
| ```sh |
| //myproject/BUILD: |
| some_rule( |
| name = "my_rule", |
| some_attr = select({ |
| ":foo_mode": [":foo"], |
| ":bar_mode": [":bar"], |
| }) |
| ) |
| ``` |
| |
| ```sh |
| $ bazel build //myproject/my_rule --define mode=foo |
| ``` |
| |
| Rule implementation code sees `ctx.attr.some_attr` as `[":foo"]`. |
| |
| Macros can accept `select()` clauses and pass them through to native |
| rules. But *they cannot directly manipulate them*. For example, there's no way |
| for a macro to convert |
| |
| ```sh |
| `select({"foo": "val"}, ...)` |
| ``` |
| |
| to |
| |
| ```sh |
| `select({"foo": "val_with_suffix"}, ...)`. |
| ``` |
| |
| This is for two reasons. |
| |
| First, macros that need to know which path a `select` will choose *cannot work* |
| because macros are evaluated in Bazel's [loading phase] |
| (user-manual.html#loading-phase), which occurs before flag values are known. |
| This is a core Bazel design restriction that's unlikely to change any time soon. |
| |
| Second, macros that just need to iterate over *all* `select` paths, while |
| technically feasible, lack a coherent UI. Further design is necessary to change |
| this. |
| |
| ## <a name="query"></a>Bazel Query and Cquery |
| Bazel `query` operates over Bazel's [loading phase] |
| (user-manual.html#loading-phase). This means it doesn't know what command line |
| flags will be applied to a rule since those flags aren't evaluated until later |
| in the build (during the [analysis phase](user-manual.html#analysis-phase)). So |
| the [`query`](query.html) command can't accurately determine which path a |
| configurable attribute will follow. |
| |
| [Bazel `cquery`](cquery.html) has the advantage of being able to parse build |
| flags and operating post-analysis phase so it correctly resolves configurable |
| attributes. It doesn't have full feature parity with query but supports most |
| major functionality and is actively being worked on. |
| Querying the following build file... |
| |
| ```sh |
| //myproject/BUILD: |
| cc_library( |
| name = "my_lib", |
| deps = select({ |
| ":long": [":foo_dep"], |
| ":short": [":bar_dep"], |
| }) |
| ) |
| config_setting( |
| name = 'long', |
| values = { "define": "dog=dachshund" } |
| ) |
| config_setting( |
| name = 'short', |
| values = { "define": "dog=pug" } |
| ) |
| ``` |
| ...would return the following results. |
| |
| ```sh |
| $ bazel query 'deps(//myproject:my_lib)' |
| //myproject:my_lib |
| //myproject:foo_dep |
| //myproject:bar_dep |
| |
| $ bazel cquery 'deps(//myproject:my_lib)' --define dog=pug |
| //myproject:my_lib |
| //myproject:bar_dep |
| ``` |
| |
| ## FAQ |
| |
| ## <a name="macros-select"></a>Why doesn't select() work in macros? |
| select() *does* work in rules! See [Starlark compatibility](#starlark) for |
| details. |
| |
| The key issue this question usually means is that select() doesn't work in |
| *macros*. These are different than *rules*. See the |
| documentation on [rules](skylark/rules.html) and [macros](skylark/macros.html) |
| to understand the difference. |
| Here's an end-to-end example: |
| |
| Define a rule and macro: |
| |
| ```sh |
| # myproject/defs.bzl: |
| |
| # Rule implementation: when an attribute is read, all select()s have already |
| # been resolved. So it looks like a plain old attribute just like any other. |
| def _impl(ctx): |
| name = ctx.attr.name |
| allcaps = ctx.attr.my_config_string.upper() # This works fine on all values. |
| print("My name is " + name + " with custom message: " + allcaps) |
| |
| # Rule declaration: |
| my_custom_bazel_rule = rule( |
| implementation = _impl, |
| attrs = {"my_config_string": attr.string()} |
| ) |
| |
| # Macro declaration: |
| def my_custom_bazel_macro(name, my_config_string): |
| allcaps = my_config_string.upper() # This line won't work with select(s). |
| print("My name is " + name + " with custom message: " + allcaps) |
| ``` |
| |
| Instantiate the rule and macro: |
| |
| ```sh |
| # myproject/BUILD: |
| load("//myproject:defx.bzl", "my_custom_bazel_rule") |
| load("//myproject:defs.bzl", "my_custom_bazel_macro") |
| |
| my_custom_bazel_rule( |
| name = "happy_rule", |
| my_config_string = select({ |
| "//tools/target_cpu:x86": "first string", |
| "//tools/target_cpu:ppc": "second string", |
| }), |
| ) |
| |
| my_custom_bazel_macro( |
| name = "happy_macro", |
| my_config_string = "fixed string", |
| ) |
| |
| my_custom_bazel_macro( |
| name = "sad_macro", |
| my_config_string = select({ |
| "//tools/target_cpu:x86": "first string", |
| "//tools/target_cpu:ppc": "other string", |
| }), |
| ) |
| ``` |
| |
| Building fails because `sad_macro` can't process the `select()`: |
| |
| ```sh |
| $ bazel build //myproject:all |
| ERROR: /myworkspace/myproject/BUILD:17:1: Traceback |
| (most recent call last): |
| File "/myworkspace/myproject/BUILD", line 17 |
| my_custom_bazel_macro(name = "sad_macro", my_config_stri..."})) |
| File "/myworkspace/myproject/defs.bzl", line 4, in |
| my_custom_bazel_macro |
| my_config_string.upper() |
| type 'select' has no method upper(). |
| ERROR: error loading package 'myproject': Package 'myproject' contains errors. |
| ``` |
| |
| Building succeeds when we comment out `sad_macro`: |
| |
| ```sh |
| # Comment out sad_macro so it doesn't mess up the build. |
| $ bazel build //myproject:all |
| DEBUG: /myworkspace/myproject/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING. |
| DEBUG: /myworkspace/myproject/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING. |
| ``` |
| |
| This is impossible to change because *by definition* macros are evaluated before |
| Bazel reads the build's command line flags. That means there isn't enough |
| information to evaluate select()s. |
| |
| Macros can, however, pass `select()`s as opaque blobs to rules: |
| |
| ```sh |
| # myproject/defs.bzl: |
| def my_custom_bazel_macro(name, my_config_string): |
| print("Invoking macro " + name) |
| my_custom_bazel_rule( |
| name = name + "_as_rule", |
| my_config_string = my_config_string) |
| ``` |
| |
| ```sh |
| $ bazel build //myproject:sad_macro_less_sad |
| DEBUG: /myworkspace/myproject/defs.bzl:23:3: Invoking macro sad_macro_less_sad. |
| DEBUG: /myworkspace/myproject/defs.bzl:15:3: My name is sad_macro_less_sad with custom message: FIRST STRING. |
| ``` |
| |
| ## <a name="boolean-select"></a>Why does select() always return true? |
| Because *macros* (but not rules) by definition |
| [can't evaluate select(s)](#macros-select), any attempt to do so |
| usually produces a an error: |
| |
| ```sh |
| ERROR: /myworkspace/myproject/BUILD:17:1: Traceback |
| (most recent call last): |
| File "/myworkspace/myproject/BUILD", line 17 |
| my_custom_bazel_macro(name = "sad_macro", my_config_stri..."})) |
| File "/myworkspace/myproject/defs.bzl", line 4, in |
| my_custom_bazel_macro |
| my_config_string.upper() |
| type 'select' has no method upper(). |
| ``` |
| |
| Booleans are a special case that fail silently, so you should be particularly |
| vigilant with them: |
| |
| ```sh |
| $ cat myproject/defs.bzl: |
| def my_boolean_macro(boolval): |
| print("TRUE" if boolval else "FALSE") |
| |
| $ cat myproject/BUILD: |
| load("//myproject:defx.bzl", "my_boolean_macro") |
| my_boolean_macro( |
| boolval = select({ |
| "//tools/target_cpu:x86": True, |
| "//tools/target_cpu:ppc": False, |
| }), |
| ) |
| |
| $ bazel build //myproject:all --cpu=x86 |
| DEBUG: /myworkspace/myproject/defs.bzl:4:3: TRUE. |
| $ bazel build //myproject:all --cpu=ppc |
| DEBUG: /myworkspace/myproject/defs.bzl:4:3: TRUE. |
| ``` |
| |
| This happens because macros don't understand the contents of `select()`. |
| So what they're really evaluting is the `select()` object itself. According to |
| [Pythonic](https://docs.python.org/release/2.5.2/lib/truth.html) design |
| standards, all objects aside from a very small number of exceptions |
| automatically return true. |
| ## <a name="inspectable-select"></a>Can I read select() like a dict? |
| Fine. Macros [can't](#macros-select) evaluate select(s) because |
| macros are evaluated before Bazel knows what the command line flags are. |
| |
| Can macros at least read the `select()`'s dictionary, say, to add an extra |
| suffix to each branch? |
| |
| Conceptually this is possible. But this isn't yet implemented and is not |
| currently prioritized. |
| What you *can* do today is prepare a straight dictionary, then feed it into a |
| `select()`: |
| |
| ```sh |
| $ cat myproject/defs.bzl |
| def selecty_genrule(name, select_cmd): |
| for key in select_cmd.keys(): |
| select_cmd[key] += " WITH SUFFIX" |
| native.genrule( |
| name = name, |
| outs = [name + ".out"], |
| srcs = [], |
| cmd = "echo " + select(select_cmd + {"//conditions:default": "default"}) |
| + " > $@" |
| ) |
| |
| $ cat myproject/BUILD |
| selecty_genrule( |
| name = "selecty", |
| select_cmd = { |
| "//tools/target_cpu:x86": "x86 mode", |
| }, |
| ) |
| |
| $ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out |
| x86 mode WITH SUFFIX |
| ``` |
| |
| If you'd like to support both `select()` and native types, you can do this: |
| |
| ```sh |
| $ cat myproject/defs.bzl |
| def selecty_genrule(name, select_cmd): |
| cmd_suffix = "" |
| if type(select_cmd) == "string": |
| cmd_suffix = select_cmd + " WITH SUFFIX" |
| elif type(select_cmd) == "dict": |
| for key in select_cmd.keys(): |
| select_cmd[key] += " WITH SUFFIX" |
| cmd_suffix = select(select_cmd + {"//conditions:default": "default"}) |
| |
| native.genrule( |
| name = name, |
| outs = [name + ".out"], |
| srcs = [], |
| cmd = "echo " + cmd_suffix + "> $@") |
| ``` |