Practical Bazel: Isolating Ruleset’s Public Interface
Practical Bazel bazel
Published: 2022-10-12
Practical Bazel: Isolating Ruleset's Public Interface

When writing a custom Bazel ruleset, it is important to carefully separate its public interface from its private implementation and be deliberate and careful about changes to the public interface. Here’s the pattern I use when I’m writing rulesets to handle this.

Consider a ruleset named rules_foo. I will define the public endpoints of the ruleset in a file named @rules_foo//foo:def.bzl, and put all implementation inside the folder @rules_foo//foo/private. The public endpoint definition file def.bzl does nothing but decide which pieces of Bazel code in the private implemtnation define the ruleset’s public interface.

For example, here’s what the def.bzl might look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# @rules_foo//foo:def.bzl
#
# Only load and expose the Bazel rules, providers, etc. that
# comprise the public interface of rules_foo.  Leave everything
# else implicitly hidden in //foo/private/.
load("//foo/private/rules:binary.bzl", _foo_binary = "foo_binary")
load("//foo/private/rules:library.bzl", _foo_library = "foo_library")
load("//foo/private/rules:test.bzl", _foo_test = "foo_test")

foo_binary = _foo_binary
foo_library = _foo_library
foo_test = _foo_test

# Also expose any providers, toolchains, macros, etc. here which
# make up the public interface of rules_foo.