Skip to content

Commit ff35ee4

Browse files
authored
fix(stamping): skip build data spawn when --stamp=false (bazel-contrib#4160)
## Summary `PyWriteBuildData` is dirty on every build with a cold Skyframe when outputs are not downloaded. That is, once per `py_binary` and `py_test` in the build. When stamping is off, the output is a constant, so the action spawn is pure overhead. ## Root Cause Analysis The output is declared with `_py_builtins.declare_constant_metadata_file`. Constant metadata always reports itself as unchanged, so Bazel cannot validate it from the action cache. It stats the output on disk instead. Under `--remote_download_outputs=minimal` the output is not on disk. The stat fails and the action is dirty. It re-executes, hits the remote cache, is still not downloaded, and is dirty again on the next cold-server build. Setting `--remote_download_regex` to match the file avoids this, at the cost of downloading it, which confirms the stat is the trigger. The use of a constant metadata file is justified (as per the comment in the code). With stamping on, the action reads `volatile-status.txt`, which changes every build. The build data file is in the binary's runfiles, so without it its consumers would invalidate on every build due to timestamps. However, with stamping off, there is no volatile input. There is nothing to shield. Unfortunately, the current implementation keeps the _additional_ cost (constant metadata being always unchanged plus failed `stat` syscalls) for none of the benefits. ## Change When `is_stamping_enabled(ctx)` is false, the content of the file is known at analysis time: ``` TARGET <label> STAMPED FALSE ``` So declare an ordinary output and write it with `ctx.actions.write`. No spawn, and no constant metadata output. Note this is broader than `--nostamp`. `is_stamping_enabled` is also false for the exec configuration and for `stamp = 0`, so tool-config binaries benefit from the fix even when `--stamp=true` is set at the top level. The stamped path is unchanged and keeps its constant metadata output. ## Compatibility The output is byte-identical. The label is written with `str(ctx.label)` to match `"TARGET": str(ctx.label)` in the spawn's env, which yields the canonical `@@//pkg:target` form. `ctx.actions.args().add(ctx.label)` renders the display form and drops the `@@`. After the change, the tests in `tests/build_data/build_data_test.py` pass without any modifications. ## Alternative considered Keeping the spawn and declaring an ordinary output would also fix the dirtiness. Writing at analysis time is lighter, since it needs no subprocess. ## Environment rules_python 2.2.0, Bazel 9.2.0, Linux. Reproduce with `--remote_download_outputs=minimal` against a warm remote cache, then `bazel shutdown` and rebuild.
1 parent 809c0f5 commit ff35ee4

2 files changed

Lines changed: 36 additions & 14 deletions

File tree

‎news/4160.fixed.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(stamping) The build data spawn action is now skipped when `--stamp=false` and,
2+
instead the metadata file is written at analysis time via `ctx.actions.write()`.

‎python/private/py_executable.bzl‎

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,17 +1634,31 @@ WARNING: Target {} is using implicit __init__.py creation.
16341634
)
16351635

16361636
def _write_build_data(ctx):
1637+
build_data_name = ctx.label.name + ".build_data.txt"
1638+
1639+
if not is_stamping_enabled(ctx):
1640+
# Without stamping there are no volatile inputs, so the content is known
1641+
# at analysis time. Write it directly. This skips a spawn per executable.
1642+
# It also skips the constant metadata output below, which never reports
1643+
# as up to date.
1644+
build_data = ctx.actions.declare_file(build_data_name)
1645+
1646+
ctx.actions.write(
1647+
output = build_data,
1648+
content = ctx.actions.args()
1649+
.set_param_file_format("multiline")
1650+
.add(str(ctx.label), format = "TARGET %s")
1651+
.add("STAMPED FALSE"),
1652+
)
1653+
1654+
return build_data
1655+
1656+
info_file = ctx.info_file
1657+
version_file = ctx.files._uncachable_version_file[0]
1658+
16371659
inputs = builders.DepsetBuilder()
1638-
if is_stamping_enabled(ctx):
1639-
# NOTE: ctx.info_file is undocumented; see
1640-
# https://github.com/bazelbuild/bazel/issues/9363
1641-
info_file = ctx.info_file
1642-
version_file = ctx.files._uncachable_version_file[0]
1643-
inputs.add(info_file)
1644-
inputs.add(version_file)
1645-
else:
1646-
info_file = None
1647-
version_file = None
1660+
inputs.add(info_file)
1661+
inputs.add(version_file)
16481662

16491663
# A "constant metadata" file is basically a special file that doesn't
16501664
# support change detection logic and reports that it is unchanged. i.e., it
@@ -1670,9 +1684,15 @@ def _write_build_data(ctx):
16701684
# mostly works. Google's RBE, unfortunately, rejects it.
16711685
# * A binary's transitive closure may be so large that it exceeds
16721686
# Google RBE limits for action inputs.
1687+
#
1688+
# The cost is that this action never reports as up to date. Constant
1689+
# metadata cannot be restored from the action cache, so ActionCacheChecker
1690+
# stats the output instead. That stat fails when outputs are not downloaded,
1691+
# and the action re-runs. The trade is worth it here, because the inputs do
1692+
# really change every build.
16731693
build_data = _py_builtins.declare_constant_metadata_file(
16741694
ctx = ctx,
1675-
name = ctx.label.name + ".build_data.txt",
1695+
name = build_data_name,
16761696
root = ctx.bin_dir,
16771697
)
16781698

@@ -1698,13 +1718,13 @@ def _write_build_data(ctx):
16981718
executable = action_exe,
16991719
arguments = [action_args],
17001720
env = {
1701-
"INFO_FILE": info_file.path if info_file else "",
1721+
"INFO_FILE": info_file.path,
17021722
"OUTPUT": build_data.path,
17031723
# Include this so it's explicit, otherwise, one has to detect
17041724
# this by looking for the absense of info_file keys.
1705-
"STAMPED": "TRUE" if is_stamping_enabled(ctx) else "FALSE",
1725+
"STAMPED": "TRUE",
17061726
"TARGET": str(ctx.label),
1707-
"VERSION_FILE": version_file.path if version_file else "",
1727+
"VERSION_FILE": version_file.path,
17081728
},
17091729
inputs = inputs.build(),
17101730
outputs = [build_data],

0 commit comments

Comments
 (0)