bazel-contrib / bazel-contrib/rules_shell

`rlocation` fails for relative symlinks runfiles.bash

Open
#46 2 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.