Practical Bazel: Supporting Trinary Stamp Attributes
Practical Bazel bazel stamping
Published: 2023-03-06
Practical Bazel: Supporting Trinary Stamp Attributes

In Bazel, stamping is the process of embedding additional information into built binaries, such as the source control revision or other workspace-related information. Rules that support stamping typically include an integer stamp attribute, where 1 means “always stamp”, 0 means “never stamp”, and -1 means “use the Bazel build --stamp flag. This blog post explains how to write a rule that supports these values.

Quite frankly, the 0 and 1 cases are trivial; the only trick is supporting -1. We can implement this by using a combination of Bazel macros and configurable build attributes

Consider a ruleset for the hypothetical programming language mylang.

First, define a config_setting() that can be used to detect whether --stamp was set on the comamnd-line:

1
2
3
4
5
6
7
8
# @rules_mylang//mylang/private:BUILD.bzl

# Used to detect whether --stamp is specified on command-line.  See
# https://github.com/bazelbuild/bazel/issues/11164 for details
config_setting(
    name = "private_stamp_detect",
    values = {"stamp": "1"},
)

Now, create a rule mylang_binary(), but rather than exposing the rule directly, wrap the rule with a macro that reads the value of private_stamp_detect from above:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# mylang_binary.bzl: Contains the definition of the mylang_binary, which
# is wrapped by a macro to support stamp detection
def mylang_binary(name, **kwargs):
    _mylang_binary(
        name = name,
        private_stamp_detect = select({
            "//mylang/private:private_stamp_detect": True,
            "//conditions:default": False,
        }),
        **kwargs
    )

Then implement the rule as normal, setting stamp based on a combination of the rule’s stamp attribute and the value of private_stamp_detect:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# mylang_binary.bzl: Contains the definition of the mylang_binary, which
# is wrapped by a macro to support stamp detection
def _impl(ctx):
    if ctx.attr.stamp == -1:
        stamp = ctx.attr.private_stamp_detect
    else:
        stamp = (ctx.attr.stamp == 1)
    # stamp is a boolean which says whether this rule should perform stamping

    ...

_mylang_binary = rule(
    implementation = _impl,
    attrs = {
        "stamp": attr.int(default = -1),
        "private_stamp_detect": attr.bool()
    }
)

Now mylang_binary will stamp binaries by default if users use the bazel build --stamp command-line optoin.