bazel-contrib / bazel-contrib/rules_shell
`rlocation` fails for relative symlinks runfiles.bash
- Dominant language
- Starlark
- Stars
- 27
- Forks
- 13
- PR merge metrics
- No merged PRs in 30d
Description
## Description of the bug
When a Starlark rule creates a directory symlink using `ctx.actions.symlink(output, target_path=relative_dir)`, the `rlocation` function in `runfiles.bash` fails to resolve the symlink path, returning an empty string.
The symlink **exists and works correctly** in the runfiles directory. The issue is that `runfiles.bash` initialization prefers the manifest, and the manifest cannot properly represent symlinks with relative targets.
### High level symptom
1. Rule creates symlink: `ctx.actions.symlink(output=link, target_path="target_dir")`
2. Manifest stores: `path/to/link target_dir` (relative path)
3. `rlocation("path/to/link")` looks up manifest, gets `target_dir`
4. Checks if `target_dir` exists as file - it doesn't (relative path)
5. Returns empty string
### Steps to reproduce
- The reproduction files are available at https://github.com/wade-arista/bazel-demo/tree/runfiles-relative-symlink
- This GitHub Workflow failed when running the reproduction:
https://github.com/wade-arista/bazel-demo/actions/runs/21880813798/job/63162467607
OR if you'd like to manually run it
1. Extract [runfiles-relative-symlink.tar.gz](https://github.com/user-attachments/files/25219289/runfiles-relative-symlink.tar.gz)
2. Run: `bazel run //pkg:test`
3. Observe the error output showing `rlocation` returns empty for the symlink
**Note**: The test must be in a non-root package (`pkg/`) to reproduce. In the root package, the bug is masked because the relative path `_subdir_link_dir` happens to exist from `$RUNFILES/_main/`. In nested packages, it's at `$RUNFILES/_main/pkg/_subdir_link_dir` but rlocation still returns just `_subdir_link_dir`.
## Root Cause
In `runfiles.bash` lines 88-96, initialization only sets ONE of `RUNFILES_DIR` or `RUNFILES_MANIFEST_FILE`:
```bash
if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
if [[ -f "$0.runfiles_manifest" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" # Wins, RUNFILES_DIR never set
elif [[ -f "$0.runfiles/MANIFEST" ]]; then
export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
export RUNFILES_DIR="$0.runfiles"
fi
fi
```
Since `$0.runfiles_manifest` typically exists, the manifest is used and `RUNFILES_DIR` is never set.
Then in `runfiles_rlocation_checked` (line 351):
```bash
if [[ -e "${RUNFILES_DIR:-/dev/null}/$1" ]]; then # Always false: RUNFILES_DIR not set
```
## Proposed Fix
Set BOTH variables when both the runfiles directory and manifest exist.
This ensures `rlocation` can check the directory first (where symlinks work) before falling back to manifest.
## Workaround
Add this after the runfiles.bash initialization block
```bash
runfiles_export_envvars
```
## Operating System
```bash
$ cat /etc/os-release
NAME="AlmaLinux"
VERSION="9.7 (Moss Jungle Cat)"
ID="almalinux"
VERSION_ID="9.7"
PLATFORM_ID="platform:el9"
```
## Bazel Version
```bash
$ bazel info release
release 8.5.1
```
This also happens in Bazel 9.0
## Additional Context
Debug output from `RUNFILES_LIB_DEBUG=1`:
```
INFO[runfiles.bash]: rlocation(_main/path/_subdir_link): looking in RUNFILES_MANIFEST_FILE
INFO[runfiles.bash]: rlocation(_main/path/_subdir_link): found in manifest as (_subdir_link_dir), but file does not exist
```
The manifest correctly maps the symlink, but the relative target path `_subdir_link_dir` cannot be resolved as a standalone path.
Contributor guide
Assessment
This issue has not been assessed yet.