diff --git a/bazel/certs/README.md b/bazel/certs/README.md index a2bc623c1f..0d7bb804d9 100644 --- a/bazel/certs/README.md +++ b/bazel/certs/README.md @@ -53,6 +53,10 @@ By default, `generated_certs` derives the year hermetically from the `bazel-out/volatile-status.txt` for stamped builds (`stamp = 1`, set automatically by the macro). No consumer configuration is required. +Year selection runs in a separate stamped action. Certificate generation +consumes only the selected year, so unrelated workspace status changes do +not invalidate cached fixtures or their dependent compilation actions. + Workspaces that want to pin the year from their own `workspace_status_command` may use `year_status_key` as an optional override by emitting a line of the form: diff --git a/bazel/certs/certs.bzl b/bazel/certs/certs.bzl index cc068a408b..b2ff1dab42 100644 --- a/bazel/certs/certs.bzl +++ b/bazel/certs/certs.bzl @@ -69,10 +69,10 @@ def generated_certs( """ gen_label = str(Label(str(gen))) + year_srcs = [] if year != None: year_cmd = "YEAR=\"" + str(year) + "\";" year_arg = "\"" + str(year) + "\"" - stamp = 0 else: year_cmd = " ".join([ "YEAR=$$(sed -n -E 's/^" + year_status_key + " ([0-9]{4})$$/\\1/p' bazel-out/stable-status.txt 2>/dev/null);", @@ -82,12 +82,28 @@ def generated_certs( "fi;", "if [ -z \"$$YEAR\" ]; then YEAR=$$(date -u +%Y); fi;", ]) - year_arg = "\"$$YEAR\"" - stamp = 1 + + # Only the selected year affects certificates. Keep unrelated status + # changes (including BUILD_TIMESTAMP) out of certificate action inputs. + year_file = name + "_epoch_year.txt" + native.genrule( + name = name + "_epoch_year", + outs = [year_file], + cmd = year_cmd + " printf '%s\\n' \"$$YEAR\" > $@", + # Undocumented attr to depend on the workspace status files; see + # https://github.com/bazelbuild/bazel/issues/4942 + stamp = 1, + testonly = testonly, + tags = tags, + visibility = visibility, + ) + year_srcs = [year_file] + year_cmd = "" + year_arg = "\"$$(cat $(location " + year_file + "))\"" native.genrule( name = name + "_gen", - srcs = [spec] + srcs, + srcs = [spec] + srcs + year_srcs, outs = outs, cmd = " ".join([ year_cmd, @@ -105,9 +121,7 @@ def generated_certs( " exit 1;", "fi;", ]), - # Undocumented attr to depend on the workspace status files; see - # https://github.com/bazelbuild/bazel/issues/4942 - stamp = stamp, + stamp = 0, testonly = testonly, tags = tags, tools = [gen_label], diff --git a/bazel/certs/test/BUILD b/bazel/certs/test/BUILD index 202b6f229a..da89f4ca28 100644 --- a/bazel/certs/test/BUILD +++ b/bazel/certs/test/BUILD @@ -1,6 +1,6 @@ load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//certs:certs.bzl", "generated_certs") -load(":certs_analysis_test.bzl", "command_test", "files_test") +load(":certs_analysis_test.bzl", "command_test", "files_test", "inputs_test") generated_certs( name = "generated", @@ -125,22 +125,51 @@ command_test( command_test( name = "stamped_command_test", - target_under_test = ":stamped_gen", + target_under_test = ":stamped_epoch_year", expected = "STABLE_CERT_EPOCH_YEAR", ) command_test( name = "build_timestamp_command_test", - target_under_test = ":stamped_gen", + target_under_test = ":stamped_epoch_year", expected = "BUILD_TIMESTAMP", ) command_test( name = "custom_status_command_test", - target_under_test = ":custom_status_gen", + target_under_test = ":custom_status_epoch_year", expected = "CUSTOM_CERT_YEAR", ) +inputs_test( + name = "year_status_inputs_test", + expected = [ + "stable-status.txt", + "volatile-status.txt", + ], + target_under_test = ":stamped_epoch_year", +) + +inputs_test( + name = "certificate_inputs_test", + expected = ["stamped_epoch_year.txt"], + forbidden = [ + "stable-status.txt", + "volatile-status.txt", + ], + target_under_test = ":stamped_gen", +) + +inputs_test( + name = "pinned_inputs_test", + forbidden = [ + "stable-status.txt", + "volatile-status.txt", + "generated_epoch_year.txt", + ], + target_under_test = ":generated_gen", +) + files_test( name = "runtime_filegroup_test", target_under_test = ":generated", diff --git a/bazel/certs/test/certs_analysis_test.bzl b/bazel/certs/test/certs_analysis_test.bzl index 3cbf6d2ae8..e761fa65b8 100644 --- a/bazel/certs/test/certs_analysis_test.bzl +++ b/bazel/certs/test/certs_analysis_test.bzl @@ -20,6 +20,25 @@ command_test = analysistest.make( attrs = {"expected": attr.string(mandatory = True)}, ) +def _inputs_test_impl(ctx): + env = analysistest.begin(ctx) + actions = [action for action in analysistest.target_actions(env) if action.mnemonic == "Genrule"] + asserts.equals(env, 1, len(actions)) + inputs = [f.basename for f in actions[0].inputs.to_list()] + for name in ctx.attr.expected: + asserts.true(env, name in inputs, "missing input: " + name) + for name in ctx.attr.forbidden: + asserts.false(env, name in inputs, "unexpected input: " + name) + return analysistest.end(env) + +inputs_test = analysistest.make( + _inputs_test_impl, + attrs = { + "expected": attr.string_list(), + "forbidden": attr.string_list(), + }, +) + def _files_test_impl(ctx): env = analysistest.begin(ctx) files = sorted([