feat: yq supports stamping

Fixes #223
This commit is contained in:
Malte Poll 2023-06-14 16:21:55 +02:00 committed by Alex Eagle
parent 3008f91387
commit d79f4d4867
7 changed files with 104 additions and 4 deletions

15
docs/yq.md generated
View File

@ -96,6 +96,19 @@ genrule(
)
```
```starlark
# With --stamp, causes properties to be replaced by version control info.
yq(
name = "stamped",
srcs = ["package.yaml"],
expression = "|".join([
"load(strenv(STAMP)) as $stamp",
# Provide a default using the "alternative operator" in case $stamp is empty dict.
".version = ($stamp.BUILD_EMBED_LABEL // "<unstamped>")",
]),
)
```
yq is capable of parsing and outputting to other formats. See their [docs](https://mikefarah.gitbook.io/yq) for more examples.
@ -106,7 +119,7 @@ yq is capable of parsing and outputting to other formats. See their [docs](https
| :------------- | :------------- | :------------- |
| <a id="yq-name"></a>name | Name of the rule | none |
| <a id="yq-srcs"></a>srcs | List of input file labels | none |
| <a id="yq-expression"></a>expression | yq expression (https://mikefarah.gitbook.io/yq/commands/evaluate). Defaults to the identity expression "." | <code>"."</code> |
| <a id="yq-expression"></a>expression | yq expression (https://mikefarah.gitbook.io/yq/commands/evaluate). Defaults to the identity expression ".". Subject to stamp variable replacements, see [Stamping](./stamping.md). When stamping is enabled, an environment variable named "STAMP" will be available in the expression.<br><br>Be careful to write the filter so that it handles unstamped builds, as in the example above. | <code>"."</code> |
| <a id="yq-args"></a>args | Additional args to pass to yq. Note that you do not need to pass _eval_ or _eval-all_ as this is handled automatically based on the number <code>srcs</code>. Passing the output format or the parse format is optional as these can be guessed based on the file extensions in <code>srcs</code> and <code>outs</code>. | <code>[]</code> |
| <a id="yq-outs"></a>outs | Name of the output files. Defaults to a single output with the name plus a ".yaml" extension, or the extension corresponding to a passed output argment (e.g., "-o=json"). For split operations you must declare all outputs as the name of the output files depends on the expression. | <code>None</code> |
| <a id="yq-kwargs"></a>kwargs | Other common named parameters such as <code>tags</code> or <code>visibility</code> | none |

View File

@ -3,6 +3,7 @@ exports_files(
"diff_test_tmpl.sh",
"diff_test_tmpl.bat",
"parse_status_file.jq",
"parse_status_file.yq",
],
visibility = ["//visibility:public"],
)

View File

@ -217,6 +217,7 @@ bzl_library(
bzl_library(
name = "yq",
srcs = ["//lib/private:yq.bzl"],
deps = ["//lib:stamping"],
)
bzl_library(

View File

@ -0,0 +1,3 @@
load_str(filename) | split("\n") | .[] | select(length!=0)
| [capture("(?P<key>[^\s]+)\s+(?P<value>.*)")]
| from_entries

View File

@ -1,6 +1,8 @@
"""Implementation for yq rule"""
_yq_attrs = {
load("//lib:stamping.bzl", "STAMP_ATTRS", "maybe_stamp")
_yq_attrs = dict({
"srcs": attr.label_list(
allow_files = [".yaml", ".json", ".xml"],
mandatory = True,
@ -9,7 +11,11 @@ _yq_attrs = {
"expression": attr.string(mandatory = False),
"args": attr.string_list(),
"outs": attr.output_list(mandatory = True),
}
"_parse_status_file_expression": attr.label(
allow_single_file = True,
default = Label("//lib/private:parse_status_file.yq"),
),
}, **STAMP_ATTRS)
def is_split_operation(args):
for arg in args:
@ -38,6 +44,31 @@ def _yq_impl(ctx):
if len(ctx.attr.srcs) == 0 and "-n" not in args and "--null-input" not in args:
args = args + ["--null-input"]
stamp = maybe_stamp(ctx)
stamp_yaml = ctx.actions.declare_file("_%s_stamp.yaml" % ctx.label.name)
if stamp:
# create an action that gives a YAML representation of the stamp keys
ctx.actions.run_shell(
tools = [yq_bin],
inputs = [stamp.stable_status_file, stamp.volatile_status_file, ctx.file._parse_status_file_expression],
outputs = [stamp_yaml],
command = "{yq} --from-file {expression} {stable} {volatile} > {out}".format(
yq = yq_bin.path,
expression = ctx.file._parse_status_file_expression.path,
stable = stamp.stable_status_file.path,
volatile = stamp.volatile_status_file.path,
out = stamp_yaml.path,
),
mnemonic = "ConvertStatusToYaml",
)
else:
# create an empty stamp file as placeholder
ctx.actions.write(
output = stamp_yaml,
content = "{}",
)
inputs.append(stamp_yaml)
# For split operations, yq outputs files in the same directory so we
# must cd to the correct output dir before executing it
bin_dir = "/".join([ctx.bin_dir.path, ctx.label.package]) if ctx.label.package else ctx.bin_dir.path
@ -58,6 +89,7 @@ def _yq_impl(ctx):
inputs = inputs,
outputs = outs,
command = cmd,
env = {"STAMP": escape_bin_dir + stamp_yaml.path},
mnemonic = "Yq",
)

View File

@ -1,5 +1,6 @@
load("//lib/private:diff_test.bzl", "diff_test")
load("//lib:yq.bzl", "yq")
load("//lib:testing.bzl", "assert_contains")
exports_files(
[
@ -308,6 +309,38 @@ diff_test(
file2 = ":case_merge_expression_json",
)
# Expression that uses a stamp variable
[
yq(
name = ("" if stamp else "un") + "stamped",
srcs = ["a.yaml"],
expression = "|".join([
"load(strenv(STAMP)) as $stamp",
# Provide a default using the "alternative operator"
".foo = ($stamp.BUILD_EMBED_LABEL // \"<unstamped>\")",
".value = ($stamp.BUILD_TIMESTAMP // 1 | @yamld)",
]),
stamp = stamp,
)
for stamp in [
0,
1,
]
]
assert_contains(
name = "check_stamped",
actual = "stamped.yaml",
# v1.2.3 comes from the --embed_label flag in .bazelrc
expected = """foo: v1.2.3""",
)
assert_contains(
name = "check_unstamped",
actual = "unstamped.yaml",
expected = """foo: <unstamped>""",
)
# Call yq within a genrule
genrule(
name = "case_genrule",

View File

@ -95,13 +95,30 @@ def yq(name, srcs, expression = ".", args = [], outs = None, **kwargs):
)
```
```starlark
# With --stamp, causes properties to be replaced by version control info.
yq(
name = "stamped",
srcs = ["package.yaml"],
expression = "|".join([
"load(strenv(STAMP)) as $stamp",
# Provide a default using the "alternative operator" in case $stamp is empty dict.
".version = ($stamp.BUILD_EMBED_LABEL // "<unstamped>")",
]),
)
```
yq is capable of parsing and outputting to other formats. See their [docs](https://mikefarah.gitbook.io/yq) for more examples.
Args:
name: Name of the rule
srcs: List of input file labels
expression: yq expression (https://mikefarah.gitbook.io/yq/commands/evaluate). Defaults to the identity
expression "."
expression ".". Subject to stamp variable replacements, see [Stamping](./stamping.md).
When stamping is enabled, an environment variable named "STAMP" will be available in the expression.
Be careful to write the filter so that it handles unstamped builds, as in the example above.
args: Additional args to pass to yq. Note that you do not need to pass _eval_ or _eval-all_ as this
is handled automatically based on the number `srcs`. Passing the output format or the parse format
is optional as these can be guessed based on the file extensions in `srcs` and `outs`.