Skip to content

Commit

Permalink
feat: basic stamping support
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeagle authored and gregmagolan committed Jul 15, 2022
1 parent 6f52c77 commit de081fb
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,9 @@ stardoc_with_diff_test(
bzl_library_target = "//lib:host_repo",
)

stardoc_with_diff_test(
name = "stamping",
bzl_library_target = "//lib:stamping",
)

update_docs()
112 changes: 112 additions & 0 deletions docs/stamping.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions lib/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("//lib/private:stamping.bzl", "stamp_build_setting")

exports_files(
glob(["*.bzl"]),
Expand All @@ -8,6 +9,9 @@ exports_files(
],
)

# Macro that creates targets enabling the use of `--stamp` in Starlark rules
stamp_build_setting(name = "stamp")

toolchain_type(
name = "jq_toolchain_type",
visibility = ["//visibility:public"],
Expand Down Expand Up @@ -165,3 +169,10 @@ bzl_library(
"//lib/private:host_repo",
],
)

bzl_library(
name = "stamping",
srcs = ["stamping.bzl"],
visibility = ["//visibility:public"],
deps = ["//lib/private:stamping"],
)
7 changes: 7 additions & 0 deletions lib/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ bzl_library(
visibility = ["//lib:__subpackages__"],
deps = [
":expand_make_vars",
"//lib:stamping",
"@bazel_skylib//lib:dicts",
],
)
Expand Down Expand Up @@ -168,3 +169,9 @@ bzl_library(
visibility = ["//lib:__subpackages__"],
deps = [":repo_utils"],
)

bzl_library(
name = "stamping",
srcs = ["stamping.bzl"],
visibility = ["//lib:__subpackages__"],
)
60 changes: 60 additions & 0 deletions lib/private/stamping.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""A small utility module dedicated to detecting whether or not the `--stamp` flag is enabled
This module can be removed likely after the following PRs ar addressed:
- https://github.com/bazelbuild/bazel/issues/11164
"""

StampSettingInfo = provider(
doc = "Information about the `--stamp` command line flag",
fields = {
"value": "bool: Whether or not the `--stamp` flag was enabled",
},
)

def _stamp_build_setting_impl(ctx):
return StampSettingInfo(value = ctx.attr.value)

_stamp_build_setting = rule(
doc = "Adapter from our config_setting to a Provider for downstream rules",
implementation = _stamp_build_setting_impl,
attrs = {
"value": attr.bool(
doc = "The default value of the stamp build flag",
mandatory = True,
),
},
)

def stamp_build_setting(name, visibility = ["//visibility:public"]):
native.config_setting(
name = "stamp_detect",
values = {"stamp": "1"},
visibility = visibility,
)

_stamp_build_setting(
name = name,
value = select({
":stamp_detect": True,
"//conditions:default": False,
}),
visibility = visibility,
)

def is_stamping_enabled(attr):
"""Determine whether or not build stamping is enabled.
Args:
attr (struct): A rule's struct of attributes (`ctx.attr`)
Returns:
bool: The stamp value
"""
stamp_num = getattr(attr, "stamp", -1)
if stamp_num > 0:
return True
elif stamp_num == 0:
return False
elif stamp_num < 0:
stamp_flag = getattr(attr, "_stamp_flag", None)
return stamp_flag[StampSettingInfo].value if stamp_flag else False
else:
fail("Unexpected `stamp` value: {}".format(stamp_num))
131 changes: 131 additions & 0 deletions lib/stamping.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""Version Stamping
Bazel is generally only a build tool, and is unaware of your version control system.
However, when publishing releases, you may want to embed version information in the resulting distribution.
Bazel supports this with the concept of a "Workspace status" which is evaluated before each build.
See [the Bazel workspace status docs](https://docs.bazel.build/versions/master/user-manual.html#workspace_status)
To stamp a build, you pass the `--stamp` argument to Bazel.
> Note: https://github.com/bazelbuild/bazel/issues/14341 proposes that Bazel enforce this by
> only giving constant values to rule implementations when stamping isn't enabled.
Stamping is typically performed on a later action in the graph, like on a linking or packaging rule (`pkg_*`).
This means that a changed status variable only causes that action, not re-compilation and thus does not cause cascading re-builds.
Bazel provides a couple of statuses by default, such as `BUILD_EMBED_LABEL` which is the value of the `--embed_label`
argument, as well as `BUILD_HOST` and `BUILD_USER`. You can supply more with the workspace status script, see below.
Some rules accept an attribute that uses the status variables.
They will usually say something like "subject to stamp variable replacements".
## Stamping with a Workspace status script
To define additional statuses, pass the `--workspace_status_command` argument to `bazel`.
The value of this flag is a path to a script that prints space-separated key/value pairs, one per line, such as
```bash
#!/usr/bin/env bash
echo STABLE_GIT_COMMIT $(git rev-parse HEAD)
```
> For a more full-featured script, take a look at the [bazel_stamp_vars in Angular]
Make sure you set the executable bit, eg. `chmod 755 tools/bazel_stamp_vars.sh`.
> **NOTE** keys that start with `STABLE_` will cause a re-build when they change.
> Other keys will NOT cause a re-build, so stale values can appear in your app.
> Non-stable (volatile) keys should typically be things like timestamps that always vary between builds.
You might like to encode your setup using an entry in `.bazelrc` such as:
```sh
# This tells Bazel how to interact with the version control system
# Enable this with --config=release
build:release --stamp --workspace_status_command=./tools/bazel_stamp_vars.sh
```
[bazel_stamp_vars in Angular]: https://github.com/angular/angular/blob/master/tools/bazel_stamp_vars.sh
## Writing a custom rule which reads stamp variables
First, load the helpers:
```starlark
load("@aspect_bazel_lib//lib:stamping.bzl", "STAMP_ATTRS", "maybe_stamp")
```
In your rule implementation, call the `maybe_stamp` function.
If it returns `None` then this build doesn't have stamping enabled.
Otherwise you can use the returned struct to access two files.
The stable_status file contains the keys which were prefixed with `STABLE_`, see above.
The volatile_status file contains the rest of the keys.
```starlark
def _rule_impl(ctx):
args = ctx.actions.args()
inputs = []
stamp = maybe_stamp(ctx)
if stamp:
args.add("--volatile_status_file", stamp.volatile_status)
args.add("--stable_status_file", stamp.stable_status)
inputs.extend([stamp.volatile_status, stamp.stable_status])
# ... call actions which parse the stamp files and do something with the values ...
```
Finally, in the declaration of the rule, include the `STAMP_ATTRS` to declare attributes
which are read by that `maybe_stamp` function above.
```starlark
my_stamp_aware_rule = rule(
attrs = dict({
# ... my attributes ...
}, **STAMP_ATTRS),
)
```
"""

load("//lib/private:stamping.bzl", "is_stamping_enabled")

def maybe_stamp(ctx):
"""Provide the bazel-out/stable_status.txt and bazel-out/volatile_status.txt files.
Args:
ctx: The rule context
Returns:
If stamping is not enabled for this rule under the current build, returns None.
Otherwise, returns a struct containing (volatile_status_file, stable_status_file) keys
"""
if is_stamping_enabled(ctx.attr):
return struct(
volatile_status_file = ctx.version_file,
stable_status_file = ctx.info_file,
)

return None

STAMP_ATTRS = {
"stamp": attr.int(
doc = """\
Whether to encode build information into the output. Possible values:
- `stamp = 1`: Always stamp the build information into the output, even in
[--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds.
This setting should be avoided, since it is non-deterministic.
It potentially causes remote cache misses for the target and
any downstream actions that depend on the result.
- `stamp = 0`: Never stamp, instead replace build information by constant values.
This gives good build result caching.
- `stamp = -1`: Embedding of build information is controlled by the
[--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.
Stamped targets are not rebuilt unless their dependencies change.
""",
default = -1,
values = [1, 0, -1],
),
"_stamp_flag": attr.label(
doc = "Internal use only. A setting used to determine whether or not the `--stamp` flag is enabled.",
default = Label("//lib:stamp"),
),
}
32 changes: 32 additions & 0 deletions lib/tests/stamping/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
load(":stamp_aware_rule.bzl", "my_stamp_aware_rule")
load("//lib:run_binary.bzl", "run_binary")

my_stamp_aware_rule(
name = "test_stamped",
out = "always",
stamp = 1,
)

my_stamp_aware_rule(
name = "test_unstamped",
out = "never",
stamp = 0,
)

my_stamp_aware_rule(
name = "test_default",
out = "default",
)

sh_binary(
name = "stamper",
srcs = ["stamper.sh"],
)

# Build this with --stamp enabled to see your username in the resulting output file
run_binary(
name = "run_stamper",
outs = ["stamped"],
args = ["$(location stamped)"],
tool = "stamper",
)
Loading

0 comments on commit de081fb

Please sign in to comment.