feat: basic stamping support
This commit is contained in:
parent
6f52c77a51
commit
de081fb72e
|
@ -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()
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
<!-- Generated with Stardoc: http://skydoc.bazel.build -->
|
||||
|
||||
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),
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
<a id="maybe_stamp"></a>
|
||||
|
||||
## maybe_stamp
|
||||
|
||||
<pre>
|
||||
maybe_stamp(<a href="#maybe_stamp-ctx">ctx</a>)
|
||||
</pre>
|
||||
|
||||
Provide the bazel-out/stable_status.txt and bazel-out/volatile_status.txt files.
|
||||
|
||||
**PARAMETERS**
|
||||
|
||||
|
||||
| Name | Description | Default Value |
|
||||
| :------------- | :------------- | :------------- |
|
||||
| <a id="maybe_stamp-ctx"></a>ctx | The rule context | none |
|
||||
|
||||
**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
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||
load("//lib/private:stamping.bzl", "stamp_build_setting")
|
||||
|
||||
exports_files(
|
||||
glob(["*.bzl"]),
|
||||
|
@ -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"],
|
||||
|
@ -165,3 +169,10 @@ bzl_library(
|
|||
"//lib/private:host_repo",
|
||||
],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "stamping",
|
||||
srcs = ["stamping.bzl"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//lib/private:stamping"],
|
||||
)
|
||||
|
|
|
@ -139,6 +139,7 @@ bzl_library(
|
|||
visibility = ["//lib:__subpackages__"],
|
||||
deps = [
|
||||
":expand_make_vars",
|
||||
"//lib:stamping",
|
||||
"@bazel_skylib//lib:dicts",
|
||||
],
|
||||
)
|
||||
|
@ -168,3 +169,9 @@ bzl_library(
|
|||
visibility = ["//lib:__subpackages__"],
|
||||
deps = [":repo_utils"],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "stamping",
|
||||
srcs = ["stamping.bzl"],
|
||||
visibility = ["//lib:__subpackages__"],
|
||||
)
|
||||
|
|
|
@ -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))
|
|
@ -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"),
|
||||
),
|
||||
}
|
|
@ -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",
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
"Example of a rule that can version-stamp its outputs"
|
||||
|
||||
load("//lib:stamping.bzl", "STAMP_ATTRS", "maybe_stamp")
|
||||
|
||||
def _stamp_aware_rule_impl(ctx):
|
||||
args = ctx.actions.args()
|
||||
inputs = []
|
||||
outputs = [ctx.outputs.out]
|
||||
stamp = maybe_stamp(ctx)
|
||||
if stamp:
|
||||
args.add("--volatile_status_file", stamp.volatile_status_file)
|
||||
args.add("--stable_status_file", stamp.stable_status_file)
|
||||
|
||||
inputs.extend([stamp.volatile_status_file, stamp.stable_status_file])
|
||||
|
||||
ctx.actions.run_shell(
|
||||
inputs = inputs,
|
||||
arguments = [args],
|
||||
outputs = outputs,
|
||||
# In reality, this program would also read from the status files.
|
||||
command = "echo $@ > " + outputs[0].path,
|
||||
)
|
||||
return [DefaultInfo(files = depset(outputs))]
|
||||
|
||||
my_stamp_aware_rule = rule(
|
||||
implementation = _stamp_aware_rule_impl,
|
||||
attrs = dict({
|
||||
"out": attr.output(mandatory = True),
|
||||
}, **STAMP_ATTRS),
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env bash
|
||||
set -o nounset
|
||||
|
||||
# Snippet to parse Bazel's status file format.
|
||||
# https://github.com/bazelbuild/bazel/issues/11164#issuecomment-996186921
|
||||
# is another option, which requires Bash 4 for associative arrays.
|
||||
while IFS= read -r line; do
|
||||
read key value <<< "$line"
|
||||
declare $key="$value"
|
||||
done < <(cat "${BAZEL_STABLE_STATUS_FILE:-/dev/null}" "${BAZEL_VOLATILE_STATUS_FILE:-/dev/null}")
|
||||
|
||||
# A real program would do something useful with the stamp info, like pass it to a linker.
|
||||
echo "${BUILD_USER:-unstamped}" > $1
|
Loading…
Reference in New Issue