blob: 54b7e6505b637d7342ff329efaf3f90c8a655656 [file] [log] [blame]
.. _docs-build-system:
============
Build system
============
Building software for embedded devices is a complex process. Projects often have
custom toolchains, target different hardware platforms, and require additional
configuration and post-processing of artifacts.
As a modern embedded framework, Pigweed's goal is to collect these embedded use
cases into a powerful and flexible build system, then extend it with support for
modern software development practices.
See :ref:`docs-python-build` for information about Python build automation with
Pigweed.
.. toctree::
:hidden:
python_build
What's in a build system?
=========================
A quality build system provides a variety of features beyond compiling code.
Throughout our experience with embedded development, we've found several build
features to be especially useful, and designed Pigweed's build system with them
in mind.
Simple toolchain configuration
------------------------------
Embedded projects often use custom build toolchains for their specific hardware.
Configuring these should be a simple process, both in their initial setup and
later adjustments.
Multi-target builds
-------------------
Virtually every consumer product has firmware that targets different boards or
MCUs during development. While building for a single board is simple enough, the
complexity of supporting different targets ranges from changing compiler flags
to swapping out entire libraries of firmware and drivers. This is often done by
running multiple builds, configuring each one accordingly. In Pigweed, we've
designed our build system with first-class multi-target support in mind,
allowing any number of target configurations to be built simultaneously.
Multi-language support
----------------------
Embedded projects are typically written in C, C++, and assembly. However, it is
possible to have firmware written in other languages, such as Rust.
Additionally, projects may have host-side tooling written in a wide variety of
languages. Having all of these build together proves to be a large time saver.
Custom scripting
----------------
Embedded projects often require post-processing of build artifacts; these may
include:
* Extracting ELF sections into a different container
* Injecting metadata into firmware images
* Image signing
* Creating databases of symbols for debugging
* Extracting string tokens into a database (for example, with
:ref:`module-pw_tokenizer`)
These are run as steps during a build, facilitated by the build system.
See also
^^^^^^^^
* :ref:`module-pw_build-python-action`
Python
------
Python is a favorite scripting language of many development teams, and here at
Pigweed, we're no exception. Much of Pigweed's host-side tooling is written in
Python. While Python works great for local development, problems can arise when
scripts need to be packaged and distributed for vendors or factory teams. Having
proper support for packaging Python within a build system allows teams to focus
on writing code instead of worrying about distribution.
Size reporting
--------------
On embedded devices, memory is everything. Most projects have some sort of
custom tooling to determine how much flash and RAM space their firmware uses.
Being able to run size reports as part of a build ensures that they are always
up-to-date and allows space usage to be tracked over time.
See also
^^^^^^^^
* :ref:`module-pw_bloat`
Documentation
-------------
An oft-neglected part of software development, documentation is invaluable for
future maintainers of a project. As such, Pigweed has integrated documentation
which builds alongside its code and combines with other build features, such as
size reports, to provide high quality, up-to-date references for developers.
See also
^^^^^^^^
* :ref:`module-pw_docgen`
Unit testing
------------
Unit tests are essential to ensure that the functionality of code remains
consistent as changes are made to avoid accidental regressions. Running unit
tests as part of a build keeps developers constantly aware of the impact of
their changes.
Host-side unit tests
^^^^^^^^^^^^^^^^^^^^
Though Pigweed targets embedded devices, a lot of its code can be run and tested
on a host desktop by swapping out backends to host platform libraries. This is
highly beneficial during development, as it allows tests to consistently run
without having to go through the process of flashing a device.
Device-side unit tests
^^^^^^^^^^^^^^^^^^^^^^
As useful as host-side tests are, they are not sufficient for developing actual
firmware, and it is critical to run tests on the actual hardware. Pigweed has
invested into creating a test framework and build integration for running tests
across physical devices as part of a build.
See also
^^^^^^^^
* :ref:`module-pw_unit_test`
* :ref:`module-pw_target_runner`
Bonus: pw watch
---------------
In web development, it is common to have a file system watcher listening for
source file changes and triggering a build for quick iteration. When combined
with a fast incremental build system, this becomes a powerful feature, allowing
things such as unit tests and size reports to re-run whenever any dependent
code is modified.
While initially seen as somewhat of a gimmick, Pigweed's watcher has become a
staple of Pigweed development, with most Pigweed users having it permanently
running in a terminal window.
See also
^^^^^^^^
* :ref:`module-pw_watch`
Pigweed's build systems
=======================
Pigweed can be used either as a monolith or à la carte, slotting into an
existing project. To this end, Pigweed supports multiple build systems, allowing
Pigweed-based projects to choose the most suitable one for them.
Of the supported build systems, GN is the most full-featured, followed by CMake,
and finally Bazel.
.. note::
A quick note on terminology: the word "target" is overloaded within GN/Bazel (and
Pigweed)---it can refer to either a GN/Bazel build target, such as a ``source_set``
or ``executable``, or to an output platform (e.g. a specific board, device, or
system).
To avoid confusing the two, we refer to the former as "GN/Bazel targets" and the
latter as "Pigweed targets".
GN
--
A perhaps unfamiliar name, `GN (Generate Ninja)`_ is a meta-build system that
outputs `Ninja`_ build files, originally designed for use in Chromium. Pigweed
first experimented with GN after hearing about it from another team, and we
quickly came to appreciate its speed and simplicity. GN has become Pigweed's
primary build system; it is used for all upstream development and strongly
recommended for Pigweed-based projects where possible.
.. _CMake: https://cmake.org/
.. _Bazel: https://bazel.build/
.. _GN (Generate Ninja): https://gn.googlesource.com/gn
.. _Ninja: https://ninja-build.org/
The GN build
============
This section describes Pigweed's GN build structure, how it is used upstream,
build conventions, and recommendations for Pigweed-based projects. While
containing some details about how GN works in general, this section is not
intended to be a guide on how to use GN. To learn more about the tool itself,
refer to the official `GN reference`_.
.. _GN reference: https://gn.googlesource.com/gn/+/master/docs/reference.md
Entrypoint: .gn
---------------
The entrypoint to a GN build is the ``.gn`` file, which defines a project's root
directory (henceforth ``//``).
``.gn`` must point to the location of a ``BUILDCONFIG.gn`` file for the project.
In Pigweed upstream, this is its only purpose.
Downstream projects may additionally use ``.gn`` to set global overrides for
Pigweed's build arguments, which apply across all Pigweed targets. For example,
a project could configure the protobuf libraries that it uses. This is done by
defining a ``default_args`` scope containing the overrides.
.. code::
# The location of the BUILDCONFIG file.
buildconfig = "//BUILDCONFIG.gn"
# Build arguments set across all Pigweed targets.
default_args = {
dir_pw_third_party_nanopb = "//third_party/nanopb-0.4.2"
}
Configuration: BUILDCONFIG.gn
-----------------------------
The file ``BUILDCONFIG.gn`` configures the GN build by defining any desired
global variables/options. It can be located anywhere in the build tree, but is
conventionally placed at the root. ``.gn`` points GN to this file.
``BUILDCONFIG.gn`` is evaluated before any other GN files, and variables defined
within it are placed into GN's global scope, becoming available in every file
without requiring imports.
The options configured in this file differ from those in ``.gn`` in two ways:
1. ``BUILDCONFIG.gn`` is evaluated for every GN toolchain (and Pigweed target),
whereas ``.gn`` is only evaluated once. This allows ``BUILDCONFIG.gn`` to set
different options for each Pigweed target.
2. In ``.gn``, only GN build arguments can be overridden. ``BUILDCONFIG.gn``
allows defining arbitrary variables.
Generally, it is preferable to expose configuration options through build args
instead of globals in ``BUILDCONFIG.gn`` (something Pigweed's build previously
did), as they are more flexible, greppable, and easier to manage. However, it
may make sense to define project-specific constants in ``BUILDCONFIG.gn``.
Pigweed's upstream ``BUILDCONFIG.gn`` does not define any variables; it just
sets Pigweed's default toolchain, which GN requires.
.. _top-level-build:
Top-level GN targets: //BUILD.gn
--------------------------------
The root ``BUILD.gn`` file defines all of the libraries, images, tests, and
binaries built by a Pigweed project. This file is evaluated immediately after
``BUILDCONFIG.gn``, with the active toolchain (which is the default toolchain
at the start of a build).
``//BUILD.gn`` is responsible for enumerating each of the Pigweed targets built
by a project. This is done by instantiating a version of each of the project's
GN target groups with each Pigweed target's toolchain. For example, in upstream,
all of Pigweed's GN targets are contained within the ``pigweed_default`` group.
This group is instantiated multiple times, with different Pigweed target
toolchains.
These groups include the following:
* ``host`` -- builds ``pigweed_default`` with Clang or GCC, depending on the
platform
* ``host_clang`` -- builds ``pigweed_default`` for the host with Clang
* ``host_gcc`` -- builds ``pigweed_default`` for the host with GCC
* ``stm32f429i`` -- builds ``pigweed_default`` for STM32F429i Discovery board
* ``docs`` -- builds the Pigweed documentation and size reports
Pigweed projects are recommended to follow this pattern, creating a top-level
group for each of their Pigweed targets that builds a common GN target with the
appropriate toolchain.
It is important that no dependencies are listed under the default toolchain
within ``//BUILD.gn``, as it does not configure any build parameters, and
therefore should not evaluate any other GN files. The pattern that Pigweed uses
to achieve this is to wrap all dependencies within a condition checking the
toolchain.
.. code::
group("my_application_images") {
deps = [] # Empty in the default toolchain.
if (current_toolchain != default_toolchain) {
# This is only evaluated by Pigweed target toolchains, which configure
# all of the required options to build Pigweed code.
deps += [ "//images:evt" ]
}
}
# The images group is instantiated for each of the project's Pigweed targets.
group("my_pigweed_target") {
deps = [ ":my_application_images(//toolchains:my_pigweed_target)" ]
}
.. warning::
Pigweed's default toolchain is never used, so it is set to a dummy toolchain
which doesn't define any tools. ``//BUILD.gn`` contains conditions which check
that the current toolchain is not the default before declaring any GN target
dependencies to prevent the default toolchain from evaluating any other BUILD
files. All GN targets added to the build must be placed under one of these
conditional scopes.
"default" group
^^^^^^^^^^^^^^^
The root ``BUILD.gn`` file can define a special group named ``default``. If
present, Ninja will build this group when invoked without arguments.
.. tip::
Defining a ``default`` group makes using ``pw watch`` simple!
Optimization levels
^^^^^^^^^^^^^^^^^^^
Pigweed's ``//BUILD.gn`` defines the ``pw_default_optimization_level`` build
arg, which specifies the optimization level to use for the default groups
(``host``, ``stm32f429i``, etc.). The supported values for
``pw_default_optimization_level`` are:
* ``debug`` -- create debugging-friendly binaries (``-Og``)
* ``size_optimized`` -- optimize for size (``-Os``)
* ``speed_optimized`` -- optimized for speed, without increasing code size
(``-O2``)
Pigweed defines versions of its groups in ``//BUILD.gn`` for each optimization
level. Rather than relying on ``pw_default_optimization_level``, you may
directly build a group at the desired optimization level:
``<group>_<optimization>``. Examples include ``host_clang_debug``,
``host_gcc_size_optimized``, and ``stm32f429i_speed_optimized``.
Upstream GN target groups
^^^^^^^^^^^^^^^^^^^^^^^^^
In upstream, Pigweed splits its top-level GN targets into a few logical groups,
which are described below. In order to build a GN target, it *must* be listed in
one of the groups in this file.
apps
~~~~
This group defines the application images built in Pigweed. It lists all of the
common images built across all Pigweed targets, such as modules' example
executables. Each Pigweed target can additionally provide its own specific
images through the ``pw_TARGET_APPLICATIONS`` build arg, which is included by
this group.
host_tools
~~~~~~~~~~
This group defines host-side tooling binaries built for Pigweed.
pw_modules
~~~~~~~~~~
This group lists the main libraries for all of Pigweed's modules.
pw_module_tests
~~~~~~~~~~~~~~~
All modules' unit tests are collected here, so that they can all be run at once.
pigweed_default
~~~~~~~~~~~~~~~
This group defines everything built in a Pigweed build invocation by collecting
the above groups and conditionally depending on them based on the active Pigweed
target's configuration. Generally, new dependencies should not be added here;
instead, use one of the groups listed above.
The ``pigweed_default`` group is instantiated for each upstream Pigweed target's
toolchain.
Pigweed target instantiations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These groups wrap ``pigweed_default`` with a specific target toolchain. They are
named after the Pigweed target, e.g. ``host_clang``, ``stm32f429i``, etc.
Other BUILD files: //\*\*/BUILD.gn
----------------------------------
The rest of the ``BUILD.gn`` files in the tree define libraries, configs, and
build args for each of the modules in a Pigweed project.
Project configuration: //build_overrides/pigweed.gni
----------------------------------------------------
Each Pigweed project must contain a Pigweed configuration file at a known
location in the GN build tree. Currently, this file only contains a single build
argument, which must be set to the GN build path to the root of the Pigweed
repository within the project.
Module variables
----------------
As Pigweed is intended to be a subcomponent of a larger project, it cannot assume
where it or its modules is located. Therefore, Pigweed's upstream BUILD.gn files
do not use absolute paths; instead, variables are defined pointing to each of
Pigweed's modules, set relative to a project-specific ``dir_pigweed``.
To depend on Pigweed modules from GN code, import Pigweed's overrides file and
reference these module variables.
.. code::
# 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
-----------------------
To facilitate injecting global configuration options, Pigweed defines wrappers
around builtin GN target types such as ``source_set`` and ``executable``. These
are defined within ``$dir_pw_build/target_types.gni``.
.. note::
To take advantage of Pigweed's flexible target configuration system, use
``pw_*`` target types (e.g. ``pw_source_set``) in your BUILD.gn files instead
of GN builtins.
Pigweed targets
---------------
To build for a specific hardware platform, builds define Pigweed targets. These
are essentially GN toolchains which set special arguments telling Pigweed how to
build. For information on Pigweed's target system, refer to
:ref:`docs-targets`.
The dummy toolchain
-------------------
Pigweed's ``BUILDCONFIG.gn`` sets the project's default toolchain to a "dummy"
toolchain which does not specify any compilers or override any build arguments.
Downstream projects are recommended to do the same, following the steps
described in :ref:`top-level-build` to configure builds for each of their
Pigweed targets.
.. admonition:: Why use a dummy?
To support some of its advanced (and useful!) build features, Pigweed requires
the ability to generate new toolchains on the fly. This requires having
knowledge of the full configuration of the current toolchain (which is easy if
it's all defined within a scope), something which is impractical to achieve
using the default toolchain.
Additionally, there are some cases where GN treats default and non-default
toolchains differently. By not using the default toolchain, we avoid having
to deal with these inconsistencies.
It is possible to build Pigweed using only the default toolchain, but it
requires a more complicated setup to get everything working and should be
avoided unless necessary (for example, when integrating with a large existing
GN-based project).
Upstream development examples
-----------------------------
If developing for upstream Pigweed, some common build use cases are described
below.
Building a custom executable/app image
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. Define your executable GN target using the ``pw_executable`` template.
.. code::
# //foo/BUILD.gn
pw_executable("foo") {
sources = [ "main.cc" ]
deps = [ ":libfoo" ]
}
2. In the root ``BUILD.gn`` file, add the executable's GN target to the ``apps``
group.
.. code::
# //BUILD.gn
group("apps") {
deps = [
# ...
"//foo", # Shorthand for //foo:foo
]
}
3. Run the ninja build to compile your executable. The apps group is built by
default, so there's no need to provide a target. The executable will be
compiled for every supported Pigweed target.
.. code::
ninja -C out
Alternatively, build your executable by itself by specifying its path to
Ninja. When building a GN target manually, the Pigweed target for which it
is built must be specified on the Ninja command line.
For example, to build for the Pigweed target ``host_gcc_debug``:
.. code::
ninja -C out host_gcc_debug/obj/foo/bin/foo
.. note::
The path passed to Ninja is a filesystem path within the ``out`` directory,
rather than a GN path. This path can be found by running ``gn outputs``.
4. Retrieve your compiled binary from the out directory. It is located at the
path
.. code::
out/<pw_target>/obj/<gn_path>/{bin,test}/<executable>
where ``pw_target`` is the Pigweed target for which the binary was built,
``gn_path`` is the GN path to the BUILD.gn file defining the executable,
and ``executable`` is the executable's GN target name (potentially with an
extension). Note that the executable is located within a ``bin`` subdirectory
in the module (or ``test`` for unit tests defined with ``pw_test``).
For example, the ``foo`` executable defined above and compiled for the
Pigweed target stm32f429i_disc1_debug is found at:
.. code::
out/stm32f429i_disc1_debug/obj/foo/bin/foo
CMake
-----
A well-known name in C/C++ development, `CMake`_ is widely used by all kinds of
projects, including embedded devices. Pigweed's CMake support is provided
primarily for projects that have an existing CMake build and wish to integrate
Pigweed modules.
Bazel
-----
The open source version of Google's internal build system. `Bazel`_ has been
growing in popularity within the open source world, as well as being adopted by
various proprietary projects. Its modular structure makes it a great fit for
à la carte usage.
.. note::
Bazel support is experimental and only for the brave for now. If you are
looking for stable set of build API's please use GN.
The Bazel build
===============
This section describes Pigweed's Bazel build structure, how it is used upstream,
build conventions, and recommendations for Pigweed-based projects. While
containing some details about how Bazel works in general, this section is not
intended to be a guide on how to use Bazel. To learn more about the tool itself,
refer to the official `Bazel reference`_.
.. _Bazel reference: https://docs.bazel.build/versions/4.0.0/bazel-overview.html
General usage
-------------
While described in more detail in the Bazel docs there a few Bazel features that
are of particular importance when targeting embedded platforms. The most
commonly used commands used in bazel are;
.. code:: sh
bazel build //your:target
bazel test //your:target
bazel coverage //your:target
.. note:: Code coverage support is only available on the host for now.
Building
^^^^^^^^
When it comes to building/testing your Bazel target for a specific Pigweed
target (e.g. stm32f429i-discovery) a slight variation is required.
.. code:: sh
bazel build //your:target \
--platforms=@pigweed//pw_build/platforms:stm32f429i-disc1
For more information on how to create your own platforms refer to the official
`Bazel platforms reference`_. You may also find helpful examples of constraints
and platforms in the '//pw_build/platforms' and '//pw_build/constraints'
directories.
.. _Bazel platforms reference: https://docs.bazel.build/versions/master/platforms.html
Testing
^^^^^^^
Running tests on an embedded target with Bazel is possible although support for
this is experimental. The easiest way of achieving this at the moment is to use
Bazel's '--run_under' flag. To make this work create a Bazel target
('//your_handler') that;
1. Takes a single argument (the path to the elf) and uploads the elf to your
Pigweed target.
2. Connects with your target using serial or other communication method.
3. Listens to the communication transport for the keywords ("PASSED", "FAIL")
and returns (0, 1) respectively if one of the keywords is intercepted. (This
step assumes you are using the pw_unit_test package and it is configured for
your target).
4. Run;
.. code:: sh
bazel test //your:test --platforms=//your/platform --run_under=//your_handler
Code Coverage
^^^^^^^^^^^^^
Making use of the code coverage functionality in Bazel is straightforward.
1. Add the following lines to your '.bazelrc'.
.. code:: sh
coverage --experimental_generate_llvm_lcov
coverage --combined_report=lcov
2. Generate a combined lcov coverage report. This will produce a combined lcov
coverage report at the path 'bazel-out/_coverage/_coverage_report.dat'. e.g.
.. code:: sh
bazel coverage //pw_log/...
3. View the results using the command line utility 'lcov'.
.. code:: sh
lcov --list bazel-out/_coverage/_coverage_report.dat
Configuration
-------------
Generally speaking there are three primary concepts that make up Bazel's
configuration API.
1. Selects
2. Compatibility lists
3. Flags/Build settings
Selects
^^^^^^^
Selects are useful for specifying different dependencies/source depending on the
platform that is currently being targeted. For more information on this please
see the `Bazel selects reference`_. e.g.
.. code:: py
pw_cc_library(
name = "some_platform_dependant_library",
deps = select({
"@platforms//cpu:armv7e-m": [":arm_libs"],
"//conditions:default": [":host_libs"],
}),
)
Compatibility lists
^^^^^^^^^^^^^^^^^^^
Compatibility lists allow you to specify which platforms your targets are
compatible with. Consider an example where you want to specify that a target is
compatible with only a host os;
.. code:: py
pw_cc_library(
name = "some_host_only_lib",
srcs = ["host.cc"],
target_compatible_with = select({
"@platforms//os:windows": [],
"@platforms//os:linux": [],
"@platforms//os:macos": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
)
In this case building from or for either Windows/Linux/Mac will be supported, but
other OS's will fail if this target is explicitly depended on. However if
building with a wild card for a non-host platform this target will be skipped
and the build will continue. e.g.
.. code:: sh
bazel build //... --platforms=@pigweed//pw_build/platforms:cortex_m0
This allows for you to easily create compatibility matricies without adversely
affecting your ability build your entire repo for a given Pigweed target.
For more detailed information on how to use the target_compatible_with attribute
please see `Bazel target_compatible_with reference`_.
Flags/build settings
^^^^^^^^^^^^^^^^^^^^
Flags/build settings are particularly useful in scenarios where you may want to
be able to quickly inject a dependency from the command line but don't
necessarily want to create an entirely new set of constraints to use with a
select statement.
.. note::
The scope for what is possible with build flags/settings goes well beyond
what will be described here. For more detailed information on flags/settings
please see `Bazel config reference`_.
A simple example of when it is useful to use a label_flag is when you want to
swap out a single dependency from the command line. e.g.
.. code:: py
pw_cc_library(
name = "some_default_io",
srcs = ["default_io.cc"],
)
pw_cc_library(
name = "some_other_io",
srcs = ["other_io.cc"],
)
label_flag(
name = "io",
default_build_setting = ":some_default_io",
)
pw_cc_library(
name = "some_target_that_needs_io",
deps = [":io"],
)
From here the label_flag by default redirects to the target ":some_default_io",
however it is possible to override this from the command line. e.g.
.. code:: sh
bazel build //:some_target_that_needs_io --//:io=//:some_other_io
.. _Bazel selects reference: https://docs.bazel.build/versions/master/configurable-attributes.html#select-and-dependencies
.. _Bazel target_compatible_with reference: https://docs.bazel.build/versions/master/platforms.html#skipping-incompatible-targets
.. _Bazel config reference: https://docs.bazel.build/versions/master/skylark/config.html
Pigweeds configuration
^^^^^^^^^^^^^^^^^^^^^^
Pigweeds Bazel configuration API is designed to be distributed across the
Pigweed repository and/or your downstream repository. If you are coming from
GN's centralized configuration API it might be useful to think about
Pigweed+Bazel's configuration as the transpose of GN's configuration. The
configuration interface that is supported by Pigweed is designed to start simple
and then grow with your project.
.. note::
There are plans to extend the configuration API for Bazel. However,
currently the only configurations that are available under the Bazel+Pigweed
configuration API is the ability to switch facade backends. For more
information on what this is please see the
:ref:`docs-module-structure-facades` section of :ref:`docs-module-structure`.
Consider a scenario that you are building a flight controller for a
spacecraft. But have very little experience with Pigweed and you have just
landed here. First things first you would;
1. Set up your WORKSPACE to fetch the Pigweeds repository. Then add the
dependencies that you need from Pigweeds WORKSPACE.
2. Add a pigweed_config rule to your WORKSPACE, using Pigweed's default
configuration.
.. code:: py
# WORKSPACE ...
load("//pw_build:target_config.bzl", "pigweed_config")
# Configure Pigweeds backend.
pigweed_config(
name = "pigweed_config",
build_file = "@pigweed//targets:default_config.BUILD",
)
.. note::
We are aware, that the experience of setting up your WORKSPACE file to work
with pigweed is less than ideal. This API is under construction, but we are
working on this!
..
TODO: Add in a better way to pull down WORKSPACE dependencies in Bazel then
add docs in here.
Now to explain what is going on here. Housed under the "pigweed_config" remote
repository is a set of configuration flags. These can be used to inject
dependencies into facades to override the default backend.
Continuing on with our scenario, consider that you maybe want to try using the
'//pw_chrono' module. So you create a target in your repository like so;
.. code::
# BUILD
pw_cc_library(
name = "time_is_relative",
srcs = ["relative_time_on_earth.cc"],
deps = ["@pigweed//pw_chrono"],
)
Now this should work out of the box for any host operating system. e.g. Running;
.. code::
bazel build //:time_is_relative
will produce a working library. But as your probably here because Pigweed offers
a set of embedded libraries you might be interested in running your code on some
random micro-controller/FPGA combined with an RTOS. For now let's assume that by
some coincidence you are using FreeRTOS and are happy to make use
of our default '//pw_chrono' backend for FreeRTOS. You could build the following
with;
.. code:: sh
bazel build //:time_is_relative \
--platforms=@pigweed//pw_build/platforms:freertos
There is a fair bit to unpack here in terms of how our configuration system
is determining which dependencies to choose for your build. The dependency
tree (that is important for configuration) in a project such as this would
look like.
.. code::
@pigweed//pw_chrono:pw_chrono_facade <-----------.
^ |
| @pigweed//pw_chrono_freertos:system_clock
| (Actual backend)
| ^
| |
| @pigweed//pw_chrono:backend_multiplexer
| Select backend based on OS:
| [FreeRTOS (X), Embos ( ), STL ( ), Threadx ( )]
| ^
| |
@pigweed//pw_chrono -------> @pigweed_config//:pw_chrono_backend
^ (Injectable)
|
//:time_is_relative
So when evaluating this setup Bazel checks the dependencies for '//pw_chrono'
and finds that it depends on "@pigweed_config//:pw_chrono_backend" which looks
like this;
.. code:: py
# pw_chrono config.
label_flag(
name = "pw_chrono_backend",
build_setting_default = "@pigweed//pw_chrono:backend_multiplexer",
)
Looking at the 'build_setting_default' we can see that by default it depends
back on the target "@pigweed//pw_chrono:backend_multiplexer". If you only had one
backend you could actually just change the 'build_setting_default' to point
directly to your backend. However because we have four different backends we
have to use the select semantics to choose the right one. In this case it looks
like;
.. code:: py
pw_cc_library(
name = "backend_multiplexer",
visibility = ["@pigweed_config//:__pkg__"],
deps = select({
"@pigweed//pw_build/constraints/rtos:freertos":
["//pw_chrono_freertos:system_clock"],
"@pigweed//pw_build/constraints/rtos:embos":
["//pw_chrono_embos:system_clock"],
"@pigweed//pw_build/constraints/rtos:threadx":
["//pw_chrono_threadx:system_clock"],
"//conditions:default": ["//pw_chrono_stl:system_clock"],
}),
)
Intuitively you can see that the first option was selected, which terminates
the configuration chain.
Continuing on with our scenario let's say that you have read
:ref:`docs-module-structure` and now want to implement your own backend for
'//pw_chrono' using a hardware RTC. In this case you would create a new
directory 'pw_chrono_my_hardware_rtc'. To ensure that your new backend compiles
with the facade an easy and temporary way to override the dependency tree is
to override the label flag in '@pigweed_config'. For example;
.. code:: sh
bazel build //:time_is_relative \
--@pigweed_config//pw_chrono_backend=//pw_chrono_my_hardware_rtc:system_clock
This temporarily modifies the build graph to look something like this;
.. code::
@pigweed//pw_chrono:pw_chrono_facade <-----.
^ |
| @your_workspace//pw_chrono_my_hardware_rtc:system_clock
| (Actual backend)
| ^
| |
@pigweed//pw_chrono -> @pigweed_config//:pw_chrono_backend
^ (Injectable)
|
//:time_is_relative
Now while this is a nice temporary change, but you might find yourself in need
of a more permanent configuration. Particularly if you want to override multiple
different backends. In other words if you had several backends to override, that
would translate to several different command line flags (one for each override).
This problem further compounds as you have multiple Pigweed targets all
requiring different combinations of different backends as you can't even reuse
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,
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: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;
1. Create a constraint_setting and a set of constraint_value's for the flight
computer. For example;
.. code:: py
# //platforms/flight_computer/BUILD
constraint_setting(
name = "flight_computer",
)
constraint_value(
name = "primary",
constraint_setting = ":flight_computer",
)
constraint_value(
name = "backup",
constraint_setting = ":flight_computer",
)
2. Create a set of platforms that can be used to switch constraint_value's.
For example;
.. code:: py
# //platforms/BUILD
platform(
name = "primary_computer",
constraint_values = ["//platforms/flight_computer:primary"],
)
platform(
name = "backup_computer",
constraint_values = ["//platforms/flight_computer:backup"],
)
3. Create a target multiplexer that will select the right backend depending on
which computer you are using. For example;
.. code:: py
# //pw_chrono/BUILD
load("//pw_build:pigweed.bzl", "pw_cc_library")
pw_cc_library(
name = "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. Copy and paste across the target/default_config.BUILD across from the
Pigweed repository and modifying the build_setting_default for the target
'pw_chrono_backend' to point to your new backend_multiplexer target. For example;
This;
.. code:: py
# @pigweed//target:default_config.BUILD
label_flag(
name = "pw_chrono_backend",
build_setting_default = "@pigweed//pw_chrono:backend_multiplexer",
)
Becomes this;
.. code:: py
# @your_workspace//target:your_config.BUILD
label_flag(
name = "pw_chrono_backend",
build_setting_default =
"@your_workspace//pw_chrono:backend_multiplexer",
)
5. Switch your workspace 'pigweed_config' rule over to use your custom config.
.. code:: py
# WORKSPACE
pigweed_config(
name = "pigweed_config",
build_file = "//target/your_config.BUILD",
)
Building your target now will result in slightly different build graph. For
example, running;
.. code:: sh
bazel build //:time_is_relative --platforms=//platforms:primary_computer
Will result in a build graph that looks like;
.. code::
@pigweed//pw_chrono:pw_chrono_facade <---.
^ |
| @your_workspace//pw_chrono_my_hardware_rtc:system_clock
| (Actual backend)
| ^
| |
| @your_workspace//pw_chrono:backend_multiplexer
| Select backend based on OS:
| [Primary (X), Backup ( ), Host only default ( )]
| ^
| |
@pigweed//pw_chrono -> @pigweed_config//:pw_chrono_backend
^ (Injectable)
|
//:time_is_relative