Add a _runfiles_enabled marker file to runfiles trees - #30674
Draft
fmeum wants to merge 3 commits into
Draft
Conversation
Runfiles libraries currently can't tell a fully materialized runfiles directory apart from one that only contains the manifest and the workspace subdirectory, which is what `RunfileSymlinksMode.SKIP` leaves behind. They are told about this out of band via the `RUNFILES_MANIFEST_ONLY` environment variable, which Bazel derives from the value of `--enable_runfiles` alone and thus is wrong whenever the runfiles are materialized by the sandbox or by remote execution anyway. Add an empty `_runfiles_enabled` file to every runfiles tree. Since it is an ordinary runfiles entry, it is created by exactly those mechanisms that also materialize all other runfiles and thus tracks the state of the runfiles directory precisely. Use the marker file in `RunfilesTreeUpdater`, the test setup scripts and the Windows launcher, which no longer needs the `symlink_runfiles_enabled` launch info key. `RUNFILES_MANIFEST_ONLY` is still set for runfiles libraries that don't recognize the marker file yet, but the test setup scripts now derive it from the marker file rather than passing it through. Note that `tw.cc` has to clear it just like `test-setup.sh` does: `TestPolicy#computeTestEnvironment` resolves the environment common to all actions into every test's environment, and `BazelRuleClassProvider` adds it to that whenever `--enable_runfiles` is off.
fmeum
force-pushed
the
runfiles-enabled-marker
branch
from
August 11, 2026 17:03
9ebecd7 to
71e55f1
Compare
Wyverald
pushed a commit
to bazel-contrib/rules_shell
that referenced
this pull request
Aug 12, 2026
`runfiles_rlocation_checked` looked up a path in the runfiles directory before consulting the manifest. Whether the runfiles directory is populated is a property of how the action or test is executed and is not known at analysis time, so the directory may exist while holding the stale contents of a previous execution. Consult the manifest first and only fall back to the runfiles directory if no manifest exists. This also fixes `runfiles_current_repository` on Windows with `--enable_runfiles`. That mode is the only one in which both `RUNFILES_DIR` and `RUNFILES_MANIFEST_FILE` are set: Windows does not sandbox, so the runfiles tree used at runtime contains the `MANIFEST` file that `runfiles_export_envvars` promotes to `RUNFILES_MANIFEST_FILE`. `rlocation` then returned a path inside the runfiles tree, but the manifest maps rlocation paths to the locations of the original files, so the caller could never be the target of a manifest entry and `runfiles_current_repository` reported every caller as belonging to the main repository. With the manifest taking precedence, callers are manifest targets again and the lookup succeeds. `runfiles_current_repository` had no test coverage at all, which is why this went unnoticed. Cover all three combinations of the two envvars: directory only, manifest only, and both set at the same time. The last case also asserts that a stale copy in the runfiles directory is never preferred over the manifest. Future work (bazelbuild/bazel#30674) will allow the directory to be used again in both functions, assuming it's materialized.
`RunfilesTreeUpdater` skipped recreating a runfiles tree if the output manifest in it matched the input manifest. The manifest only records the *set* of runfiles, so this implies that the tree is up to date only if it consists of symbolic links, whose targets are the authoritative files. It does not on a file system that materializes symlinks as copies (Windows without `--windows_enable_symlinks`), where a runfile that is modified without the manifest changing leaves a stale copy behind. Since `linkManifest()` makes the output manifest a symbolic link on every file system that supports them, the check would never both apply and be sound, so drop it. To keep recreating the tree cheap where the check used to fire, `SymlinkTreeHelper` now keeps an existing copy that is still up to date instead of deleting and rewriting it, which also stops a single modified runfile from causing the entire tree to be copied again. Claude-Session: https://claude.ai/code/session_013o72rMrpgYKD9wYYQ2Nr98
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds an empty
_runfiles_enabledfile to every runfiles tree, which is present in a runfiles directory if and only if that directory has been fully materialized.Since the marker is an ordinary runfiles entry, it is created by exactly those mechanisms that also materialize all other runfiles: the symlink tree, the sandbox and remote execution. A runfiles directory that only contains the manifest and the workspace subdirectory, which is what
RunfileSymlinksMode.SKIPleaves behind, does not contain it.Bazel's own consumers are switched over to it:
tools/test/test-setup.shandtools/test/windows/tw.ccderiveRUNFILES_MANIFEST_ONLYfrom it instead of passing through the value set by Bazel, which in particular means that they now unset it if the sandbox or remote execution materialized the runfiles directory even though--enable_runfilesis off.symlink_runfiles_enabledlaunch info key, which is baked in at analysis time and thus suffers from the same imprecision. The key is no longer read (rules may keep emitting it).RUNFILES_MANIFEST_ONLYcontinues to be set byBazelRuleClassProviderandTestRunnerActionfor runfiles libraries that don't recognize the marker file yet.SourceManifestAction's GUID is bumped because the action's output changes without its key otherwise doing so.The second commit removes the only case in which a fully materialized runfiles directory can hold stale contents. The marker file cannot detect that case, so it would otherwise be pushed onto every consumer that starts trusting the directory.
RunfilesTreeUpdaterskipped recreating a tree if the output manifest in it matched the input manifest. The manifest only records the set of runfiles, so this implies that the tree is up to date only if it consists of symbolic links, whose targets are the authoritative files. It does not on a file system that materializes symlinks as copies (Windows without--windows_enable_symlinks), where a runfile that is modified without the manifest changing leaves a stale copy behind. SincelinkManifest()makes the output manifest a symbolic link on every file system that supports them, the check would never both apply and be sound, so it is dropped. To keep recreating the tree cheap where it used to fire,SymlinkTreeHelpernow keeps an existing copy that is still up to date instead of deleting and rewriting it, which also stops a single modified runfile from causing the entire tree to be copied again.Motivation
Fixes #24929.
Runfiles libraries have to prefer the manifest over the runfiles directory because they can't tell whether the directory is usable. The information they get instead,
RUNFILES_MANIFEST_ONLY, is derived from the value of--enable_runfilesalone, so it is wrong whenever a spawn's inputs are materialized by something other than the local symlink tree.With the marker file, a runfiles library can reliably detect that the directory is usable and prefer it, which makes runfiles resolve to paths inside
$RUNFILES_DIRrather than to their sources — the behavior requested in #24929 — and is also faster than parsing the manifest.This is the Bazel-side prerequisite. The follow-up in
rules_cc,rules_javaandrules_pythonis forRunfiles.Createto apply the following order, which preserves today's behavior exactly when the marker file is absent, i.e. on older Bazel versions:RUNFILES_DIRis set and contains_runfiles_enabled, use the directory;RUNFILES_MANIFEST_ONLY=1and a manifest is available, use the manifest;RUNFILES_DIRis set, use the directory;This is based on #20964, adapted to the current code base.
Build API Changes
No
Checklist
Release Notes
RELNOTES: Runfiles trees now contain an empty
_runfiles_enabledfile, which is present in a runfiles directory if and only if that directory has been fully materialized. Runfiles libraries can use it to decide whether to resolve runfiles paths against the directory instead of the manifest.