xref: /aosp_15_r20/external/pigweed/docs/bazel_compatibility.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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