Skip to content

Read and write the regular runfiles variables - #373

Draft
fmeum wants to merge 3 commits into
bazelbuild:masterfrom
fmeum:runfiles-regular-env-vars
Draft

Read and write the regular runfiles variables#373
fmeum wants to merge 3 commits into
bazelbuild:masterfrom
fmeum:runfiles-regular-env-vars

Conversation

@fmeum

@fmeum fmeum commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Runfiles libraries decide between resolving runfiles paths against the runfiles directory and resolving them through the runfiles manifest. The Java library makes that decision based on RUNFILES_MANIFEST_ONLY, which Bazel derives from --enable_runfiles alone. That flag is evaluated at analysis time and thus doesn't account for the runfiles directory being materialized by the sandbox or by remote execution, in which case no manifest is staged at all. The java_binary launcher script has the same problem, one level out: it bakes the value of the flag into the script and points subprocesses at a manifest that may not be there.

This makes the library read and write the variables that name the runfiles directory and the runfiles manifest, and select the implementation based on which of the two actually exists — the way every other runfiles library does it. Whoever sets up the environment already knows which of the two it staged, and communicates that by naming only the one that is usable, so a value that names something that isn't there is dropped rather than trusted.

Concretely, with --noenable_runfiles, a java_binary used as a tool of a sandboxed action fails to run today:

$ bazel build --noenable_runfiles //:run_as_tool
grep: .../app.runfiles/MANIFEST: No such file or directory
bazel-out/darwin_arm64-opt-exec/bin/app: line 409: exec: : not found
ERROR: Build did NOT complete successfully

Both the launcher script and the runfiles library insist on a manifest, while the sandbox has materialized the entire runfiles directory. After this change the same target builds, and the binary resolves its runfiles against the directory.

RUNFILES_MANIFEST_ONLY is still written by getEnvVars and by the launcher script whenever the manifest is the implementation in use, for the benefit of subprocesses whose runfiles library still reads it. It is never propagated from the environment, since an inherited value describes how some ancestor process' runfiles were staged.

The commits are independent and can be taken separately:

  1. Select the runfiles implementation based on the regular runfiles variables — the read side of the library.
  2. Clear the manifest variables in getEnvVars if the runfiles directory is used — the write side of the library.
  3. Export the regular runfiles variables from the java_binary launcher — the launcher script, which is what fixes the failure above end to end.

Follow-up, out of scope here: bazelbuild/bazel is adding a _runfiles_enabled marker file to every runfiles tree, which is present if and only if the directory has been fully materialized. With it, the selection can prefer a materialized runfiles directory over an available manifest instead of the other way around, which avoids parsing a manifest that isn't needed. That is a one-line addition on top of this change and needs a Bazel release that stages the marker.

Tested manually on macOS with bazel run, bazel run --noenable_runfiles, bazel build of a genrule whose tool is a java_binary (sandboxed, with and without --noenable_runfiles), plus the new unit tests. //test/... passes.

fmeum added 3 commits August 12, 2026 22:50
…ables

`RUNFILES_MANIFEST_ONLY` is derived from `--enable_runfiles`, an analysis-time flag, and thus doesn't account for the runfiles directory being materialized by the sandbox or by remote execution. When it is set but the runfiles directory has been materialized, no manifest has been staged and the library fails to initialize even though all runfiles are right there.

Determine the implementation from the variables that name the two candidates instead, as all other runfiles libraries do: use the manifest if `RUNFILES_MANIFEST_FILE` names an existing file, otherwise use the runfiles directory if `RUNFILES_DIR` (or `JAVA_RUNFILES`) names an existing directory. Whoever sets up the environment already knows which of the two it staged and only names the one that is usable, so a value that names something that isn't there is dropped rather than trusted.

Also pass the runfiles directory from the environment on to `getEnvVars` instead of always deriving it from the manifest path, which only works if the two are adjacent.
…y is used

A subprocess that inherits `RUNFILES_MANIFEST_FILE` or `RUNFILES_MANIFEST_ONLY` from an ancestor process resolves its runfiles through a manifest that describes how that process' runfiles were staged, not how the current process' runfiles were, and thus disagrees with the runfiles directory passed to it in `RUNFILES_DIR`.

Set both variables to the empty string, which every runfiles library treats as unset, so that the implementation this process selected is also the one its subprocesses select.
The launcher script exports `JAVA_RUNFILES`, but not `RUNFILES_DIR`, so a subprocess whose runfiles library only knows about the latter can't find the runfiles directory. It also exports `RUNFILES_MANIFEST_FILE` and `RUNFILES_MANIFEST_ONLY` based on `--enable_runfiles` alone, which tells subprocesses to use a manifest that hasn't been staged whenever the sandbox or remote execution materialized the runfiles directory instead. This makes a `java_binary` used as a tool fail with `--noenable_runfiles`, since even its own `rlocation` can't resolve the JVM launcher.

Export `RUNFILES_DIR` alongside `JAVA_RUNFILES`, and only point subprocesses at the manifest if it has actually been staged. `rlocation` now resolves against the runfiles directory in that case, which is what it already did whenever runfiles were enabled.
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.

1 participant