Bazel tips

Bazel cheat sheet

Commands

TaskCommand
Build everything (slow)bazel build //...:all
Build tests not marked as manualbazel build --build_tag_filters=-manual //...:all
Build a specific library targetbazel build //lib/Dialect/Polynomial/IR\:Dialect
Build heir-optbazel build //tools:heir-opt
List targets in a directorybazel query //lib/Dialect/Polynomial/IR\:all
Run all testsbazel test //...:all
Run a subset of tests by pathbazel test //tests/Dialect/...
Run unit testsbazel test //lib/...:all
Run lit and end-to-end testsbazel test //tests/...:all
Run only lit tests (fast)bazel test $(bazel query 'filter("\.mlir\.test$", //...)')
Run end-to-end tests (slow)bazel test //tests/Examples/...:all
Clear build cachebazel clean
Clear build cache and system analysis (where is clang, etc.)bazel clean --expunge

Bazel flags

These flags are for bazel, and so they go before the target, and before any -- that separates flags for bazel from flags for the binary target being run, i.e., heir-opt or heir-translate. These can also be persistently configured in a .bazelrc file.

FlagDescription
-c optBuild with optimizations enabled (-O3).
-c dbgBuild with debug symbols (better stack traces)
-c fastbuildSets compiler flags to optimize for fast build time
--subcommandsShows subcommands that bazel invokes while building (e.g., raw clang or heir-opt commands run.
--test_tag_filters and --build_tag_filtersFilter targets by the tag set on the build target. E.g., --build_tag_filters=-manual will exclude any targets with the manual tag.

BUILD file formatting

The buildifier tool can be used to format BUILD files. You can download the latest Buildifier release from the Bazel Release Page. See IDE configuration for tips on integrating this with your IDE.

Avoiding rebuilds

Bazel is notoriously fickle when it comes to deciding whether a full rebuild is necessary, which is bad for HEIR because rebuilding LLVM from scratch takes 15 minutes or more. We try to avoid this as much as possible by setting default options in the project root’s .bazelrc.

The main things that cause a rebuild are:

  • Pulling an update from the remote main branch. The pinned LLVM commit is updated roughly once a day via automation (example commit) and changing that commit triggers a full rebuild of LLVM.
  • A change to the .bazelrc that implicitly causes a flag change. Note HEIR has its own project-specific .bazelrc in the root directory.
  • A change to the command-line flags passed to bazel, e.g., -c opt vs -c dbg for optimization level and debug symbols. The default is -c dbg, and you may want to override this to optimize performance of generated code. For example, the OpenFHE backend generates much faster code when compiled with -c opt.
  • A change to relevant command-line variables, such as PATH, which is avoided by the incompatible_strict_action_env flag. Note activating a python virtualenv triggers a PATH change. The default is incompatible_strict_action_env=true, and you would override this in the event that you want your shell’s environment variables to change and be inherited by bazel.

Pointing HEIR to a local clone of llvm-project

Occasionally changes in HEIR will need to be made in tandem with upstream changes in MLIR. In particular, we occasionally find upstream bugs that only occur with HEIR passes, and we are the primary owners/users of the upstream polynomial dialect.

To tell bazel to use a local clone of llvm-project instead of a pinned commit hash, replace bazel/import_llvm.bzl with the following file:

cat > bazel/import_llvm.bzl << EOF
"""Provides the repository macro to import LLVM."""

def import_llvm(name):
    """Imports LLVM."""
    native.new_local_repository(
        name = name,
        # this BUILD file is intentionally empty, because the LLVM project
        # internally contains a set of bazel BUILD files overlaying the project.
        build_file_content = "# empty",
        path = "/path/to/llvm-project",
    )
EOF

The next bazel build will require a full rebuild if the checked-out LLVM commit differs from the pinned commit hash in bazel/import_llvm.bzl.

Note that you cannot reuse the LLVM CMake build artifacts in the bazel build. Based on what you’re trying to do, this may require some extra steps.

  • If you just want to run existing MLIR and HEIR tests against local llvm-project changes, you can run the tests from HEIR using bazel test @llvm-project//mlir/...:all. New lit tests can be added in llvm-project’s existing directories and tested this way without a rebuild.
  • If you add new CMake targets in llvm-project, then to incorporate them into HEIR you need to add new bazel targets in llvm-project/utils/bazel/llvm-project-overlay/mlir/BUILD.bazel. This is required if, for example, a new dialect or pass is added in MLIR upstream.

Send any upstream changes to HEIR-relevant MLIR files to @j2kun (Jeremy Kun) who has LLVM commit access and can also suggest additional MLIR reviewers.

Finding upstream dependency targets

Whenever a new dependency is added in C++ or Tablegen, a new bazel BUILD dependency is required, which requires finding the path to the relevant target that provides the file you want. In HEIR the BUILD target should be defined in the same directory as the file you want to depend on (e.g., the targets that provide foo.h are in BUILD in the same directory), but upstream MLIR’s bazel layout is different.

LLVM’s bazel overlay for MLIR is contained in a single file, and so you can manually look there to find the right target. With bazel, if you know the filepath of interested you can also run:

bazel query --keep_going 'same_pkg_direct_rdeps(@llvm-project//mlir:<path>)'

where <path> is the path relative to mlir/ in the llvm-project project root. For example, to find the target that provides mlir/include/mlir/Pass/PassBase.td, run

bazel query --keep_going 'same_pkg_direct_rdeps(@llvm-project//mlir:include/mlir/Pass/PassBase.td)'

And the output will be something like

@llvm-project//mlir:PassBaseTdFiles

You can find more examples and alternative queries at the Bazel query docs.