One of the premises of Bazel is to provide reproducible, hermetic builds, thus you shouldn’t depend on whatever is installed on the host OS and all the dependencies typically managed by Bazel directly.
However, if you want to build plugins for LLVM (or any other project really), then you should link against the specific versions installed on the user’s system.
As I’m working on such a plugin, it’s been a long “dream” of mine to migrate to Bazel for the many benefits it provides. Over time, the existing build system (CMake) has grown its capabilities and I have certain requirements for how the builds should work.
Namely:
the plugin must work on different versions of OS (Ubuntu 20.xx-24.xx, macOS)
the plugin must support different versions of LLVM, which are different on each OS (e.g., LLVM 12 on Ubuntu 20.04, LLVM 16, 17, 18 on Ubuntu 24.04 etc)
the plugin must be linking against the system libraries due to the ABI requirements
the build system should support multiple versions at the same time
None of these are necessarily hard or impossible with Bazel, but the devil is always in the details.
What follows is my take on solving this problem.
Source code is available here https://github.com/AlexDenisov/bazel-llvm-plugin.
Following the Cunningham's Law I claim that there is no better way to do it.
Detecting available LLVM versions
Third-party dependencies in Bazel are typically coming in a form of external repositories, thus all supported LLVM versions must be defined in MODULE.bazel
upfront. However, what happens if the version is not supported or not installed on the host OS? In this case, these repositories must be defined dynamically.
To do so, first we need to define a custom dynamic repository which will check which versions are installed on the host OS and store this information in a global variable available for later use by different parts of the build system:
Which must be defined in MODULE.bazel:
Defining LLVM repositories
Now, as we know which versions are available installed on the host system, we can define LLVM repositories which will expose libLLVM.so
and all the needed headers.
This part requires a dynamic module extension which will either define a real repository, or will define a “fake” empty repo. This is needed so that all the repositories can be later defined in MODULE.bazel
safely.
How we can tell Bazel that these repos are available for consumption:
Defining plugin targets
Now, the rest is rather trivial. We can define all the plugin libraries depending on the LLVM versions available on the host OS:
Defining test targets
Obviously, we must have tests for the plugin. This is also relatively trivial, we need to define a test case for each available LLVM versions as well, thus producing NxM
tests where N
is the number of tests and M
is the number of LLVM versions.
Conclusion
With all the little pieces above, the builds are now completely transparent and smooth for the end user:
Full working example can be found here: https://github.com/AlexDenisov/bazel-llvm-plugin