Bazel build compatibility patterns#

This document describes the Bazel patterns Pigweed uses to express that a build target is compatible with a platform. The main motivation is to enable maintainable wildcard builds of upstream Pigweed for non-host platforms:

bazelisk build --config=rp2040 //...

The bulk of this document describes recommended patterns for expressing compatibility in various scenarios. For context, we also discuss alternative patterns and why they should be avoided. For the implementation plan, see Are we there yet?.

See Appendix: Background and the Platforms documentation for more context.

Intended audience#

This document is targeted at upstream Pigweed developers. The patterns described here are suitable for downstream projects, too, but downstream projects can employ a broader variety of approaches. Because Pigweed is middleware that must preserve users’ flexibility in configuring it, we need to be more careful.

This document assumes you’re familiar with regular Bazel usage, but perhaps not Bazel’s build configurability primitives.

Are we there yet?#

As of this writing, upstream Pigweed does not yet follow the best practices recommended below. b/344654805 tracks fixing this.

Here’s a high-level roadmap for the recommendations’ implementation:

  1. Implement the “syntactic sugar” referenced in the rest of this doc: boolean_constraint_value, incompatible_with_mcu, etc.

  2. b/342691352 | “Platforms should set backends for Pigweed facades through label flags”. For each facade,

  3. b/343487589 | Retire the Board and chipset constraint settings.

Appendix: Background#

Why wildcard builds?#

Pigweed is generic microcontroller middleware: you can use Pigweed to accelerate development on any microcontroller platform. In addition, Pigweed provides explicit support for a number of specific hardware platforms, such as the Raspberry Pi RP2040 or STM32f429i Discovery Board. For these specific platforms, every Pigweed module falls into one of three buckets:

  • works with the platform, or,

  • is not intended to work with the platform, because the platform lacks the relevant capabilities (e.g., the pw_spi_mcuxpresso module specifically supports NXP chips, and is not intended to work with the Raspberry Pi Pico).

  • should work but doesn’t yet; that’s a bug or missing feature in Pigweed.

Bazel’s wildcard builds provide a nice way to ensure each Pigweed build target is known to fall into one of those three buckets. If you run:

bazelisk build --config=rp2040 //...

Bazel will attempt to build all Pigweed build targets for the specified platform, with the exception of targets that are explicitly annotated as not compatible with it. Such incompatible targets will be automatically skipped.

Challenge: designing constraint_values#

As noted above, for wildcard builds to work we need to annotate some targets as not compatible with certain platforms. This is done through the target_compatible_with attribute, which is set to a list of constraint_values (essentially, enum values). For example, here’s a target only compatible with Linux:

cc_library(
  name = "pw_digital_io_linux",
  target_compatible_with = ["@platforms//os:linux"],
)

If the platform lists all the constraint_values that appear in the target’s target_compatible_with attribute, then the target is compatible; otherwise, it’s incompatible, and will be skipped.

If this sounds a little abstract, that’s because it is! Bazel is not very opinionated about what the constraint_values actually represent. There are only two sets of canonical constraint_values, @platforms//os and @platforms//cpu. Here are some possible choices—not necessarily good ones, but all seen in the wild:

  • A set of constraint_values representing RTOSes:

    • @pigweed//pw_build/constraints/rtos:embos

    • @pigweed//pw_build/constraints/rtos:freertos

  • A set of representing individual boards:

    • @pigweed//pw_build/constraints/board:mimxrt595_evk

    • @pigweed//pw_build/constraints/board:stm32f429i-disc1

  • A pair of constraint values associated with a single module:

    • @pigweed//pw_spi_mcuxpresso:compatible (the module is by definition compatible with any platform containing this constraint value)

    • @pigweed//pw_spi_mcuxpresso:incompatible

There are many more possible structures.

What about constraint_settings?#

Final piece of background: we mentioned above that constraint_values are a bit like enum values. The enums themselves (groups of constraint_values) are called constraint_settings.

Each constraint_value belongs to a constraint_setting, and a platform may specify at most one value from each setting.

Guiding principles#

These are the principles that guided the selection of the recommended patterns:

  • Be consistent. Make the patterns for different use cases as similar to each other as possible.

  • Make compatibility granular. Avoid making assumptions about what sets of backends or HAL modules will be simultaneously compatible with the same platforms.

  • Minimize the amount of boilerplate that downstream users need to put up with.

  • Support the autodetected host platform. That is, ensure bazel build --platforms=@platforms//host //... works. This is necessary internally (for google3) and arguably more convenient for downstream users generally.