How to Format Bazel Starlark Files Using
This article introduces bzlformat, a set of Bazel rules and macros that format Bazel Starlark source files using Buildifier, test that the formatted files exist in the workspace directory, and copy the formatted files to the workspace directory.
Table of Contents
I am a big fan of Bazel. I am also a big fan of automatic code formatters and linters. So, whenever I begin a project, I start with a
WORKSPACE file and a
BUILD.bazel file. I use Buildifier to format my Starlark files keeping everything nice and tidy. While running Buildifier from my favorite text editor (vim using ALE) works great, it is not a bullet-proof solution. Updates from other sources (e.g., automated scripts, other contributors who may not have Buildifier installed) leave open the possibility of poorly formatted code merging to the main branch.
Folks have solved this problem in several ways. The most common is to add steps to their continuous integration (CI) process that run the code formatters and linters across the source tree. Anything that does not pass muster is automatically updated, or the original merge fails. If the merge fails, the author fixes the code and resubmits the changes. Checking for poorly formatted code in one’s CI works just fine. First, anything except the most trivial project should have a build-test PR merge check. Second, the author should ensure that the submitted code passes all required checks.
The biggest fault in this workflow is that the feedback to the author is pretty late in the cycle. Wouldn’t it be great to get this feedback when building and testing their code before submitting it to the CI system? I think it would. That is why I have written several Bazel rulesets (e.g., updatesrc, rules_swiftformat) to shorten this feedback loop by integrating it into the normal build-test development cycle. With this article, I would like to introduce bzlformat, a Bazel ruleset that formats, tests, and updates Starlark files.
bzlformat for your Bazel project
The following provides a quick introduction on how to configure and use
bzlformat. For more information, check out the how-to quickstart, the API documentation, and the examples.
1. Configure your workspace.
Add the following to your
WORKSPACE file to download and configure cgrindel/bazel-starlib, the repository that hosts
NOTE: While there are other rulesets in this repository, the focus of this article will be on using
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "cgrindel_bazel_starlib", sha256 = "fc2ee0fce914e3aee1a6af460d4ba1eed9d82e8125294d14e7d3f236d4a10a5d", strip_prefix = "bazel-starlib-0.3.2", urls = [ "http://github.com/cgrindel/bazel-starlib/archive/v0.3.2.tar.gz", ], ) load("@cgrindel_bazel_starlib//:deps.bzl", "bazel_starlib_dependencies") bazel_starlib_dependencies() load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") bazel_skylib_workspace()
Next, add the following to download and configure a prebuilt version of Buildifier using keith/buildifier-prebuilt.
load("@buildifier_prebuilt//:deps.bzl", "buildifier_prebuilt_deps") buildifier_prebuilt_deps() load("@buildifier_prebuilt//:defs.bzl", "buildifier_prebuilt_register_toolchains", "buildtools_assets") buildifier_prebuilt_register_toolchains()
2. Update the build file at your workspace root.
At the root of your workspace, create a Bazel build file, if you don’t have one. Add the following:
load( "@cgrindel_bazel_starlib//bzlformat:defs.bzl", "bzlformat_missing_pkgs", "bzlformat_pkg", ) load( "@cgrindel_bazel_starlib//updatesrc:defs.bzl", "updatesrc_update_all", ) # Ensures that the Starlark files in this package are formatted properly. bzlformat_pkg( name = "bzlformat", ) # Provides targets to find, test, and fix any Bazel packages that are missing bzlformat_pkg # declarations. # # bzlformat_missing_pkgs_find: Find and report any Bazel packages missing the bzlformat_pkg # declaration. # bzlformat_missing_pkgs_test: Like find except it fails if any missing packages are found. This is # useful to run in CI tests to ensure that all is well. # bzlformat_missing_pkgs_fix: Adds bzlformat_pkg declarations to any packages that are missing # the declaration. bzlformat_missing_pkgs( name = "bzlformat_missing_pkgs", ) # Define a runnable target to execute all of the updatesrc_update targets # that are defined in your workspace. updatesrc_update_all( name = "update_all", targets_to_run = [ # Fix the Bazel packages when we update our source files from build outputs. ":bzlformat_missing_pkgs_fix", ], )
bzlformat_pkg macro defines targets for a Bazel package that will format the Starlark source files, test that the formatted files are in the workspace directory, and copies the formatted files to the workspace directory.
bzlformat_missing_pkgs macro defines executable targets that find, test, and fix Bazel packages missing a
updatesrc_update_all macro defines a runnable target that copies all of the formatted Starlark source files to the workspace directory. We add a reference to the
:bzlformat_missing_pkgs_fix target to fix the appropriate Bazel packages when
bazel run //:update_all executes.
bzlformat_pkg to every Bazel package.
Next, we need to add
bzlformat_pkg declarations to every Bazel package. The quickest way to do so is to execute
bazel run //:bzlformat_missing_pkgs_fix or
bazel run //:update_all.
# Update the world, including any bzlformat_pkg fixes $ bazel run //:update_all
4. Format, Update, and Test
From the command line, you can format the Starlark source files, copy them back to the workspace directory and execute the tests that ensure the formatted sources are in the workspace directory.
# Format the Starlark source files and copy the formatted files back to the workspace directory $ bazel run //:update_all # Execute all of your tests, including the formatting checks $ bazel test //...
5. (Optional) Update Your CI Test Runs
To ensure that all of your Bazel packages are monitored by
bzlformat, add a call to
bazel run //:bzlformat_missing_pkgs_test to your CI test runs. If any Bazel packages are missing
bzlformat_pkg declarations, this executable target will fail (i.e., exit with a non-zero value).
# Add this to your CI test runs. $ bazel run //:bzlformat_missing_pkgs_test
If it does fail, run
bazel run //:bzlformat_missing_pkgs_fix and execute your tests (
bazel test //...).
How It Works
The workhorse for the
bzlformat ruleset is
bzlformat_pkgmacro defines a bzlformat_format declaration for each Starlark file (i.e.,
BUILD.bazel) in the Bazel package. Each of these targets is a build-time action that formats the source file and outputs the file to the appropriate Bazel output directory.
bzlformat_pkgmacro defines a diff_test target for each Starlark file comparing the source file with the formatted file. If the files differ in any way, the test fails.
bzlformat_pkgmacro defines a single updatesrc_update executable target. When executed, it copies all of the formatted Starlark files in the Bazel package to the source directory.
The updatesrc_update_all target executes a Bazel query looking for all of the updatesrc_update targets in your workspace, then runs each one. It will also execute any executable targets listed in the targets_to_run attribute.
The bzlformat_missing_pkgs macro defines three targets:
bzlformat_missing_pkgs_find: Executable target that reports any Bazel packages missing a
bzlformat_missing_pkgs_test: Executable target that does the same query as
bzlformat_missing_pkgs_findfailing if any missing packages are detected. This target is typically run as a step in a CI workflow.
bzlformat_missing_pkgs_fix: Executable target that adds
bzlformat_pkgdeclarations to any Bazel packages missing one.
If you have gotten this far, I hope I have piqued your interest in bzlformat. If you have any questions or run into any issues, don’t hesitate to submit an issue.