pw_sys_io: Add facade constraint_setting, docs

Adds a constraint_setting associated with the pw_sys_io facade, and
constraint_values for the backends for this facade provided by Pigweed.
Also documents how this mechanism should be used.

pw_sys_io is just the first facade to receive this treatment: the rest
will be handled in followup CLs.

Bug: b/272090220
Change-Id: Id1f670ce194757f4ccfe489e0f29ac936f36a1ab
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/133290
Reviewed-by: Erik Gilling <konkers@google.com>
Commit-Queue: Ted Pudlik <tpudlik@google.com>
Reviewed-by: Kayce Basques <kayce@google.com>
diff --git a/docs/build_system.rst b/docs/build_system.rst
index 0e20ae7..549f003 100644
--- a/docs/build_system.rst
+++ b/docs/build_system.rst
@@ -923,11 +923,11 @@
     name = "system_clock_backend_multiplexer",
     visibility = ["@pigweed_config//:__pkg__"],
     deps = select({
-        "@pigweed//pw_build/constraints/rtos:freertos":
+        "@pigweed//pw_chrono_freertos:system_clock_backend":
             ["//pw_chrono_freertos:system_clock"],
-        "@pigweed//pw_build/constraints/rtos:embos":
+        "@pigweed//pw_chrono_embos:system_clock_backend":
             ["//pw_chrono_embos:system_clock"],
-        "@pigweed//pw_build/constraints/rtos:threadx":
+        "@pigweed//pw_chrono_threadx:system_clock_backend":
             ["//pw_chrono_threadx:system_clock"],
         "//conditions:default": ["//pw_chrono_stl:system_clock"],
     }),
@@ -972,83 +972,72 @@
 your command line entries. Instead you would have to memorize the correct
 combination of backends for each of your targets.
 
-So continuing on with our scenario, let's say we add a backup micro-controller,
+So continuing on with our scenario, let's say we add a backup microcontroller
 to our spacecraft. But this backup computer doesn't have a hardware RTC. We
 still want to share the bulk of the code between the two computers but now we
 need two separate implementations for our pw_chrono facade. Let's say we choose
-to keep the primary flight computer using the hardware RTC and switch the backup
-computer over to use Pigweeds default FreeRTOS backend. In this case we might,
-want to do something similar to
-'@pigweed//pw_chrono:system_clock_backend_multiplexer' and create selectable
-dependencies for the two different computers. Now because there are no default
-constraint_setting's that meet our requirements we are going to have to;
+to keep the primary flight computer using the hardware RTC and switch the
+backup computer over to use Pigweed's default FreeRTOS backend. In this case we
+might want to do something similar to
+``@pigweed//pw_chrono:system_clock_backend_multiplexer`` and create selectable
+dependencies for the two different computers:
 
-1. Create a constraint_setting and a set of constraint_value's for the flight
-   computer. For example;
+#. Create a constraint value corresponding to your custom backend:
 
-  .. code:: py
+   .. code-block:: python
 
-    # //platforms/flight_computer/BUILD
-    constraint_setting(
-      name = "flight_computer",
-    )
+     # //pw_chrono_my_hardware_rtc/BUILD.bazel
+     constraint_value(
+       name = "system_clock_backend",
+       constraint_setting = "//pw_chrono:system_clock_constraint_setting",
+     )
 
-    constraint_value(
-      name = "primary",
-      constraint_setting = ":flight_computer",
-    )
+#. Create a set of platforms that can be used to switch constraint values.
+   For example:
 
-    constraint_value(
-      name = "backup",
-      constraint_setting = ":flight_computer",
-    )
+   .. code-block:: python
 
-2. Create a set of platforms that can be used to switch constraint_value's.
-   For example;
+      # //platforms/BUILD.bazel
+      platform(
+        name = "primary_computer",
+        constraint_values = ["//pw_chrono_my_hardware_rtc:system_clock_backend"],
+      )
 
-  .. code:: py
+      platform(
+        name = "backup_computer",
+        constraint_values = ["@pigweed//pw_chrono_freertos:system_clock_backend"],
+      )
 
-    # //platforms/BUILD
-    platform(
-      name = "primary_computer",
-      constraint_values = ["//platforms/flight_computer:primary"],
-    )
+#. Create a target multiplexer that will select the right backend depending on
+   which computer you are using. For example:
 
-    platform(
-      name = "backup_computer",
-      constraint_values = ["//platforms/flight_computer:backup"],
-    )
+   .. code-block:: python
 
-3. Create a target multiplexer that will select the right backend depending on
-   which computer you are using. For example;
+     # //pw_chrono/BUILD
+     load("//pw_build:pigweed.bzl", "pw_cc_library")
 
-  .. code:: py
+     pw_cc_library(
+       name = "system_clock_backend_multiplexer",
+       deps = select({
+         "//pw_chrono_my_hardware_rtc:system_clock_backend": [
+           "//pw_chrono_my_hardware_rtc:system_clock",
+         ],
+         "@pigweed//pw_chrono_freertos:system_clock_backend": [
+           "@pigweed//pw_chrono_freertos:system_clock",
+         ],
+         "//conditions:default": [
+           "@pigweed//pw_chrono_stl:system_clock",
+         ],
+       }),
+     )
 
-    # //pw_chrono/BUILD
-    load("//pw_build:pigweed.bzl", "pw_cc_library")
-
-    pw_cc_library(
-      name = "system_clock_backend_multiplexer",
-      deps = select({
-        "//platforms/flight_computer:primary": [
-          "//pw_chrono_my_hardware_rtc:system_clock",
-        ],
-        "//platforms/flight_computer:backup": [
-          "@pigweed//pw_chrono_freertos:system_clock",
-        ],
-        "//conditions:default": [
-          "@pigweed//pw_chrono_stl:system_clock",
-        ],
-      }),
-    )
-
-4. Add a build setting override for the ``pw_chrono_system_clock_backend`` label
+#. Add a build setting override for the ``pw_chrono_system_clock_backend`` label
    flag to your ``.bazelrc`` file that points to your new target multiplexer.
 
-  .. code:: py
+   .. code-block:: python
 
-    # //.bazelrc
-    build --@pigweed_config//:pw_chrono_system_clock_backend=@your_workspace//pw_chrono:system_clock_backend_multiplexer
+     # //.bazelrc
+     build --@pigweed_config//:pw_chrono_system_clock_backend=@your_workspace//pw_chrono:system_clock_backend_multiplexer
 
 Building your target now will result in slightly different build graph. For
 example, running;
diff --git a/pw_build/BUILD.bazel b/pw_build/BUILD.bazel
index 4d84665..d1690dd 100644
--- a/pw_build/BUILD.bazel
+++ b/pw_build/BUILD.bazel
@@ -47,3 +47,16 @@
         "PW_BUILD_EXPECTED_SOURCE_PATH=\\\"pw_build/file_prefix_map_test.cc\\\"",
     ],
 )
+
+# Special target that may be used instead of a cc_library as the default
+# condition in backend multiplexer select statements.  This produces better
+# error messages than e.g. using an invalid label.
+#
+# If you're a user whose build errored out because a library transitively
+# depended on this target: the platform you're targeting did not specify which
+# backend to use for some facade. Look at the previous step in the dependency
+# chain (printed with the error) to figure out which one.
+cc_library(
+    name = "unspecified_backend",
+    target_compatible_with = ["@platforms//:incompatible"],
+)
diff --git a/pw_build/docs.rst b/pw_build/docs.rst
index ee98663..3c74013 100644
--- a/pw_build/docs.rst
+++ b/pw_build/docs.rst
@@ -1484,11 +1484,52 @@
    If a project uses only one backend for a given facade, the backend label
    flag should point at that backend target.
 
+#. The **facade constraint setting** and **backend constraint values**. Every
+   facade has an associated `constraint setting
+   <https://bazel.build/concepts/platforms#api-review>`_ (enum used in platform
+   definition), and each backend for this facade has an associated
+   ``constraint_value`` (enum value). Example:
+
+   .. code-block:: python
+
+     # //pw_sync/BUILD.bazel
+     constraint_setting(
+       name = "binary_semaphore_backend_constraint_setting",
+     )
+
+     # //pw_sync_stl/BUILD.bazel
+     constraint_value(
+       name = "binary_semaphore_backend",
+       constraint_setting = "//pw_sync:binary_semaphore_backend_constraint_setting",
+     )
+
+     # //pw_sync_freertos/BUILD.bazel
+     constraint_value(
+       name = "binary_semaphore_backend",
+       constraint_setting = "//pw_sync:binary_semaphore_backend_constraint_setting",
+     )
+
+   `Target platforms <https://bazel.build/extending/platforms>`_ for Pigweed
+   projects should indicate which backend they select for each facade by
+   listing the corresponding ``constraint_value`` in their definition. This can
+   be used in a couple of ways:
+
+   #.  It allows projects to switch between multiple backends based only on the
+       `target platform <https://bazel.build/extending/platforms>`_ using a
+       *backend multiplexer* (see below) instead of setting label flags in
+       their ``.bazelrc``.
+
+   #.  It allows tests or libraries that only support a particular backend to
+       express this through the `target_compatible_with
+       <https://bazel.build/reference/be/common-definitions#common.target_compatible_with>`_
+       attribute. Bazel will use this to `automatically skip incompatible
+       targets in wildcard builds
+       <https://bazel.build/extending/platforms#skipping-incompatible-targets>`_.
+
 #. The **backend multiplexer**. If a project uses more than one backend for a
    given facade (e.g., it uses different backends for host and embedded target
    builds), the backend label flag will point to a target that resolves to the
-   correct backend based on the `target platform
-   <https://bazel.build/extending/platforms>`_. This will typically be an
+   correct backend based on the target platform. This will typically be an
    `alias <https://bazel.build/reference/be/general#alias>`_ with a ``select``
    statement mapping constraint values to the appropriate backend targets. For
    example,
@@ -1500,6 +1541,15 @@
          actual = select({
              "//pw_sync_stl:binary_semaphore_backend": "@pigweed//pw_sync_stl:binary_semaphore",
              "//pw_sync_freertos:binary_semaphore_backend": "@pigweed//pw_sync_freertos:binary_semaphore_backend",
+             # If we're building for a host OS, use the STL backend.
+             "@platforms//os:macos": "@pigweed//pw_sync_stl:binary_semaphore",
+             "@platforms//os:linux": "@pigweed//pw_sync_stl:binary_semaphore",
+             "@platforms//os:windows": "@pigweed//pw_sync_stl:binary_semaphore",
+             # Unless the target platform is the host platform, it must
+             # explicitly specify which backend to use. The unspecified_backend
+             # is not compatible with any platform; taking this branch will produce
+             # an informative error.
+             "//conditions:default": "@pigweed//pw_build:unspecified_backend",
          }),
      )
 
diff --git a/pw_build/platforms/BUILD.bazel b/pw_build/platforms/BUILD.bazel
index c0a688d..744cc48 100644
--- a/pw_build/platforms/BUILD.bazel
+++ b/pw_build/platforms/BUILD.bazel
@@ -98,6 +98,7 @@
     constraint_values = [
         "//pw_build/constraints/chipset:lm3s6965evb",
         "@rust_crates//:no_std",
+        "//pw_sys_io_baremetal_lm3s6965evb:backend",
     ],
     parents = [":cortex_m3"],
 )
@@ -115,11 +116,14 @@
     parents = [":stm32f429"],
 )
 
+# Primarily a QEMU supported m0 target for rust development, based on the
+# nRF51822.
 platform(
     name = "microbit",
     constraint_values = [
         "//pw_build/constraints/board:microbit",
         "@rust_crates//:no_std",
+        # We have no pw_sys_io backend for this platform.
     ],
     parents = [":nrf52833"],
 )
@@ -137,9 +141,9 @@
         "//targets/stm32f429i_disc1_stm32cube:freertos_config_cv",
         # Use the ARM_CM4F port of FreeRTOS.
         "@freertos//:port_ARM_CM4F",
-        # Specify this chipset to use the baremetal pw_sys_io backend (because
-        # the default pw_sys_io_stdio backend is not compatible with FreeRTOS).
-        "//pw_build/constraints/chipset:stm32f429",
+        # Use the baremetal pw_sys_io backend (because the default
+        # pw_sys_io_stdio backend is not compatible with FreeRTOS).
+        "//pw_sys_io_baremetal_stm32f429:backend",
         # os:none means, we're not building for any host platform (Windows,
         # Linux, or Mac). The pw_sys_io_baremetal_stm32f429 backend is only
         # compatible with os:none.
diff --git a/pw_sys_io/BUILD.bazel b/pw_sys_io/BUILD.bazel
index 7e6eee2..1cfd15f 100644
--- a/pw_sys_io/BUILD.bazel
+++ b/pw_sys_io/BUILD.bazel
@@ -22,6 +22,10 @@
 
 licenses(["notice"])
 
+constraint_setting(
+    name = "backend_constraint_setting",
+)
+
 pw_cc_facade(
     name = "facade",
     hdrs = ["public/pw_sys_io/sys_io.h"],
@@ -53,13 +57,21 @@
     ],
 )
 
-pw_cc_library(
+alias(
     name = "backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/board:mimxrt595_evk": ["@pigweed//pw_sys_io_mcuxpresso"],
-        "//pw_build/constraints/chipset:stm32f429": ["@pigweed//pw_sys_io_baremetal_stm32f429"],
-        "//pw_build/constraints/chipset:lm3s6965evb": ["@pigweed//pw_sys_io_baremetal_lm3s6965evb"],
-        "//conditions:default": ["@pigweed//pw_sys_io_stdio"],
+    actual = select({
+        "//pw_sys_io_arduino:backend": "@pigweed//pw_sys_io_arduino",
+        "//pw_sys_io_baremetal_lm3s6965evb:backend": "@pigweed//pw_sys_io_baremetal_lm3s6965evb",
+        "//pw_sys_io_baremetal_stm32f429:backend": "@pigweed//pw_sys_io_baremetal_stm32f429",
+        "//pw_sys_io_emcraft_sf2:backend": "@pigweed//pw_sys_io_emcraft_sf2",
+        "//pw_sys_io_mcuxpresso:backend": "@pigweed//pw_sys_io_mcuxpresso",
+        "//pw_sys_io_pico:backend": "@pigweed//pw_sys_io_pico",
+        "//pw_sys_io_stm32cube:backend": "@pigweed//pw_sys_io_stm32cube",
+        "//pw_sys_io_stdio:backend": "@pigweed//pw_sys_io_stdio",
+        "@platforms//os:macos": "@pigweed//pw_sys_io_stdio",
+        "@platforms//os:linux": "@pigweed//pw_sys_io_stdio",
+        "@platforms//os:windows": "@pigweed//pw_sys_io_stdio",
+        "//conditions:default": "@pigweed//pw_build:unspecified_backend",
     }),
+    visibility = ["@pigweed_config//:__pkg__"],
 )
diff --git a/pw_sys_io_arduino/BUILD.bazel b/pw_sys_io_arduino/BUILD.bazel
index dafbb9b..5b29974 100644
--- a/pw_sys_io_arduino/BUILD.bazel
+++ b/pw_sys_io_arduino/BUILD.bazel
@@ -21,6 +21,11 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_arduino",
     srcs = ["sys_io_arduino.cc"],
diff --git a/pw_sys_io_baremetal_lm3s6965evb/BUILD.bazel b/pw_sys_io_baremetal_lm3s6965evb/BUILD.bazel
index b3ea1c2..909feee 100644
--- a/pw_sys_io_baremetal_lm3s6965evb/BUILD.bazel
+++ b/pw_sys_io_baremetal_lm3s6965evb/BUILD.bazel
@@ -21,6 +21,11 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_baremetal_lm3s6965evb",
     srcs = ["sys_io_baremetal.cc"],
diff --git a/pw_sys_io_baremetal_stm32f429/BUILD.bazel b/pw_sys_io_baremetal_stm32f429/BUILD.bazel
index 248d987..8147ce1 100644
--- a/pw_sys_io_baremetal_stm32f429/BUILD.bazel
+++ b/pw_sys_io_baremetal_stm32f429/BUILD.bazel
@@ -21,13 +21,17 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_baremetal_stm32f429",
     srcs = ["sys_io_baremetal.cc"],
     hdrs = ["public/pw_sys_io_baremetal_stm32f429/init.h"],
     includes = ["public"],
     target_compatible_with = [
-        "//pw_build/constraints/chipset:stm32f429",
         "@platforms//os:none",
     ],
     deps = [
diff --git a/pw_sys_io_emcraft_sf2/BUILD.bazel b/pw_sys_io_emcraft_sf2/BUILD.bazel
index fb10a78..3643727 100644
--- a/pw_sys_io_emcraft_sf2/BUILD.bazel
+++ b/pw_sys_io_emcraft_sf2/BUILD.bazel
@@ -21,6 +21,11 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_emcraft_sf2",
     srcs = [
diff --git a/pw_sys_io_mcuxpresso/BUILD.bazel b/pw_sys_io_mcuxpresso/BUILD.bazel
index 726e5aa..7c91d7b 100644
--- a/pw_sys_io_mcuxpresso/BUILD.bazel
+++ b/pw_sys_io_mcuxpresso/BUILD.bazel
@@ -21,6 +21,11 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_mcuxpresso",
     srcs = ["sys_io.cc"],
diff --git a/pw_sys_io_pico/BUILD.bazel b/pw_sys_io_pico/BUILD.bazel
index 91d4242..caa77f5 100644
--- a/pw_sys_io_pico/BUILD.bazel
+++ b/pw_sys_io_pico/BUILD.bazel
@@ -21,6 +21,11 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_pico",
     srcs = [
diff --git a/pw_sys_io_stdio/BUILD.bazel b/pw_sys_io_stdio/BUILD.bazel
index 23d5103..381ce98 100644
--- a/pw_sys_io_stdio/BUILD.bazel
+++ b/pw_sys_io_stdio/BUILD.bazel
@@ -25,6 +25,11 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_stdio",
     srcs = ["sys_io.cc"],
diff --git a/pw_sys_io_stm32cube/BUILD.bazel b/pw_sys_io_stm32cube/BUILD.bazel
index 39c863d..7a9f2f8 100644
--- a/pw_sys_io_stm32cube/BUILD.bazel
+++ b/pw_sys_io_stm32cube/BUILD.bazel
@@ -21,6 +21,11 @@
 
 licenses(["notice"])
 
+constraint_value(
+    name = "backend",
+    constraint_setting = "//pw_sys_io:backend_constraint_setting",
+)
+
 pw_cc_library(
     name = "pw_sys_io_stm32cube",
     srcs = [