pw_build: Facade and configuration tweaks and docs
- Add documentation for facades and the pw_facade template.
- Document the module config pattern.
- Remove the need for the facade_name argument to pw_facade.
Change-Id: I77529583967cfdb4f47ee87313982b1259ca036e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/22045
Reviewed-by: Alexei Frolov <frolv@google.com>
Commit-Queue: Wyatt Hepler <hepler@google.com>
diff --git a/docs/build_system.rst b/docs/build_system.rst
index 9c0b2e6..2bf65e7 100644
--- a/docs/build_system.rst
+++ b/docs/build_system.rst
@@ -394,8 +394,10 @@
.. code::
- # This must be imported before .gni files from any other Pigweed modules.
- # To prevent gn format from reordering this import, a comment is added above.
+ # This must be imported before .gni files from any other Pigweed modules. To
+ # prevent gn format from reordering this import, it must be separated by a
+ # blank line from other imports.
+
import("//build_overrides/pigweed.gni")
GN target type wrappers
diff --git a/docs/module_structure.rst b/docs/module_structure.rst
index 89ec692..c10106a 100644
--- a/docs/module_structure.rst
+++ b/docs/module_structure.rst
@@ -27,7 +27,7 @@
public/pw_foo/foo.h
public/pw_foo/baz.h
- # Exposed public headers go under internal/
+ # Exposed private headers go under internal/
public/pw_foo/internal/bar.h
public/pw_foo/internal/qux.h
@@ -186,6 +186,169 @@
BUILD.gn
README.md
+Compile-time configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+Pigweed modules are intended to be used in a wide variety of environments.
+In support of this, some modules expose compile-time configuration options.
+Pigweed has an established pattern for declaring and overriding module
+configuration.
+
+.. tip::
+
+ Compile-time configuration provides flexibility, but also imposes
+ restrictions. A module can only have one configuration in a given build.
+ Compile-time configuration also makes testing more difficult. Where
+ appropriate, consider alternatives such as C++ templates or runtime
+ configuration.
+
+Declaring configuration
+^^^^^^^^^^^^^^^^^^^^^^^
+Configuration values are declared in a header file with macros. If the macro
+value is not already defined, a default definition is provided. Otherwise,
+nothing is done. Configuration headers may include ``static_assert`` statements
+to validate configuration values.
+
+.. code-block:: c++
+
+ // Example configuration header
+
+ #ifndef PW_FOO_INPUT_BUFFER_SIZE_BYTES
+ #define PW_FOO_INPUT_BUFFER_SIZE_BYTES 128
+ #endif // PW_FOO_INPUT_BUFFER_SIZE_BYTES
+
+ static_assert(PW_FOO_INPUT_BUFFER_SIZE_BYTES >= 64);
+
+The configuration header may go in one of three places in the module, depending
+on whether the header should be exposed by the module or not.
+
+.. code-block::
+
+ pw_foo/...
+
+ # Publicly accessible configuration header
+ public/pw_foo/config.h
+
+ # Internal configuration header that is included by other module headers
+ public/pw_foo/internal/config.h
+
+ # Internal configuration header
+ pw_foo_private/config.h
+
+The configuration header is provided by a build system library. This library
+acts as a :ref:`facade<docs-module-structure-facades>`. The facade uses a
+variable such as ``pw_foo_CONFIG``. In upstream Pigweed, all config facades
+default to the ``pw_build_DEFAULT_MODULE_CONFIG`` backend. In the GN build
+system, the config facade is declared as follows:
+
+.. code-block::
+
+ declare_args() {
+ # The build target that overrides the default configuration options for this
+ # module. This should point to a source set that provides defines through a
+ # public config (which may -include a file or add defines directly).
+ pw_foo_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
+ }
+
+ # Publicly accessible configuration header (most common)
+ pw_source_set("config") {
+ public = [ "public/pw_foo/config.h" ]
+ public_configs = [ ":public_include_path" ]
+ public_deps = [ pw_foo_CONFIG ]
+ }
+
+ # Internal configuration header that is included by other module headers
+ pw_source_set("config") {
+ sources = [ "public/pw_foo/internal/config.h" ]
+ public_configs = [ ":public_include_path" ]
+ public_deps = [ pw_foo_CONFIG ]
+ visibility = [":*"] # Only allow this module to depend on ":config"
+ friend = [":*"] # Allow this module to access the config.h header.
+ }
+
+ # Internal configuration header
+ pw_source_set("config") {
+ public = [ "pw_foo_private/config.h" ]
+ public_deps = [ pw_foo_CONFIG ]
+ visibility = [":*"] # Only allow this module to depend on ":config"
+ }
+
+Overriding configuration
+^^^^^^^^^^^^^^^^^^^^^^^^
+As noted above, all module configuration facades default to the same backend
+(``pw_build_DEFAULT_MODULE_CONFIG``). This allows projects to override
+configuration values for multiple modules from a single configuration backend,
+if desired. The configuration values may also be overridden individually by
+setting backends for the individual module configurations (e.g. in GN,
+``pw_foo_CONFIG = "//configuration:my_foo_config"``).
+
+Configurations are overridden by setting compilation options in the config
+backend. These options could be set through macro definitions, such as
+``-DPW_FOO_INPUT_BUFFER_SIZE_BYTES=256``, or in a header file included with the
+``-include`` option.
+
+This example shows how two ways to configure a module in the GN build system.
+
+.. code-block::
+
+ # In the toolchain, set either pw_build_DEFAULT_MODULE_CONFIG or pw_foo_CONFIG
+ pw_build_DEFAULT_MODULE_CONFIG = get_path_info(":define_overrides", "abspath")
+
+ # This configuration sets PW_FOO_INPUT_BUFFER_SIZE_BYTES using the -D macro.
+ pw_source_set("define_overrides") {
+ public_configs = [ ":define_options" ]
+ }
+
+ config("define_options") {
+ defines = [ "-DPW_FOO_INPUT_BUFFER_SIZE_BYTES=256" ]
+ }
+
+ # This configuration sets PW_FOO_INPUT_BUFFER_SIZE_BYTES with a header file.
+ pw_source_set("include_overrides") {
+ public_configs = [ ":header_options" ]
+
+ # Header file with #define PW_FOO_INPUT_BUFFER_SIZE_BYTES 256
+ sources = [ "my_config_overrides.h" ]
+ }
+
+ config("header_options") {
+ cflags = [
+ "-include",
+ "my_config_overrides.h",
+ ]
+ }
+
+.. _docs-module-structure-facades:
+
+Facades
+-------
+In Pigweed, facades represent a dependency that can be swapped at compile time.
+Facades are similar in concept to a virtual interface, but the implementation is
+set by the build system. Runtime polymorphism with facades is not
+possible, and each facade may only have one implementation (backend) per
+toolchain compilation.
+
+In the simplest sense, a facade is just a dependency represented by a variable.
+For example, the ``pw_log`` facade is represented by the ``pw_log_BACKEND``
+build variable. Facades typically are bundled with a build system library that
+depends on the backend.
+
+Modules should only use facades when necessary. Since they are fixed at compile
+time, runtime dependency injection is not possible. Where appropriate, modules
+should use other mechanisms, such as virtual interfaces or callbacks, in place
+of facades.
+
+Facades are essential in some circumstances:
+
+* Low-level, platform-specific features (:ref:`module-pw_cpu_exception`).
+* Features that require a macro or non-virtual function interface
+ (:ref:`module-pw_tokenizer`),
+* Highly leveraged code where a virtual interface or callback is too costly or
+ cumbersome (:ref:`module-pw_log`, :ref:`module-pw_assert`).
+
+The GN build system provides the
+:ref:`pw_facade template<module-pw_build-facade>` as a convenient way to declare
+facades.
+
Documentation
-------------
Documentation should go in the root module folder, typically in the
diff --git a/pw_arduino_build/BUILD.gn b/pw_arduino_build/BUILD.gn
index 5556dc5..db72ea8 100644
--- a/pw_arduino_build/BUILD.gn
+++ b/pw_arduino_build/BUILD.gn
@@ -28,7 +28,6 @@
pw_facade("arduino_init") {
backend = pw_arduino_build_INIT_BACKEND
public = [ "public/pw_arduino_build/init.h" ]
- facade_name = "arduino_init_facade"
public_configs = [ ":default_config" ]
}
config("default_config") {
diff --git a/pw_build/docs.rst b/pw_build/docs.rst
index 64c67f9..8fe235b 100644
--- a/pw_build/docs.rst
+++ b/pw_build/docs.rst
@@ -42,7 +42,7 @@
Target types
^^^^^^^^^^^^
-.. code::
+.. code-block::
import("$dir_pw_build/target_types.gni")
@@ -75,6 +75,34 @@
All of the ``pw_*`` target type overrides accept any arguments, as they simply
forward them through to the underlying target.
+.. _module-pw_build-facade:
+
+pw_facade
+^^^^^^^^^
+In their simplest form, a :ref:`facade<docs-module-structure-facades>` is a GN
+build arg used to change a dependency at compile time. Pigweed targets configure
+these facades as needed.
+
+The ``pw_facade`` template bundles a ``pw_source_set`` with a facade build arg.
+This allows the facade to provide header files, compilation options or anything
+else a GN ``source_set`` provides.
+
+The ``pw_facade`` template declares two targets:
+
+* ``$target_name``: the public-facing ``pw_source_set``, with a ``public_dep``
+ on the backend
+* ``$target_name.facade``: target used by the backend to avoid circular
+ dependencies
+
+.. code-block::
+
+ # Declares ":foo" and ":foo.facade" GN targets
+ pw_facade("foo") {
+ backend = pw_log_BACKEND
+ public_configs = [ ":public_include_path" ]
+ public = [ "public/pw_foo/foo.h" ]
+ }
+
.. _module-pw_build-python-script:
pw_python_action
@@ -127,13 +155,13 @@
Evaluates to the output file of the provided GN target. For example, the
expression
- .. code::
+ .. code-block::
"<TARGET_FILE(//foo/bar:static_lib)>"
might expand to
- .. code::
+ .. code-block::
"/home/User/project_root/out/obj/foo/bar/static_lib.a"
@@ -158,14 +186,14 @@
For example, consider this expression:
- .. code::
+ .. code-block::
"--database=<TARGET_FILE_IF_EXISTS(//alpha/bravo)>"
If the ``//alpha/bravo`` target file exists, this might expand to the
following:
- .. code::
+ .. code-block::
"--database=/home/User/project/out/obj/alpha/bravo/bravo.elf"
@@ -181,13 +209,13 @@
For example, the expression
- .. code::
+ .. code-block::
"<TARGET_OBJECTS(//foo/bar:a_source_set)>"
might expand to multiple separate arguments:
- .. code::
+ .. code-block::
"/home/User/project_root/out/obj/foo/bar/a_source_set.file_a.cc.o"
"/home/User/project_root/out/obj/foo/bar/a_source_set.file_b.cc.o"
@@ -195,7 +223,7 @@
**Example**
-.. code::
+.. code-block::
import("$dir_pw_build/python_action.gni")
@@ -235,7 +263,7 @@
**Example**
-.. code::
+.. code-block::
import("$dir_pw_build/input_group.gni")
@@ -254,7 +282,7 @@
files are modified.
pw_zip
-^^^^^^^^^^^^^^
+^^^^^^
``pw_zip`` is a target that allows users to zip up a set of input files and
directories into a single output ``.zip`` file—a simple automation of a
potentially repetitive task.
@@ -286,7 +314,7 @@
Let's say we have the following structure for a ``//source/`` directory:
-.. code::
+.. code-block::
source/
├── file1.txt
@@ -299,7 +327,7 @@
And we create the following build target:
-.. code::
+.. code-block::
import("$dir_pw_build/zip.gni")
@@ -324,7 +352,7 @@
This will result in a ``.zip`` file called ``foo.zip`` stored in
``//$target_out_dir`` with the following structure:
-.. code::
+.. code-block::
foo.zip
├── bar/
@@ -345,7 +373,7 @@
The following command generates Ninja build files in the out/cmake directory.
-.. code:: sh
+.. code-block:: sh
cmake -B out/cmake -S /path/to/pigweed -G Ninja
@@ -373,7 +401,7 @@
To use Pigweed libraries form a CMake-based project, simply include the Pigweed
repository from a ``CMakeLists.txt``.
-.. code:: cmake
+.. code-block:: cmake
add_subdirectory(path/to/pigweed pigweed)
@@ -382,7 +410,7 @@
If desired, modules can be included individually.
-.. code:: cmake
+.. code-block:: cmake
include(path/to/pigweed/pw_build/pigweed.cmake)
diff --git a/pw_build/facade.gni b/pw_build/facade.gni
index 684a4f7..4bda84f 100644
--- a/pw_build/facade.gni
+++ b/pw_build/facade.gni
@@ -23,8 +23,19 @@
# link against. Typically this will be done by pointing a build arg like
# `pw_[module]_BACKEND` at a backend implementation for that module.
#
+# To avoid circular dependencies, pw_facade creates two targets:
+#
+# - $target_name: the public-facing pw_source_set
+# - $target_name.facade: target used by the backend to avoid circular
+# dependencies
+#
+# If the target name matches the directory name (e.g. //foo:foo), a ":facade"
+# alias of the facade target (e.g. //foo:facade) is also provided. This avoids
+# the need to repeat the directory name, for consistency with the main target.
+#
# Example facade:
#
+# # Creates ":module_name" and ":module_name.facade" GN targets.
# pw_facade("module_name") {
# backend = dir_module_name_backend
# public_deps = [
@@ -32,20 +43,32 @@
# ]
# }
#
-# Args:
-# - backend: the dependency that implements this facade
-# - facade_name: (optional) The name to use for the facade target on which the
-# backend depends. Only required when a module defines multiple facades.
-# Defaults to "facade".
+# Accepts the standard pw_source_set args with the following additions:
+#
+# - backend: the dependency that implements this facade (a GN variable)
#
template("pw_facade") {
assert(defined(invoker.backend),
"pw_facade requires a reference to a backend variable for the facade")
+ _facade_name = "$target_name.facade"
+
if (defined(invoker.facade_name)) {
- _facade_name = invoker.facade_name
- } else {
- _facade_name = "facade"
+ not_needed([ invoker.facade_name ])
+ print("pw_facade(\"$target_name\") uses the facade_name arg, which is",
+ "deprecated. Access the facade target as $target_name.facade")
+ }
+
+ if (get_path_info(get_label_info(":$target_name", "dir"), "name") ==
+ get_label_info(":$target_name", "name")) {
+ group("facade") {
+ public_deps = [ ":$_facade_name" ]
+ }
+ }
+
+ # For backwards compatibility, provide a _facade version of the name.
+ group(target_name + "_facade") {
+ public_deps = [ ":$_facade_name" ]
}
# A facade's headers are split into a separate target to avoid a circular
@@ -90,7 +113,7 @@
# error message.
_main_target_name = target_name
- pw_python_action(_main_target_name + "_NO_BACKEND_SET") {
+ pw_python_action(_main_target_name + ".NO_BACKEND_SET") {
stamp = true
script = "$dir_pw_build/py/pw_build/null_backend.py"
args = [ _main_target_name ]
@@ -112,8 +135,8 @@
if (invoker.backend != "") {
public_deps += [ invoker.backend ]
} else {
- # If the backend is not set, depend on the *_NO_BACKEND_SET target.
- public_deps += [ ":$_main_target_name" + "_NO_BACKEND_SET" ]
+ # If the backend is not set, depend on the *.NO_BACKEND_SET target.
+ public_deps += [ ":$_main_target_name" + ".NO_BACKEND_SET" ]
}
}
}
diff --git a/pw_cpu_exception/BUILD.gn b/pw_cpu_exception/BUILD.gn
index 58879fc..f2562e2 100644
--- a/pw_cpu_exception/BUILD.gn
+++ b/pw_cpu_exception/BUILD.gn
@@ -52,16 +52,14 @@
pw_facade("entry") {
backend = pw_cpu_exception_ENTRY_BACKEND
- facade_name = "entry_facade"
public_configs = [ ":default_config" ]
public_deps = [ "$dir_pw_preprocessor" ]
- deps = [ ":handler_facade" ]
+ deps = [ ":handler.facade" ]
public = [ "public/pw_cpu_exception/entry.h" ]
}
pw_facade("handler") {
backend = pw_cpu_exception_HANDLER_BACKEND
- facade_name = "handler_facade"
public_configs = [ ":default_config" ]
public_deps = [
"$dir_pw_preprocessor",
@@ -77,7 +75,6 @@
# pw_CpuExceptionState members.
pw_facade("support") {
backend = pw_cpu_exception_SUPPORT_BACKEND
- facade_name = "support_facade"
public_configs = [ ":default_config" ]
public_deps = [ "$dir_pw_span" ]
public = [ "public/pw_cpu_exception/support.h" ]
@@ -85,7 +82,7 @@
pw_source_set("basic_handler") {
deps = [
- ":handler_facade",
+ ":handler.facade",
dir_pw_log,
]
sources = [ "basic_handler.cc" ]
diff --git a/pw_cpu_exception_armv7m/BUILD.gn b/pw_cpu_exception_armv7m/BUILD.gn
index 5a97e57..a3a4b35 100644
--- a/pw_cpu_exception_armv7m/BUILD.gn
+++ b/pw_cpu_exception_armv7m/BUILD.gn
@@ -26,7 +26,7 @@
pw_source_set("support") {
public_configs = [ ":default_config" ]
public_deps = [
- "$dir_pw_cpu_exception:support_facade",
+ "$dir_pw_cpu_exception:support.facade",
dir_pw_preprocessor,
dir_pw_string,
]
@@ -56,7 +56,7 @@
public_deps = [
":proto_dump",
":support",
- "$dir_pw_cpu_exception:entry_facade",
+ "$dir_pw_cpu_exception:entry.facade",
"$dir_pw_cpu_exception:handler",
"$dir_pw_preprocessor",
]
diff --git a/pw_log_tokenized/BUILD.gn b/pw_log_tokenized/BUILD.gn
index 3de893c..94db9c1 100644
--- a/pw_log_tokenized/BUILD.gn
+++ b/pw_log_tokenized/BUILD.gn
@@ -33,7 +33,7 @@
# backend.
#
# This target depends on the pw_tokenizer facade target
-# (dir_pw_tokenizer:global_handler_with_payload_facade) to avoid circular
+# (dir_pw_tokenizer:global_handler_with_payload.facade) to avoid circular
# dependencies. The dependency graph for pw_log_tokenized is the following:
#
# pw_log:facade <--- :pw_log_tokenized
@@ -45,7 +45,7 @@
public_configs = [ ":default_config" ]
public_deps = [
"$dir_pw_log:facade",
- "$dir_pw_tokenizer:global_handler_with_payload_facade",
+ "$dir_pw_tokenizer:global_handler_with_payload.facade",
dir_pw_preprocessor,
]
public = [ "public/pw_log_tokenized/log_tokenized.h" ]
diff --git a/pw_log_tokenized/docs.rst b/pw_log_tokenized/docs.rst
index 323522f..1c30ee7 100644
--- a/pw_log_tokenized/docs.rst
+++ b/pw_log_tokenized/docs.rst
@@ -38,7 +38,7 @@
``log_backend``. The ``pw_log_tokenized`` target provides the
``pw_log_tokenized/log_tokenized.h`` header. The ``log_backend`` target
implements the backend for the ``pw_log`` facade. ``pw_log_tokenized`` invokes
-the ``pw_tokenizer:global_handler_with_facade`` facade, which must be
+the ``pw_tokenizer:global_handler_with_payload`` facade, which must be
implemented by the user of ``pw_log_tokenized``.
.. note::
diff --git a/pw_tokenizer/BUILD.gn b/pw_tokenizer/BUILD.gn
index 1f7e442..8d245a2 100644
--- a/pw_tokenizer/BUILD.gn
+++ b/pw_tokenizer/BUILD.gn
@@ -88,7 +88,6 @@
}
pw_facade("global_handler") {
- facade_name = "global_handler_facade"
backend = pw_tokenizer_GLOBAL_HANDLER_BACKEND
public_configs = [ ":public_include_path" ]
@@ -98,7 +97,6 @@
}
pw_facade("global_handler_with_payload") {
- facade_name = "global_handler_with_payload_facade"
backend = pw_tokenizer_GLOBAL_HANDLER_WITH_PAYLOAD_BACKEND
public_configs = [ ":public_include_path" ]
diff --git a/targets/arduino/BUILD.gn b/targets/arduino/BUILD.gn
index 06f8318..07d84a6 100644
--- a/targets/arduino/BUILD.gn
+++ b/targets/arduino/BUILD.gn
@@ -84,7 +84,7 @@
"$dir_pw_third_party_arduino:arduino_core_sources",
]
deps = [
- "$dir_pw_arduino_build:arduino_init_facade",
+ "$dir_pw_arduino_build:arduino_init.facade",
"$dir_pw_preprocessor",
]
}