Often we want to allow users of our Bazel rules to enable or disable functionality on an as-needed basis. While Bazel offers several mechanisms for this, features are the simplest—and probably the right—approach for the majority of cases.
Using features
Features can be enabled in two ways:
- On the command line:
--features=my_feature - As a rule attribute:
features = ["my_feature"]
NOTE: The command-line approach is cumulative, meaning multiple features can be enabled by repeating the --features flag with different values.
Reading features from rules
Say we have the following rule that writes a name to a file:
def _hello_impl(ctx):
content = ctx.attr.first_name
out = ctx.actions.declare_file(ctx.label.name + ".txt")
ctx.actions.write(
output = out,
content = content,
)
return DefaultInfo(files = depset([out]))
hello = rule(
implementation = _hello_impl,
attrs = {
"first_name": attr.string(),
},
)
Now imagine we want to ensure there is a trailing newline at the end of the file, but we want this behavior to be opt-in. The simplest way to achieve that is:
- Check whether
ctx.featurescontainshello.trailing_newline - If yes, append
\n - Otherwise, keep the content as is
if "hello.trailing_newline" in ctx.features:
content = ctx.attr.first_name + "\n"
To apply the feature from the command line:
bazel build :hello --features=hello.trailing_newline
Or directly on the target:
hello(
name = "hello",
first_name = "Adin",
features = ["hello.trailing_newline"],
)
Disabling features
One neat trick about Bazel features is that they can be explicitly disabled, both on the command line and via the features attribute, by prefixing the feature name with -.
For example, to disable the hello.trailing_newline feature:
bazel build :hello --features=-hello.trailing_newline
Inside the rule implementation, Bazel exposes a conveniently named ctx.disabled_features list, which contains all features explicitly disabled for that target.
A few things to know
- Features are global, not scoped to a specific rule. This is why prefixing feature names (for example,
hello.trailing_newline) is strongly recommended. rules_swift follows the same convention. - Changing
--featuresinvalidates the analysis cache. - Bazel also provides a
--host_featuresflag, which applies to the execution (host) configuration.
Wrapping up
This is one of those simple mechanisms that turns out to be extremely useful. So next time you think about adding an extra attribute to a rule, think twice—features are likely the better choice.