/rules_runfiles

Compile-time safe runfiles access for Bazel

Primary LanguageStarlarkApache License 2.0Apache-2.0

Compile-time safe runfiles access for Bazel

A key concept of Bazel is that of runfiles: the collection of files that a binary needs to have access to at runtime. Runfiles are usually declared using the data attribute and are automatically propagated from dependencies.

How a binary can find its runfiles at runtime depends on the OS: While Bazel uses a tree of symlinks on Unix systems, it usually falls back to a manifest file on Windows. Thus, the recommended way to look up runfiles is through the runfiles libraries provided for all major languages under @bazel_tools//tools/<lang>/runfiles or @rules_<lang>//<lang>/runfiles. These libraries all offer an rlocation function that maps the runfiles path of a file, which is of the form repository/path/to/pkg/filename, to the actual location of the file at runtime.

This ruleset offers a convenient wrapper around the runfiles libraries that has the following advantages over using the libraries directly:

  • Compile-time safety: If your Bazel target compiles, it will find its runfiles at runtime.

  • Automatic cross-platform support: //foo:bar is libbar.so on Linux, but bar.dll on Windows? With rules_runfiles, the runfiles path of the file will be available as a constant named after the label //foo:bar, which is independent of the particular target platform.

  • IDE completions: rules_runfiles translates the Bazel package structure into generated, language-specific structures that work well with IDEs. For example, //foo:bar is available as ::runfiles::current_repo::foo::bar (nested namespaces) in C++ and JavaRunfilesTargetName.current_repo.foo.bar (nested classes) in Java.

  • Required for Bazel modules: With Bazel's new module system (also known as bzlmod), the internal names of repositories depend on their declared versions as well as the particular way they are loaded. Since the names are part of the runfiles paths, these paths are no longer static and thus can no longer be hardcoded. rules_runfiles uses code generation together with a naming scheme that carefully avoids the internal names of repositories to allow consistent runfiles access from modules.

The following languages are currently supported:

  • C++
  • Java

Support for other languages is planned, suggestions and contributions are very welcome.

Usage

Step 0: WORKSPACE setup

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "fmeum_rules_runfiles",
    sha256 = "057f42a28cccc4a27ba3d65f1b49ef734e95bd74f10c5a98d8812e7a0e663e00",
    strip_prefix = "rules_runfiles-0.1.1",
    url = "https://github.com/fmeum/rules_runfiles/archive/refs/tags/v0.1.1.tar.gz",
)

Step 1: Declaring a <lang>_runfiles target

Create a <lang>_runfiles target with all the data dependencies you would usually specify on your <lang>_binary or <lang>_library and reference it in the deps attribute. Here, <lang> is either cc (C++) or java (Java).

load("@fmeum_rules_runfiles//runfiles:<lang>_defs.bzl", "<lang>_runfiles")

<lang>_runfiles(
    name = "foo_runfiles",
    data = [
        "data/test.txt",
        "data/dir",
        ":other_binary",
        "@other_repo//path/to/other:binary",
    ],     
)

<lang>_binary(
    name = "foo",
    deps = [
        ":foo_runfiles",
        ...
    ],
    ...
)

Note:

  • Since the code generated by rules_runfiles uses the labels listed in the data attribute to refer to runfiles, every label must correspond to exactly one file. Rules such as bazel-skylib's select_file can be used to break up targets into individual files.
  • The data dependencies should not be repeated on the <lang>_binary or <lang>_library target.
  • <lang>_runfiles targets are only visible from the package in which they are defined. Since they contain generated code that refers to the "current package", exposing them to other packages would lead to very confusing situations. If there is a larger list of data dependencies shared between multiple targets in different packages, consider exporting the list via a Starlark constant in a .bzl file instead.
  • Use of Bazel 5.1.0 or higher is recommended as lower versions of Bazel include runfiles libraries that do not always find runfiles contained in directories that are themselves runfiles when using a manifest (bazelbuild/bazel#14336).

Step 2: Accessing the runfiles constants

C++: A cc_runfiles target with name = "foo_runfiles" provides a generated header foo_runfiles.h that can be included via

#include "path/to/pkg/foo_runfiles.h"

This file contains a string constant for every label in the data attribute of the cc_runfiles rule, organized in namespaces. When deriving the fully-qualified name of a constant, Bazel repositories and packages map to namespaces and non-alphanumeric characters are replaced by underscores. There are also special namespaces for the current package, the current repository and the main repository.

Bazel label C++ constant
:foo ::runfiles::current_pkg::foo
:dir/some-File.txt ::runfiles::current_pkg::dir_some_File_txt
//path/to/pkg:foo ::runfiles::current_repo::path::to::pkg::foo
@foobar//path/to/pkg:foo ::runfiles::foobar::path::to::pkg::foo
@//path/to/pkg:foo ::runfiles::main_repo::path::to::pkg::foo

The fully-qualified names of these constants can be shortened with using and using namespace directives.

Java: A java_runfiles target with name = "foo_runfiles" provides a generated class FooRunfiles (name converted to CamelCase with special characters replaced with underscores). By default, this class is contained in a package determined from the containing Bazel package by the same rules as java_binary's main_class. It can be overriden with the package attribute on java_runfiles.

This class contains a String constant for every label in the data attribute of the java_runfiles rule, organized in nested classes. When deriving the fully-qualified name of a constant, Bazel repositories and packages map to nested classes and non-alphanumeric characters are replaced by underscores. There are also special nested classes for the current package, the current repository and the main repository.

Bazel label Java constant
:foo FooRunfiles.current_pkg.foo
:dir/some-File.txt FooRunfiles.current_pkg.dir_some_File_txt
//path/to/pkg:foo FooRunfiles.current_repo.path.to.pkg.foo
@foobar//path/to/pkg:foo FooRunfiles.foobar.path.to.pkg.foo
@//path/to/pkg:foo FooRunfiles.main_repo.path.to.pkg.foo

The fully-qualified names of these constants can be shortened with import static directives.

Step 3: Using the Bazel runfiles libraries

C++: No additional dependency or include is needed to access the Bazel C++ runfiles library. See its main header for usage instructions.

Java: No additional dependency is needed to access the Bazel Java runfiles library. See its main source file for usage instructions.

Examples