1.. _docs-bazel-compatibility: 2 3================================== 4Bazel build compatibility patterns 5================================== 6This document describes the Bazel patterns Pigweed uses to express that a build 7target is compatible with a platform. The main motivation is to enable 8maintainable :ref:`wildcard builds <docs-bazel-compatibility-why-wildcard>` of 9upstream Pigweed for non-host platforms: 10 11.. code-block:: sh 12 13 bazelisk build --config=rp2040 //... 14 15The bulk of this document describes :ref:`recommended patterns 16<docs-bazel-compatibility-recommended>` for expressing compatibility in various 17scenarios. For context, we also discuss :ref:`alternative patterns 18<docs-bazel-compatibility-not-recommended>` and why they should be avoided. 19For the implementation plan, see 20:ref:`docs-bazel-compatibility-implementation-plan`. 21 22See :ref:`docs-bazel-compatibility-background` and the `Platforms documentation 23<https://bazel.build/extending/platforms>`_ for more context. 24 25----------------- 26Intended audience 27----------------- 28This document is targeted at *upstream Pigweed developers*. The patterns 29described here are suitable for downstream projects, too, but downstream 30projects can employ a broader variety of approaches. Because Pigweed is 31middleware that must preserve users' flexibility in configuring it, we need to 32be more careful. 33 34This document assumes you're familiar with regular Bazel usage, but perhaps not 35Bazel's build configurability primitives. 36 37.. _docs-bazel-compatibility-recommended: 38 39---------------------------------- 40Recommended compatibility patterns 41---------------------------------- 42 43Summary 44======= 45Here's a short but complete summary of the recommendations. 46 47For library authors 48------------------- 49A library is anything represented as a ``cc_library``, ``rust_library``, or 50similar target that other code is expected to depend on. 51 52#. Rely on :ref:`your dependencies' constraints 53 <docs-bazel-compatibility-inherited>` (which you implicitly inherit) 54 whenever possible. 55#. Otherwise, use one of the :ref:`well-known constraints 56 <docs-bazel-compatibility-well-known>`. 57#. If no well-known constraint fits the bill, introduce a :ref:`module-specific 58 constraint <docs-bazel-compatibility-module-specific>`. 59 60For facade authors 61------------------ 62:ref:`Facade <docs-facades>` authors are library authors, too. But there are 63some special considerations that apply to facades. 64 65#. :ref:`The facade's default backend should be unspecified 66 <docs-bazel-compatibility-facade-default-backend>`. 67#. If you want to make it easy for users to select a group of backends for 68 related facades simultaneously, :ref:`provide dicts of such backends 69 <docs-bazel-compatibility-facade-backend-dict>` for their use. :ref:`Don't 70 provide multiplexer targets <docs-bazel-compatibility-multiplexer>`. 71#. Whenever possible, :ref:`ensure backends fully implement a facade's 72 interface <docs-bazel-compatibility-facade-backend-interface>`. 73#. When implementing backend-specific tests, you may :ref:`introduce a config 74 setting consuming a label flag <docs-bazel-compatibility-config-setting>`. 75 76For SDK build authors 77--------------------- 78 79#. :ref:`Provide config headers through a default-incompatible label flag 80 <docs-bazel-compatibility-incompatible-label-flag>`. 81 82Patterns for library authors 83============================ 84 85.. _docs-bazel-compatibility-inherited: 86 87Inherited incompatibility 88------------------------- 89Targets that transitively depend on incompatible targets are themselves 90considered incompatible. This implies that many build targets do not need a 91``target_compatible_with`` attribute. 92 93Example: your target uses ``//pw_stream:socket_stream`` for RPC communication, 94and ``socket_stream`` requires POSIX sockets. Your target *should not* try to 95express this compatibility restriction through the ``target_compatible_with`` 96attribute. It will automatically inherit it from ``socket_stream``! 97 98A particularly important special case are label flags which by default point to 99an always-incompatible target, often :ref:`provided by SDKs 100<docs-bazel-compatibility-incompatible-label-flag>`. 101 102Asserting compatibility 103^^^^^^^^^^^^^^^^^^^^^^^ 104Inherited incompatibility is very convenient, but can be dangerous. A change in 105the transitive dependencies can unexpectedly make a top-level target 106incompatible with a platform it should build for. How to minimize this risk? 107 108For tests, the risk is relatively low because ``bazel test`` will print a list 109of SKIPPED tests as part of its output. 110 111.. note:: 112 113 TODO: https://pwbug.dev/347752345 - Come up with a way to mitigate the risk 114 of accidentally skipping tests due to incompatibility. 115 116For final firmware images, assert that the image is compatible with the 117intended platform by explicitly listing it in CI invocations: 118 119.. code-block:: sh 120 121 # //pw_system:system_example is a binary that should build for this 122 # platform. 123 bazelisk build --config=rp2040 //... //pw_system:system_example 124 125.. _docs-bazel-compatibility-well-known: 126 127Well-known constraints 128---------------------- 129If we introduced a separate constraint value for every module that's not purely 130algorithmic, downstream users' platforms would become needlessly verbose. For 131certain well-defined categories of dependency we will introduce "well-known" 132constraint values that may be reused by multiple modules. 133 134.. tip:: 135 136 When a module's assumptions about the underlying platform are fully captured 137 by one of these well-known constraints, reuse them instead of creating a 138 module-specific constraint. 139 140.. _docs-bazel-compatibility-well-known-os: 141 142OS-specific modules 143^^^^^^^^^^^^^^^^^^^ 144Some build targets are compatible only with specific operating systems. For 145example, ``pw_digital_io_linux`` uses Linux syscalls. Such targets should be 146annotated with the appropriate canonical OS ``constraint_value`` from the 147`platforms repo <https://github.com/bazelbuild/platforms>`_: 148 149.. code-block:: python 150 151 cc_library( 152 name = "pw_digital_io_linux", 153 target_compatible_with = ["@platforms//os:linux"], 154 ) 155 156Cross-platform modules requiring an OS 157^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 158Some build targets are only intended for use on platforms with a fully-featured 159OS (i.e., not on microcontrollers, but on the developer's laptop or 160workstation, or on an embedded Linux system), *but* are cross-platform and not 161restricted to one *particular* OS. Example: an integration test written in Go 162that starts subprocesses using the ``os/exec`` standard library package. 163 164For these cross-platform targets, use the ``incompatible_with_mcu`` helper: 165 166.. code-block:: python 167 168 load("@pigweed//pw_build:compatibility.bzl", "incompatible_with_mcu") 169 170 go_test( 171 name = "integration_test", 172 target_compatible_with = incompatible_with_mcu(), 173 ) 174 175.. note:: 176 177 RTOSes are not OSes in the sense of this section. See 178 :ref:`docs-bazel-compatibility-rtos`. 179 180CPU-specific modules (rare) 181^^^^^^^^^^^^^^^^^^^^^^^^^^^ 182Some build targets are only intended for particular CPU architectures. In this 183case, use the canonical CPU ``constraint_value`` from the 184`platforms repo <https://github.com/bazelbuild/platforms>`_: 185 186.. code-block:: python 187 188 cc_library( 189 name = "pw_interrupt_cortex_m", 190 # Compatible only with Cortex-M processors. 191 target_compatible_with = select({ 192 "@platforms//cpu:armv6-m": [], 193 "@platforms//cpu:armv7-m": [], 194 "@platforms//cpu:armv7e-m": [], 195 "@platforms//cpu:armv7e-mf": [], 196 "@platforms//cpu:armv8-m": [], 197 ) 198 199SDK-provided constraints 200^^^^^^^^^^^^^^^^^^^^^^^^ 201If a module depends on a third-party SDK, and that SDK has ``BUILD.bazel`` 202files that define ``constraint_values``, feel free to use those authoritative 203values to indicate target compatibility. 204 205.. code-block:: python 206 207 cc_library( 208 name = "pw_digital_io_rp2040", 209 deps = [ 210 # Depends on the Pico SDK. 211 "@pico-sdk//src/rp2_common/pico_stdlib", 212 "@pico-sdk//src/rp2_common/hardware_gpio", 213 ], 214 # The Pico SDK provides authoritative constraint_values. 215 target_compatible_with = ["@pico-sdk//bazel/constraint:rp2040"], 216 ) 217 218.. note:: 219 220 This also applies to SDKs or libraries for which Pigweed provides 221 ``BUILD.bazel`` files in our ``//third_party`` directory (e.g., FreeRTOS or 222 stm32cube). 223 224 225 226.. _docs-bazel-compatibility-module-specific: 227 228Module-specific constraints 229--------------------------- 230Many Pigweed modules are purely algorithmic: they make no assumptions about the 231underlying platform. But many modules *do* make assumptions, sometimes quite 232restrictive ones. For example, the ``pw_spi_mcuxpresso`` library includes 233headers from the NXP SDK and will only work for certain NXP chips. 234 235For any library that does make such assumptions, and these assumptions are not 236captured by one of the :ref:`well-known constraints 237<docs-bazel-compatibility-well-known>`, the recommended pattern is to define a 238"boolean" ``constraint_setting`` to express compatibility. We introduce some 239syntactic sugar (:ref:`module-pw_build-bazel-boolean_constraint_value`) for 240making this concise. 241 242Example: 243 244.. code-block:: python 245 246 # pw_spi_mcuxpresso/BUILD.bazel 247 load("@pigweed//pw_build:compatibility.bzl", "boolean_constraint_value") 248 249 boolean_constraint_value( 250 name = "compatible", 251 ) 252 253 cc_library( 254 name = "pw_spi_mcuxpresso", 255 # srcs, deps, etc omitted 256 target_compatible_with = [":compatible"], 257 ) 258 259Usage in platforms 260^^^^^^^^^^^^^^^^^^ 261To use this module, a platform must include the constraint value: 262 263.. code-block:: python 264 265 platform( 266 name = "downstream_platform", 267 constraint_values = ["@pigweed//pw_spi_mcuxpresso:compatible"], 268 ) 269 270If the library happens to be a facade backend, then the platform will have to 271*both* point the label flag to the backend and list the ``constraint_value``. 272 273.. code-block:: python 274 275 platform( 276 name = "downstream_platform", 277 constraint_values = ["@pigweed//pw_sys_io_stm32cube:backend"], 278 flags = [ 279 "--@pigweed//pw_sys_io:backend=@pigweed//pw_sys_io_stm32cube", 280 ], 281 ) 282 283.. tip:: 284 285 Just because a library is a facade backend doesn't mean it has any 286 compatibility restrictions. Many backends (e.g., ``pw_assert_log``) have no 287 such restrictions, and many others rely only on the well-known constraints. 288 So, the number of ``constraint_values`` that need to be added to the typical 289 downstream platform is substantially smaller than the number of configured 290 backends. 291 292Special case: host-compatible platform specific modules 293^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 294Some modules may require platforms to explicitly assert that they support them, 295but also work on host platforms by default. An example of this is 296``pw_stream:socket_stream``. Use the following pattern: 297 298.. code-block:: python 299 300 # pw_stream/BUILD.bazel 301 load("@pigweed//pw_build:compatibility.bzl", "boolean_constraint_value", "incompatible_with_mcu") 302 303 boolean_constraint_value( 304 name = "socket_stream_compatible", 305 ) 306 307 cc_library( 308 name = "socket_stream", 309 # Compatible with host platforms, and any platform that explicitly 310 # lists `@pigweed//pw_stream:socket_stream_compatible` among its 311 # constraint_values. 312 target_compatible_with = incompatible_with_mcu(unless_platform_has=":socket_stream_compatible"), 313 ) 314 315Patterns for facade authors 316=========================== 317 318.. _docs-bazel-compatibility-facade-default-backend: 319 320Don't provide default facade backends for device 321------------------------------------------------ 322If the facade has no host-compatible backend, its default backend should be 323``//pw_build:unspecified_backend``: 324 325.. code-block:: python 326 327 label_flag( 328 name = "backend", 329 build_setting_default = "//pw_build:unspecified_backend", 330 ) 331 332Otherwise, use the following pattern: 333 334.. code-block:: python 335 336 load("@pigweed//pw_build:compatibility.bzl", "host_backend_alias") 337 338 label_flag( 339 name = "backend", 340 build_setting_default = ":unspecified_backend", 341 ) 342 343 host_backend_alias( 344 name = "unspecified_backend", 345 # "backend" points to the target implementing the host-compatible backend. 346 backend = ":host_backend", 347 ) 348 349This ensures that: 350 351* If the target platform did not explicitly set a backend for a facade, that 352 facade (and any target that transitively depends on it) is considered 353 incompatible. 354* *Except for the host platform*, which receives the host backend 355 by default. 356 357Following this pattern implies that we don't need a Bazel equivalent of GN's 358``enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""`` (`example 359<https://cs.opensource.google/pigweed/pigweed/+/main:pw_i2c/BUILD.gn;l=136-145;drc=afef6c3c7de6f5a84465aad469a89556d0b34fbb>`__). 360In Bazel, every build target is "enabled" if and only if all facades it 361transitively depends on have a backend set. 362 363Providing multiplexer targets is an alternative way to set default facade 364backends, but :ref:`is not recommended in upstream Pigweed 365<docs-bazel-compatibility-multiplexer>`. One exception is if your facade needs 366a different default host backend depending on the OS. So, the following 367is OK: 368 369.. code-block:: python 370 371 # Host backend that's OS-specific. 372 alias( 373 name = "host_backend", 374 actual = select({ 375 "@platforms//os:macos": ":macos_backend", 376 "@platforms//os:linux": ":linux_backend", 377 "@platforms//os:windows": ":windows_backend", 378 "//conditions:default": "//pw_build:unspecified_backend", 379 }), 380 ) 381 382.. _docs-bazel-compatibility-facade-backend-dict: 383 384Provide default backend collections as dicts 385-------------------------------------------- 386In cases like RTOS-specific backends, where the user is expected to want to set 387all of them at once, provide a dict of default backends for them to include in 388their platform definition: 389 390.. code-block:: python 391 392 #//pw_build/backends.bzl (in upstream Pigweed) 393 394 # Dict of typical backends for FreeRTOS. 395 FREERTOS_BACKENDS = { 396 "@pigweed//pw_chrono:system_clock_backend": "@pigweed//pw_chrono_freertos:system_clock", 397 "@pigweed//pw_chrono:system_timer_backend": "@pigweed//pw_chrono_freertos:system_timer", 398 # etc. 399 } 400 401 # User's platform definition (in downstream repo) 402 load("@pigweed//pw_build/backends.bzl", "FREERTOS_BACKENDS", "merge_flags") 403 404 platform( 405 name = "my_freertos_device", 406 flags = merge_flags( 407 base = FREERTOS_BACKENDS, 408 overrides = { 409 # Override one of the default backends. 410 "@pigweed//pw_chrono:system_clock_backend": "//src:my_device:pw_system_clock_backend", 411 # Provide additional backends. 412 "@pigweed//pw_sys_io:backend": "//src:my_device:pw_sys_io_backend", 413 }, 414 ), 415 ) 416 417.. _docs-bazel-compatibility-facade-backend-interface: 418 419Guard backend-dependent interfaces with constraints 420--------------------------------------------------- 421 422What's a backend-dependent interface? 423^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 424We :ref:`officially define a facade <docs-facades>` as "an API contract of a 425module that must be satisfied at compile-time", and a backend as merely "an 426implementation of a facade’s contract." However, a small number of facades do 427not fit this definition, and expose APIs that vary based on the backend 428selected (!!!). Examples: 429 430* ``pw_thread: Thread::join()`` :ref:`may or may not be available 431 <module-pw_thread-detaching-joining>` depending on the selected backend. 432* ``pw_async2``: The ``EPollDispatcher`` offers different APIs from other 433 ``pw_async2::Dispatcher`` backends, and parts of :ref:`module-pw_channel` 434 (:cpp:class:`pw::EpollChannel`) rely on those APIs. 435 436This breaks the invariant that a facade's APIs either are available (if it has 437a backend) or are not available (if it has no backend, in which case targets 438that depend on the facade are incompatible). These facades might have a backend 439and yet (parts of) their APIs are unavailable! 440 441What to do instead? 442^^^^^^^^^^^^^^^^^^^ 443 444Fix the class structrure 445........................ 446If possible, reorganize the class structure so that the facade's API is 447backend-independent, and users who need the additional functionality must 448depend directly on the specific backend that provides this. 449 450This is the correct fix for the ``EPollDispatcher`` case; see `b/342000726 451<https://pwbug.dev/342000726>`__ for more details. 452 453Express the backend-dependent capability through a constraint 454............................................................. 455If the backend-dependent interface cannot be refactored away, guard it using a 456custom constraint. 457 458Let's discuss :ref:`module-pw_thread` as a specific example. The 459backend-dependence of the interface is that ``Thread::join()`` may or may not 460be provided by the backend. 461 462To expose this to the build system, introduce a corresponding constraint: 463 464.. code-block:: python 465 466 # //pw_thread/BUILD.bazel 467 constraint_setting( 468 name = "joinable", 469 # Default appropriate for the autodetected host platform. 470 default_constraint_value = ":threads_are_joinable", 471 ) 472 473 constraint_value( 474 name = "threads_are_joinable", 475 constraint_setting = ":joinable", 476 ) 477 478 constraint_value( 479 name = "threads_are_not_joinable", 480 constraint_setting = ":joinable", 481 ) 482 483Platforms can declare whether threads are joinable or not by including the 484appropriate constraint value in their definitions: 485 486.. code-block:: python 487 488 # //platforms/BUILD.bazel 489 platform( 490 name = "my_device", 491 constraint_values = [ 492 "@pigweed//pw_thread:threads_are_not_joinable", 493 ], 494 ) 495 496Build targets that unconditionally call ``Thread::join()`` (not within a ``#if 497PW_THREAD_JOINING_ENABLED=1``) should be marked compatible with the 498``"@pigweed//pw_thread:threads_are_joinable"`` constraint value: 499 500.. code-block:: python 501 502 cc_library( 503 name = "my_library_requiring_thread_joining", 504 # This library will be incompatible with "//platforms:my_device", on 505 # which threads are not joinable. 506 target_compatible_with = ["@pigweed//pw_thread:threads_are_joinable"], 507 ) 508 509If your library will compile both with and without thread joining (either 510because it doesn't call ``Thread::join()``, or because all such calls are 511guarded by ``#if PW_THREAD_JOINING_ENABLED=1``), you don't need any 512``target_compatible_with`` attribute. 513 514Configuration-dependent interfaces 515^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 516Some facades have interfaces that depend not just on the choice of backend, but 517on their :ref:`module-structure-compile-time-configuration`. We don't have a 518good pattern for these libraries yet. 519 520.. note:: 521 522 TODO: https://pwbug.dev/234872811 - Establish such a pattern. 523 524Patterns for SDK build authors 525============================== 526This section discusses patterns useful when providing a Bazel build for a 527pre-existing library or SDK. 528 529.. _docs-bazel-compatibility-incompatible-label-flag: 530 531Provide config headers through label flags 532------------------------------------------ 533Many libraries used in embedded projects expect configuration to be provided 534through a header file at a predefined include path. For example, FreeRTOS 535expects the user to provide a configuration header that will be included via 536``#include "FreeRTOSConfig.h``. How to handle this when writing a *generic* 537``BUILD.bazel`` file for such a library? 538 539Use the following pattern: 540 541.. code-block:: python 542 543 # //third_party/freertos/freertos.BUILD.bazel 544 545 cc_library( 546 name = "freertos", 547 # srcs, hdrs omitted. 548 deps = [ 549 # freertos has a dependency on :freertos_config. 550 ":freertos_config", 551 ], 552 ) 553 554 # Label flag that points to the cc_library target providing FreeRTOSConfig.h. 555 label_flag( 556 name = "freertos_config", 557 build_setting_default = ":unspecified", 558 ) 559 560 cc_library( 561 name = "unspecified", 562 # The default config is not compatible with any configuration: you can't 563 # build FreeRTOS without choosing a config. 564 target_compatible_with = ["@platforms//:incompatible"], 565 ) 566 567Why is this recommended? 568 569#. The configuration header to use can be selected as part of platform 570 definition, by setting the label flag. This gives the user a lot of 571 flexibility: they can use different headers when building different targets 572 within the same repo. 573#. Any target (test, library, or binary) that depends on the ``freertos`` 574 ``cc_library`` will be considered incompatible with the target platform 575 unless that platform explicitly configured FreeRTOS by setting the 576 ``freertos_config`` label flag. So, if your target's only assumption about 577 the platform is that it supports FreeRTOS, including ``freertos`` in your 578 ``deps`` is all you need to do to express this. 579 580This pattern is not useful in upstrem Pigweed itself, because Pigweed uses a 581more elaborate configuration pattern at the C++ source level. See 582:ref:`module-structure-compile-time-configuration`. 583 584.. _docs-bazel-compatibility-not-recommended: 585 586-------------------------------------- 587Alternative patterns (not recommended) 588-------------------------------------- 589 590This section describes alternative build compatibility patterns that we've used 591or considered in the past. They are **not recommended**. We'll work to remove 592their instances from Pigweed, replacing them with the recommended patterns. 593 594.. _docs-bazel-compatibility-per-facade-constraint-settings: 595 596Per-facade constraint settings (not recommended) 597================================================ 598This approach was once recommended, although it was `never fully rolled out 599<https://pwbug.dev/272090220>`_: 600 601#. For **every facade**, introduce a ``constraint_setting`` (e.g., 602 ``@pigweed//pw_foo:backend_constraint_setting``). This would be done by 603 whoever defines the facade; if it's an upstream facade, upstream Pigweed 604 should define this setting. 605#. For every backend, introduce a corresponding constraint_value (e.g., 606 ``//backends/pw_foo:board1_backend_constraint_value``). This should be done 607 by whoever defines the backend; for backends defined in downstream projects, 608 it's done in that project. 609#. Mark the backend ``target_compatible_with`` its associated ``constraint_value``. 610 611Why is this not recommended 612--------------------------- 613The major difference between this and :ref:`what we're recommending 614<docs-bazel-compatibility-recommended>` is that *every* backend was associated 615with a *unique* ``constraint_value``, regardless of whether the backend imposed 616any constraints on its platform or not. This implied downstream platforms that 617set N backends would also have to list the corresponding N 618``constraint_values``. 619 620The original motivation for per-facade constraint settings is now obsolete. 621They were intended to allow backend selection via multiplexers before 622platform-based flags became available. `More details for the curious 623<https://docs.google.com/document/d/1O4xjnQBDpOxCMhlyzsowfYF3Cjq0fOfWB6hHsmsh-qI/edit?resourcekey=0-0B-fT2s05UYoC4TQIGDyvw&tab=t.0#heading=h.u62b26x3p898>`_. 624 625Where they still exist in upstream Pigweed, these constraint settings will be 626removed (see :ref:`docs-bazel-compatibility-implementation-plan`). 627 628.. _docs-bazel-compatibility-config-setting: 629 630Config setting from label flag (not recommended except for tests) 631================================================================= 632`This pattern <https://pwbug.dev/342691352#comment3>`_ was an attempt to keep 633the central feature of per-facade constraint settings (the selection of a 634particular backend can be detected) without forcing downstream users to list 635``constraint_values`` explicitly in their platforms. A ``config_setting`` is 636defined that detects if a backend was selected through the label flag: 637 638.. code-block:: python 639 640 # pw_sys_io_stm32cube/BUILD.bazel 641 642 config_setting( 643 name = "backend_setting", 644 flag_values = { 645 "@pigweed//pw_sys_io:backend": "@pigweed//pw_sys_io_stm32cube", 646 }, 647 ) 648 649 cc_library( 650 name = "pw_sys_io_stm32cube", 651 target_compatible_with = select({ 652 ":backend_setting": [], 653 "//conditions:default": ["@platforms//:incompatible"], 654 }), 655 ) 656 657Why is this not recommended 658--------------------------- 659#. We're really insisting on setting the label flag directly to the backend. In 660 particular, we disallow patterns like "point the ``label_flag`` to an 661 ``alias`` that may resolve to different backends based on a ``select``" 662 (because `the config_setting in the above example will be false in that case 663 <https://github.com/bazelbuild/bazel/issues/21189>`_). 664#. It's a special pattern just for facade backends. Libraries which need to 665 restrict compatibility but are not facade backends cannot use it. 666#. Using the ``config_setting`` in ``target_compatible_with`` requires the 667 weird ``select`` trick shown above. It's not very ergonomic, and definitely 668 surprising. 669 670When to use it anyway 671--------------------- 672We may resort to defining private ``config_settings`` following this pattern to 673solve special problems like `b/336843458 <https://pwbug.dev/336843458>`_ | 674"Bazel tests using pw_unit_test_light can still rely on GoogleTest" or 675`pw_malloc tests 676<https://cs.opensource.google/pigweed/pigweed/+/main:pw_malloc/BUILD.gn;l=190-191;drc=96313b7cc138b0c49742e151927e0d3a013f8b47>`_. 677 678In addition, some tests are backend-specific (directly include backend 679headers). The most common example are tests that depend on 680:ref:`module-pw_thread` but directly ``#include "pw_thread_stl/options.h"``. 681For such tests, we will define *private* ``config_settings`` following this 682pattern. 683 684.. _docs-bazel-compatibility-board-chipset: 685 686Board and chipset constraint settings (not recommended) 687======================================================= 688Pigweed has historically defined a `"board" constraint_setting 689<https://cs.opensource.google/pigweed/pigweed/+/main:pw_build/constraints/board/BUILD.bazel>`_, 690and this setting was used to indicate that some modules are compatible with 691particular boards. 692 693Why is this not recommended 694--------------------------- 695This is a particularly bad pattern: hardly any Pigweed build targets are only 696compatible with a single board. Modules which have been marked as 697``target_compatible_with = ["//pw_build/constraints/board:mimxrt595_evk"]`` are 698generally compatible with many other RT595 boards, and even with other NXP 699chips. We've already run into cases in practice where users want to use a 700particular backend for a different board. 701 702The `"chipset" constraint_setting 703<https://cs.opensource.google/pigweed/pigweed/+/main:pw_build/constraints/chipset/BUILD.bazel>`_ 704has the same problem: the build targets it was applied to don't contain 705assembly code, and so are not generally compatible with only a particular 706chipset. It's also unclear how to define chipset values in a vendor-agnostic 707manner. 708 709These constraints will be removed (see :ref:`docs-bazel-compatibility-implementation-plan`). 710 711.. _docs-bazel-compatibility-rtos: 712 713RTOS constraint setting (not recommended) 714========================================= 715Some modules include headers provided by an RTOS such as embOS, FreeRTOS or 716Zephyr. If they do not make additional assumptions about the platform beyond 717the availability of those headers, they could just declare themselves 718compatible with the appropriate value of the ``//pw_build/constraints/rtos:rtos`` 719``constraint_setting``. Example: 720 721.. code-block:: python 722 723 # pw_chrono_embos/BUILD.bazel 724 725 cc_library( 726 name = "system_clock", 727 target_compatible_with = ["//pw_build/constraints/rtos:embos"], 728 ) 729 730Why is this not recommended 731--------------------------- 732At first glance, this seems like a pretty good pattern: RTOSes kind of like 733OSes, and OSes :ref:`have their "well-known" constraint 734<docs-bazel-compatibility-well-known-os>`. So why not RTOSes? 735 736RTOSes are *not* like OSes in an important respect: the dependency on them is 737already expressed in the build system! A library that uses FreeRTOS headers 738will have an explicit dependency on the ``@freertos`` target. (This is in 739contrast to OSes: a library that includes Linux system headers will not get 740them from an explicit dependency.) 741 742So, we can push the question of compatibility down to that target: if FreeRTOS 743is compatible with your platform, then a library that depends on it is (in 744general) compatible, too. Most (all?) RTOSes require configuration through 745``label_flags`` (in particular, to specify the port), so platform compatibility 746can be elegantly handled by setting the default value of that flag to a target 747that's ``@platforms//:incompatible``. 748 749.. _docs-bazel-compatibility-multiplexer: 750 751Multiplexer targets (not recommended) 752===================================== 753Historically, Pigweed selected default backends for certain facades based on 754platform constraint values. For example, this was done by 755``//pw_chrono:system_clock``: 756 757.. code-block:: python 758 759 label_flag( 760 name = "system_clock_backend", 761 build_setting_default = ":system_clock_backend_multiplexer", 762 ) 763 764 cc_library( 765 name = "system_clock_backend_multiplexer", 766 visibility = ["@pigweed//targets:__pkg__"], 767 deps = select({ 768 "//pw_build/constraints/rtos:embos": ["//pw_chrono_embos:system_clock"], 769 "//pw_build/constraints/rtos:freertos": ["//pw_chrono_freertos:system_clock"], 770 "//pw_build/constraints/rtos:threadx": ["//pw_chrono_threadx:system_clock"], 771 "//conditions:default": ["//pw_chrono_stl:system_clock"], 772 }), 773 ) 774 775Why is this not recommended 776--------------------------- 777This pattern made it difficult for the user defining a platform to understand 778which backends were being automatically set for them (because this information 779was hidden in the ``BUILD.bazel`` files for individual modules). 780 781What to do instead 782------------------ 783Platforms should explicitly set the backends of all facades they use via 784platform-based flags. For users' convenience, backend authors may :ref:`provide 785default backend collections as dicts 786<docs-bazel-compatibility-facade-backend-dict>` for explicit inclusion in the 787platform definition. 788 789.. _docs-bazel-compatibility-implementation-plan: 790 791----------------- 792Are we there yet? 793----------------- 794As of this writing, upstream Pigweed does not yet follow the best practices 795recommended below. `b/344654805 <https://pwbug.dev/344654805>`__ tracks fixing 796this. 797 798Here's a high-level roadmap for the recommendations' implementation: 799 800#. Implement the "syntactic sugar" referenced in the rest of this doc: 801 ``boolean_constraint_value``, ``incompatible_with_mcu``, etc. 802 803#. `b/342691352 <https://pwbug.dev/342691352>`_ | "Platforms should set 804 backends for Pigweed facades through label flags". For each facade, 805 806 * Remove the :ref:`multiplexer targets 807 <docs-bazel-compatibility-multiplexer>`. 808 * Remove the :ref:`per-facade constraint settings 809 <docs-bazel-compatibility-per-facade-constraint-settings>`. 810 * Remove any :ref:`default backends 811 <docs-bazel-compatibility-facade-default-backend>`. 812 813#. `b/343487589 <https://pwbug.dev/343487589>`_ | Retire the :ref:`Board and 814 chipset constraint settings <docs-bazel-compatibility-board-chipset>`. 815 816 817.. _docs-bazel-compatibility-background: 818 819-------------------- 820Appendix: Background 821-------------------- 822 823.. _docs-bazel-compatibility-why-wildcard: 824 825Why wildcard builds? 826==================== 827Pigweed is generic microcontroller middleware: you can use Pigweed to 828accelerate development on any microcontroller platform. In addition, Pigweed 829provides explicit support for a number of specific hardware platforms, such as 830the :ref:`target-rp2040` or :ref:`STM32f429i Discovery Board 831<target-stm32f429i-disc1-stm32cube>`. For these specific platforms, every 832Pigweed module falls into one of three buckets: 833 834* **works** with the platform, or, 835* **is not intended to work** with the platform, because the platform lacks the 836 relevant capabilities (e.g., the :ref:`module-pw_spi_mcuxpresso` module 837 specifically supports NXP chips, and is not intended to work with the 838 Raspberry Pi Pico). 839* **should work but doesn't yet**; that's a bug or missing feature in Pigweed. 840 841Bazel's wildcard builds provide a nice way to ensure each Pigweed build target 842is known to fall into one of those three buckets. If you run: 843 844.. code-block:: sh 845 846 bazelisk build --config=rp2040 //... 847 848Bazel will attempt to build all Pigweed build targets for the specified 849platform, with the exception of targets that are explicitly annotated as not 850compatible with it. Such `incompatible targets will be automatically skipped 851<https://bazel.build/extending/platforms#skipping-incompatible-targets>`_. 852 853Challenge: designing ``constraint_values`` 854========================================== 855As noted above, for wildcard builds to work we need to annotate some targets as 856not compatible with certain platforms. This is done through the 857`target_compatible_with attribute 858<https://bazel.build/reference/be/common-definitions#common.target_compatible_with>`_, 859which is set to a list of `constraint_values 860<https://bazel.build/reference/be/platforms-and-toolchains#constraint_value>`_ 861(essentially, enum values). For example, here's a target only compatible with 862Linux: 863 864.. code-block:: python 865 866 cc_library( 867 name = "pw_digital_io_linux", 868 target_compatible_with = ["@platforms//os:linux"], 869 ) 870 871If the platform lists all the ``constraint_values`` that appear in the target's 872``target_compatible_with`` attribute, then the target is compatible; otherwise, 873it's incompatible, and will be skipped. 874 875If this sounds a little abstract, that's because it is! Bazel is not very 876opinionated about what the constraint_values actually represent. There are only 877two sets of canonical ``constraint_values``, ``@platforms//os`` and 878``@platforms//cpu``. Here are some possible choices---not necessarily good 879ones, but all seen in the wild: 880 881* A set of constraint_values representing RTOSes: 882 883 * ``@pigweed//pw_build/constraints/rtos:embos`` 884 * ``@pigweed//pw_build/constraints/rtos:freertos`` 885 886* A set of representing individual boards: 887 888 * ``@pigweed//pw_build/constraints/board:mimxrt595_evk`` 889 * ``@pigweed//pw_build/constraints/board:stm32f429i-disc1`` 890 891* A pair of constraint values associated with a single module: 892 893 * ``@pigweed//pw_spi_mcuxpresso:compatible`` (the module is by definition compatible with any platform containing this constraint value) 894 * ``@pigweed//pw_spi_mcuxpresso:incompatible`` 895 896There are many more possible structures. 897 898What about ``constraint_settings``? 899=================================== 900Final piece of background: we mentioned above that ``constraint_values`` are a bit 901like enum values. The enums themselves (groups of ``constraint_values``) are called 902``constraint_settings``. 903 904Each ``constraint_value`` belongs to a ``constraint_setting``, and a platform 905may specify at most one value from each setting. 906 907Guiding principles 908================== 909These are the principles that guided the selection of the :ref:`recommended 910patterns <docs-bazel-compatibility-recommended>`: 911 912* **Be consistent.** Make the patterns for different use cases as similar to 913 each other as possible. 914* **Make compatibility granular.** Avoid making assumptions about what sets of 915 backends or HAL modules will be simultaneously compatible with the same 916 platforms. 917* **Minimize the amount of boilerplate** that downstream users need to put up 918 with. 919* **Support the autodetected host platform.** That is, ensure ``bazel build 920 --platforms=@platforms//host //...`` works. This is necessary internally (for 921 google3) and arguably more convenient for downstream users generally. 922