Pigweed is an open-source collection of libraries from Google that enables fast and reliable development for embedded systems. Last September, Pigweed decided to migrate from GN to Bazel as their primary build system based on the belief that Bazel has great potential to improve embedded developers' productivity and mental wellbeing. Since then, the Pigweed team collaborated closely with the Bazel team on many improvements to make Bazel a more powerful build system not just for Pigweed developers, but also the general Bazel ecosystem.
Today, we're excited to announce the launch of the Pigweed SDK (preview) with native Bazel support. Additionally, Raspberry Pi is launching their new RP2350 microcontroller with native Bazel support in their official Raspberry Pi Pico SDK—contributed by Pigweed with help from the Bazel team. We believe this is the world’s first microchip launch with public Bazel support on launch day!
Why Bazel for embedded?
Pigweed believes Bazel has the potential to be a great build system for embedded developers and we’d like to tell you why. Nothing is perfect, however. We’ll also discuss why Bazel may not be the optimal choice for your project. First, Bazel offers the following benefits:
- Easy automatic developer setup - Embedded projects are infamous for requiring manual, error-prone installation of custom build tooling and compilers. In contrast, Bazel natively supports downloading and configuring the tooling needed to build your firmware project, including C++ and Rust compilers, Go runtimes, Python, and more. Bazel also selects the appropriate tools for the development platform– be it Linux, Mac, or Windows. This significantly reduces the time to onboard new engineers, who merely clone a repo, run Bazel, and are ready to go.
- Fundamentally reliable builds - Embedded developers may be familiar with needing to clean their build environment to fix mysterious local build failures. In contrast, Bazel’s incremental builds and tests are always correct by design, eliminating the need to clean the build in nearly all cases.** **This is because Bazel automatically downloads correct toolchains and dependencies, and stages builds in a sandbox to prevent the local environment from influencing results. This means builds are reliably reproducible no matter where and when they run.
- Ultra-fast builds - Large embedded projects have large builds, especially when they involve multiple architectures and devices. Bazel, owing to its Google-scale heritage compiling billion-line codebases, is built for this. In addition to fast local builds, Bazel’s fundamentally reliable design natively supports remote caching and remote builds.
- Seamless multi-platform, multi-architecture builds - Large embedded projects can have multiple firmware images across different architectures (Cortex-M, Xtensa DSP, RISC-V, etc) in the same product. Bazel natively supports modeling multi-platform builds and compiling with the right toolchains at the right times, including dependencies between build outputs for different platforms. Pigweed (and projects built on Pigweed) leverages this plus features like transitions, label flags, and modular C++ toolchains to make embedded builds clean, correct, and ergonomic.
- Batteries-included multi-language support - Large embedded projects typically have code in multiple languages: firmware in C, C++, or Rust; and host-side tooling in Python, Go, Java, or TypeScript. Pigweed, for example, uses all of these languages. Bazel unifies all these languages with high-quality build rules maintained by the vibrant Bazel community. This reduces the investment Pigweed developers must make to maintain correct language support and enables cross-language dependencies and build flows. For example, Python-based factory tooling can seamlessly depend on C++ and Rust firmware images.
- Easily compose Bazel-based projects - Large embedded projects often have dependencies that have their own repository and build. Bazel's new Bzlmod dependency management system lets projects depend on other Bazel projects. Bzlmod keeps track of transitive dependencies and their versions, attempts to automatically resolve them, notifies you of conflicts, and provides tools for analyzing the dependency graph. This dramatically simplifies managing dependencies on large external codebases like protobuf or Pigweed. Adding a build-level dependency on a multi-language project becomes as simple as depending on another PyPI or maven package. Here's a real-life example of how it simplified declaring dependencies in one of Pigweed's sample repos.
- Container-free development - Large embedded projects commonly bundle their dependencies in containers for repeatability. One challenge with this approach is the difficulty of communicating with devices over USB. Another challenge: Windows-only embedded vendor tooling that needs to operate on files inside the container. Bazel’s automatic hermetic environment setup eliminates the need for containers and the associated toil on embedded projects.
Other examples include a query system to interrogate the build graph, a system for custom processing the build graph (aspects), and more you can read about in the documentation. Try Pigweed’s new Sense tutorial to see many of the above features in action.
Bazel wasn’t always good for embedded; what changed?
Bazel was originally designed to build applications that run on Google’s server fleet. At the time, the fleet was x86 Linux machines, so Bazel made many simplifying assumptions about platform uniformity. Those assumptions made Bazel challenging to use for embedded development. While some teams figured out ways to twist and bend Bazel’s machinery for embedded builds, it wasn't a natural fit. Since then, software has become more multi-platform for nearly all developers, and Bazel evolved accordingly—growing a set of APIs that better suit embedded development. These include:
- Platforms, which enable modeling both the machines you want to target and the machines that cross-compile and run tests.
- Transitions, which enable declaring which build units target which devices. For example: a test runner built for Linux uploads a binary to an embedded device. Transitions can also adjust compiler settings or any other configuration.
- Arch-specific dependencies, which enable declaring custom dependencies based on what machine the build targets.
- Label flags, which enable injecting project specific code that frameworks like Pigweed can seamlessly integrate.
Pigweed recognized the potential of these features and started a close collaboration with the Bazel team to stitch them into an elegant end-to-end developer experience. This includes new features like platform-based flags—which automatically sets machine-specific configuration so you don't have to—and platform_data which makes switching architectures within a build easy.
Pigweed also pushed forward Bazel's C++ ecosystem by designing and upstreaming new toolchain configuration rules for better composability and modularity. This helps Pigweed (or any embedded team) rapidly build C++ toolchains for several devices from a common set of building blocks. Stay tuned for a future blog post that unpacks the story of these new toolchain rules in more detail.
Why not Bazel for embedded?
While we’re excited about the potential of Bazel in the embedded space, today it’s best suited for larger teams willing to make the upfront investment in infrastructure and education, or for smaller teams who are already familiar with Bazel and its tradeoffs. The additional workflows Bazel enables add complexity beyond a simple Make build that isn’t for everyone. Furthermore, the onboarding experience for embedded projects isn’t yet as smooth or documented as we want it to be (but this will improve over time); for example, toolchain setup, while hugely improved through the Pigweed and Bazel collaboration, is not mature yet.
If you’re interested in Bazel for your embedded project, but have questions or concerns, please drop by the Pigweed Chat (for embedded-specific questions) or Bazel Chat (for general Bazel questions).
Looking forward
Both the Bazel and Pigweed teams believe Bazel can become widely recognized as a great choice for developing embedded software, at all scales from hobbyists to manufacturers shipping millions of units. The work described here provides the foundation for this. Stay tuned as Bazel and Pigweed build more in the future!