Practical Bazel: Using Bazel’s Bash Runfiles Library
Practical Bazel bazel bash
Published: 2023-02-27
Practical Bazel: Using Bazel's Bash Runfiles Library

Dealing with Bazel runfiles is one of the most annoying things about using Bazel. Fortunately, Bazel provides a library to make resolving runfiles from Bash scripts easy.

Properly resolving runfiles is more complicated than it first appears. Many systems manage runfiles as symbolic links in a runfiles directory, but some use a manifest file named MANIFEST. To properly resolve a runfile you must be able to handle both cases.

Within the bazel_tools repsoitory there is a script named runfiles.bash. It is self-described as a “runfiles lookup library for Bazel-built Bash binaries and tests”. It provides a function named rlocation() which allows you to resolve the location of a runfile from any bash script.

To use:

  1. Add @bazel_tools//tools/bash/runfiles as a dependency of your sh_* rule:
1
2
3
4
5
6
7
sh_binary(
    name = "my_binary",
    srcs = ["my_binary.sh"],
    deps = [
        "@bazel_tools//tools/bash/runfiles",
    ],
)
  1. Copy-paste the initialization code from runfiles.bash to th beginning of your script:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
  source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
  source "$0.runfiles/$f" 2>/dev/null || \
  source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
  source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
  { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---
  1. Use rlocation() to resolve runfiles locations. Note that runfile paths must start with the name of your workspace:
1
2
# Resolve the location of unittest.bash using rlocation
source $(rlocation my_workspace/tools/bash/unittest/unittest.bash) || exit 1

Note: According to Jay Conrod’s blog post Writing Bazel rules: data and runfiles, the rlocation function is already predeclared in sh_test() rules which means that loading the runfiles library is not necessary. I verified that this is true in practice, but I could not find any official documentation about this guarantee.