Skip to content

Add a _runfiles_enabled marker file to runfiles trees - #30674

Draft
fmeum wants to merge 3 commits into
bazelbuild:masterfrom
fmeum:runfiles-enabled-marker
Draft

Add a _runfiles_enabled marker file to runfiles trees#30674
fmeum wants to merge 3 commits into
bazelbuild:masterfrom
fmeum:runfiles-enabled-marker

Conversation

@fmeum

@fmeum fmeum commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds an empty _runfiles_enabled file 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.SKIP leaves behind, does not contain it.

Bazel's own consumers are switched over to it:

  • tools/test/test-setup.sh and tools/test/windows/tw.cc derive RUNFILES_MANIFEST_ONLY from 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_runfiles is off.
  • The Windows launcher checks for the marker file at runtime instead of reading the symlink_runfiles_enabled launch 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_ONLY continues to be set by BazelRuleClassProvider and TestRunnerAction for 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. RunfilesTreeUpdater skipped 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. 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 it is dropped. To keep recreating the tree cheap where it 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.

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_runfiles alone, 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_DIR rather 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_java and rules_python is for Runfiles.Create to apply the following order, which preserves today's behavior exactly when the marker file is absent, i.e. on older Bazel versions:

  1. if RUNFILES_DIR is set and contains _runfiles_enabled, use the directory;
  2. otherwise, if RUNFILES_MANIFEST_ONLY=1 and a manifest is available, use the manifest;
  3. otherwise, if RUNFILES_DIR is set, use the directory;
  4. otherwise, use the manifest.

This is based on #20964, adapted to the current code base.

Build API Changes

No

Checklist

  • I have added tests for the new use cases (if any).
  • I have updated the documentation (if applicable).

Release Notes

RELNOTES: Runfiles trees now contain an empty _runfiles_enabled file, 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.

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
fmeum force-pushed the runfiles-enabled-marker branch from 9ebecd7 to 71e55f1 Compare August 11, 2026 17:03
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.
iancha1992 and others added 2 commits August 12, 2026 18:52
`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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runfiles API: obtaining the symlink path within runfiles_dir

2 participants