Sysbuild is a higher-level build system that can be used to combine multiple other build systems together. It is a higher-level layer that combines one or more Zephyr build systems and optional additional build systems into a hierarchical build system.
For example, you can use sysbuild to build a Zephyr application together with the MCUboot bootloader, flash them both onto your device, and debug the results.
Sysbuild works by configuring and building at least a Zephyr application and, optionally, as many additional projects as you want. The additional projects can be either Zephyr applications or other types of builds you want to run.
Like Zephyr's build system <build_overview>
, sysbuild is written in CMake and uses Kconfig <kconfig>
.
The following are some key concepts used in this document:
- Single-image build
When sysbuild is used to create and manage just one Zephyr application's build system.
- Multi-image build
When sysbuild is used to manage multiple build systems. The word "image" is used because your main goal is usually to generate the binaries of the firmware application images from each build system.
- Domain
Every Zephyr CMake build system managed by sysbuild.
- Multi-domain
When more than one Zephyr CMake build system (domain) is managed by sysbuild.
This figure is an overview of sysbuild's inputs, outputs, and user interfaces:
The following are some key sysbuild features indicated in this figure:
- You can run sysbuild either with
west build <west-building>
or directly viacmake
. - You can use sysbuild to generate application images from each build system, shown above as ELF, BIN, and HEX files.
- You can configure sysbuild or any of the build systems it manages using various configuration variables. These variables are namespaced so that sysbuild can direct them to the right build system. In some cases, such as the
BOARD
variable, these are shared among multiple build systems. - Sysbuild itself is also configured using Kconfig. For example, you can instruct sysbuild to build the MCUboot bootloader, as well as to build and link your main Zephyr application as an MCUboot child image, using sysbuild's Kconfig files.
- Sysbuild integrates with west's
west-build-flash-debug
commands. It does this by managing thewest-runner
, and specifically therunners.yaml
files that each Zephyr build system will contain. These are packaged into a global view of how to flash and debug each build system in adomains.yaml
file generated and managed by sysbuild. - Build names are prefixed with the target name and an underscore, for example the sysbuild target is prefixed with
sysbuild_
and if MCUboot is enabled as part of sysbuild, it will be prefixed withmcuboot_
. This also allows for running things like menuconfig with the prefix, for example (if using ninja)ninja sysbuild_menuconfig
to configure sysbuild or (if using make)make mcuboot_menuconfig
.
As mentioned above, you can run sysbuild via west build
or cmake
.
west build
Here is an example. For details, see west-multi-domain-builds
in the west build documentation
.
Tip
To configure west build
to use --sysbuild
by default from now on, run:
west config build.sysbuild True
Since sysbuild supports both single- and multi-image builds, this lets you use sysbuild all the time, without worrying about what type of build you are running.
To turn this off, run this before generating your build system:
west config build.sysbuild False
To turn this off for just one west build
command, run:
west build --no-sysbuild ...
cmake
Here is an example using CMake and Ninja.
To use sysbuild directly with CMake, you must specify the sysbuild project as the source folder, and give -DAPP_DIR=<path-to-sample>
as an extra CMake argument. APP_DIR
is the path to the main Zephyr application managed by sysbuild.
When building a single Zephyr application without sysbuild, all CMake cache settings and Kconfig build options given on the command line as -D<var>=<value>
or -DCONFIG_<var>=<value>
are handled by the Zephyr build system.
However, when sysbuild combines multiple Zephyr build systems, there could be Kconfig settings exclusive to sysbuild (and not used by any of the applications). To handle this, sysbuild has namespaces for configuration variables. You can use these namespaces to direct settings either to sysbuild itself or to a specific Zephyr application managed by sysbuild using the information in these sections.
The following example shows how to build hello_world
with MCUboot enabled, applying to both images debug optimizations:
west build
cmake
See the following subsections for more information.
CMake variable settings can be passed to CMake using -D<var>=<value>
on the command line. You can also set Kconfig options via CMake as -DCONFIG_<var>=<value>
or -D<namespace>_CONFIG_<var>=<value>
.
Since sysbuild is the entry point for the build system, and sysbuild is written in CMake, all CMake variables are first processed by sysbuild.
Sysbuild creates a namespace for each domain. The namespace prefix is the domain's application name. See sysbuild_zephyr_application
for more information.
To set the variable <var>
in the namespace <namespace>
, use this syntax:
-D<namespace>_<var>=<value>
For example, to set the CMake variable FOO
in the my_sample
application build system to the value BAR
, run the following commands:
west build
west build --sysbuild ... -- -Dmy_sample_FOO=BAR
cmake
cmake -Dmy_sample_FOO=BAR ...
To set the sysbuild Kconfig option <var>
to the value <value>
, use this syntax:
-DSB_CONFIG_<var>=<value>
In the previous example, SB_CONFIG
is the namespace prefix for sysbuild's Kconfig options.
To set a Zephyr application's Kconfig option instead, use this syntax:
-D<namespace>_CONFIG_<var>=<value>
In the previous example, <namespace>
is the application name discussed above in sysbuild_cmake_namespace
.
For example, to set the Kconfig option FOO
in the my_sample
application build system to the value BAR
, run the following commands:
west build
west build --sysbuild ... -- -Dmy_sample_CONFIG_FOO=BAR
cmake
cmake -Dmy_sample_CONFIG_FOO=BAR ...
Tip
When no <namespace>
is used, the Kconfig setting is passed to the main Zephyr application my_sample
.
This means that passing -DCONFIG_<var>=<value>
and -Dmy_sample_CONFIG_<var>=<value>
are equivalent.
This allows you to build the same application with or without sysbuild using the same syntax for setting Kconfig values at CMake time. For example, the following commands will work in the same way:
west build -b <board> my_sample -- -DCONFIG_FOO=BAR
west build -b <board> --sysbuild my_sample -- -DCONFIG_FOO=BAR
You can use west flash <west-flashing>
to flash applications with sysbuild.
When invoking west flash
on a build consisting of multiple images, each image is flashed in sequence. Extra arguments such as --runner jlink
are passed to each invocation.
For more details, see west-multi-domain-flashing
.
You can use west debug
to debug the main application, whether you are using sysbuild or not. Just follow the existing west debug <west-debugging>
guide to debug the main sample.
To debug a different domain (Zephyr application), such as mcuboot
, use the --domain
argument, as follows:
west debug --domain mcuboot
For more details, see west-multi-domain-debugging
.
Sysbuild supports MCUboot natively.
To build a sample like hello_world
with MCUboot, enable MCUboot and build and flash the sample as follows:
west build
cmake
This builds hello_world
and mcuboot
for the reel_board
, and then flashes both the mcuboot
and hello_world
application images to the board.
More detailed information regarding the use of MCUboot with Zephyr can be found in the MCUboot with Zephyr documentation page on the MCUboot website.
Note
MCUBoot default configuration will perform a full chip erase when flashed. This can be controlled through the MCUBoot Kconfig option CONFIG_ZEPHYR_TRY_MASS_ERASE
. If this option is enabled, then flashing only MCUBoot, for example using west flash --domain mcuboot
, may erase the entire flash, including the main application image.
You can set sysbuild's Kconfig options for a single application using configuration files. By default, sysbuild looks for a configuration file named sysbuild.conf
in the application top-level directory.
In the following example, there is a sysbuild.conf
file that enables building and flashing with MCUboot whenever sysbuild is used:
<home>/application
├── CMakeLists.txt
├── prj.conf
└── sysbuild.conf
SB_CONFIG_BOOTLOADER_MCUBOOT=y
You can set a configuration file to use with the -DSB_CONF_FILE=<sysbuild-conf-file>
CMake build setting.
For example, you can create sysbuild-mcuboot.conf
and then specify this file when building with sysbuild, as follows:
west build
cmake
Sysbuild creates build targets for each image (including sysbuild itself) for the following modes:
- menuconfig
- hardenconfig
- guiconfig
For the main application (as is the same without using sysbuild) these can be ran normally without any prefix. For other images (including sysbuild), these are ran with a prefix of the image name and an underscore e.g. sysbuild_
or mcuboot_
, using ninja or make - for details on how to run image build targets that do not have mapped build targets in sysbuild, see the sysbuild_dedicated_image_build_targets
section.
Not all build targets for images are given equivalent prefixed build targets when sysbuild is used, for example build targets like ram_report
, rom_report
, footprint
, puncover
and pahole
are not exposed. When using Trusted Firmware <tfm_build_system>
, this includes build targets prefix with tfm_
and bl2_
, for example: tfm_rom_report
and bl2_ram_report
. To run these build targets, the build directory of the image can be provided to west/ninja/make along with the name of the build target to execute and it will run.
west
Assuming that a project has been configured and built using west
using sysbuild with mcuboot enabled in the default build
folder location, the rom_report
build target for mcuboot
can be ran with:
west build -d build/mcuboot -t rom_report
ninja
Assuming that a project has been configured using cmake
and built using ninja
using sysbuild with mcuboot enabled, the rom_report
build target for mcuboot
can be ran with:
ninja -C mcuboot rom_report
make
Assuming that a project has been configured using cmake
and built using make
using sysbuild with mcuboot enabled, the rom_report
build target for mcuboot
can be ran with:
make -C mcuboot rom_report
You can use the ExternalZephyrProject_Add()
function to add Zephyr applications as sysbuild domains. Call this CMake function from your application's sysbuild.cmake
file, or any other CMake file you know will run as part sysbuild CMake invocation.
To include my_sample
as another sysbuild domain, targeting the same board as the main image, use this example:
ExternalZephyrProject_Add(
APPLICATION my_sample
SOURCE_DIR <path-to>/my_sample
)
This could be useful, for example, if your board requires you to build and flash an SoC-specific bootloader along with your main application.
In sysbuild and Zephyr CMake build system a board may refer to:
- A physical board with a single core SoC.
- A specific core on a physical board with a multi-core SoC, such as
nrf5340dk_nrf5340
. - A specific SoC on a physical board with multiple SoCs, such as
nrf9160dk_nrf9160
andnrf9160dk_nrf52840
.
If your main application, for example, is built for mps2_an521
, and your helper application must target the mps2_an521_remote
board (cpu1), add a CMake function call that is structured as follows:
ExternalZephyrProject_Add(
APPLICATION my_sample
SOURCE_DIR <path-to>/my_sample
BOARD mps2_an521_remote
)
This could be useful, for example, if your main application requires another helper Zephyr application to be built and flashed alongside it, but the helper runs on another core in your SoC.
You can control whether extra applications are included as sysbuild domains using Kconfig.
If the extra application image is specific to the board or an application, you can create two additional files: sysbuild.cmake
and Kconfig.sysbuild
.
For an application, this would look like this:
<home>/application
├── CMakeLists.txt
├── prj.conf
├── Kconfig.sysbuild
└── sysbuild.cmake
In the previous example, sysbuild.cmake
would be structured as follows:
if(SB_CONFIG_SECOND_SAMPLE)
ExternalZephyrProject_Add(
APPLICATION second_sample
SOURCE_DIR <path-to>/second_sample
)
endif()
Kconfig.sysbuild
would be structured as follows:
source "sysbuild/Kconfig"
config SECOND_SAMPLE
bool "Second sample"
default y
This will include second_sample
by default, while still allowing you to disable it using the Kconfig option SECOND_SAMPLE
.
For more information on setting sysbuild Kconfig options, see sysbuild_kconfig_namespacing
.
When adding a Zephyr application to sysbuild, such as MCUboot, then the configuration files from the application (MCUboot) itself will be used.
When integrating multiple applications with each other, then it is often necessary to make adjustments to the configuration of extra images.
Sysbuild gives users the ability of creating Kconfig fragments or devicetree overlays that will be used together with the application's default configuration. Sysbuild also allows users to change application-configuration-directory
in order to give users full control of an image's configuration.
In the folder of the main application, create a Kconfig fragment or a devicetree overlay under a sysbuild folder, where the name of the file is <image>.conf
or <image>.overlay
, for example if your main application includes my_sample
then create a sysbuild/my_sample.conf
file or a devicetree overlay sysbuild/my_sample.overlay
.
A Kconfig fragment could look as:
# sysbuild/my_sample.conf
CONFIG_FOO=n
In the folder of the main application, create a new folder under sysbuild/<image>/
. This folder will then be used as APPLICATION_CONFIG_DIR
when building <image>
. As an example, if your main application includes my_sample
then create a sysbuild/my_sample/
folder and place any configuration files in there as you would normally do:
<home>/application
├── CMakeLists.txt
├── prj.conf
└── sysbuild
└── my_sample
├── prj.conf
├── app.overlay
└── boards
├── <board_A>.conf
├── <board_A>.overlay
├── <board_B>.conf
└── <board_B>.overlay
All configuration files under the sysbuild/my_sample/
folder will now be used when my_sample
is included in the build, and the default configuration files for my_sample
will be ignored.
This give you full control on how images are configured when integrating those with application
.
Sometimes, in a multi-image build, you may want certain Zephyr applications to be configured or flashed in a specific order. For example, if you need some information from one application's build system to be available to another's, then the first thing to do is to add a configuration dependency between them. Separately, you can also add flashing dependencies to control the sequence of images used by west flash
; this could be used if a specific flashing order is required by an SoC, a _runner, or something else.
By default, sysbuild will configure and flash applications in the order that they are added, as ExternalZephyrProject_Add()
calls are processed by CMake, but this order is not well defined. To make adjustments according to your needs, you can use the sysbuild_add_dependencies()
function. Its usage is similar to the standard add_dependencies()
function in CMake.
Here is an example of adding configuration dependencies for my_sample
:
sysbuild_add_dependencies(IMAGE CONFIGURE my_sample sample_a sample_b)
This will ensure that sysbuild will run CMake for sample_a
and sample_b
(in some order) before doing the same for my_sample
, when building these domains in a single invocation.
If you want to add flashing dependencies instead, then do it like this:
sysbuild_add_dependencies(IMAGE FLASH my_sample sample_a sample_b)
As a result, my_sample
will be flashed after sample_a
and sample_b
(in some order), when flashing these domains in a single invocation.
Tip
Unlike with add_dependencies()
, you are allowed to use sysbuild_add_dependencies()
with non-existent images. This makes it possible to define relationships between various images in the build system, regardless of whether they are present in the current multi-image build.
You can include non-Zephyr applications in a multi-image build using the standard CMake module ExternalProject. Please refer to the CMake documentation for usage details.
When using ExternalProject
, the non-Zephyr application will be built as part of the sysbuild build invocation, but west flash
or west debug
will not be aware of the application. Instead, you must manually flash and debug the application.
Sysbuild can be extended by other modules to give it additional functionality or include other configuration or images, an example could be to add support for another bootloader or external signing method.
Modules can be extended by adding custom CMake or Kconfig files as normal modules <module-yml>
do, this will cause the files to be included in each image that is part of a project. Alternatively, there are sysbuild-specific module extension <sysbuild_module_integration>
files which can be used to include CMake and Kconfig files for the overall sysbuild image itself, this is where e.g. a custom image for a particular board or SoC can be added.
When integrating multiple Zephyr applications into a single sysbuild project, you may find it useful to distinguish between different categories of images, such as bootloaders and non-bootloaders. Each image could be further divided according to which core it targets on whichever SoC on your physical board. As these groups emerge, often with some overlap, you may want all images in a particular group to share a set of properties, including a common configuration or a relationship to another group.
You can use the ExternalZephyrProject_Group()
function to arbitrarily define named groups of sysbuild domains. This function can both create new groups and progressively extend the definition of existing groups in-tree and out-of-tree.
When adding a new Zephyr application to sysbuild, you can assign it to a group simply like this:
ExternalZephyrProject_Add(
APPLICATION my_sample
SOURCE_DIR <path-to>/my_sample
GROUP my_group
)
Alternatively, you could use the ExternalZephyrProject_Group()
function:
ExternalZephyrProject_Group(
other_group
INCLUDE my_sample
)
In particular, if you are defining an out-of-tree group and you want to assign pre-existing application images to it, then this is the way to do that. At this point, my_sample
belongs to both my_group
and other_group
.
As mentioned above, it is possible to extend the definition of existing groups in sysbuild. For instance, if you would like to add more images to my group
, then you can do it as follows:
ExternalZephyrProject_Group(
my_group
INCLUDE hci_rpmsg mcuboot
)
Now, my_group
contains my_sample
, hci_rpmsg
, and mcuboot
, provided that all of these images are present in the current multi-image build (non-existent images are quietly ignored). This works, because calling this function multiple times for the same group has a cumulative effect by default.
On the other hand, if you want to actively prevent my_group
from being extended like this, then you can define it as immutable:
ExternalZephyrProject_Group(
my_group
IMMUTABLE
INCLUDE my_sample
)
An immutable group can only be defined once. In this case, since my_group
already exists, this would produce an error, so you could use a different name to ensure that your group has a consistent meaning in the build system.
Besides defining sysbuild groups in terms of individual images, you also have the option to inherit images from another group:
ExternalZephyrProject_Group(
my_group
INCLUDE_FROM other_group
)
Here, every image in other_group
now belongs to my_group
as well. Remember that groups can be defined progressively, so as more images are added to other_group
during sysbuild's own CMake configuration stage, those same images will automatically appear in my_group
as well.
As my_group
starts inheriting images from other groups, its final contents may become less predictable. If you know for sure that there are certain images which should not be part of this group, then you can filter them out, like so:
ExternalZephyrProject_Group(
my_group
EXCLUDE hello_world
)
Similarly, you can filter out entire groups of images:
ExternalZephyrProject_Group(
another_group
INCLUDE_FROM my_group
EXCLUDE_FROM other_group
)
This example defines another_group
, which contains images that are unique to my_group
and do not overlap with other_group
. This illustrates a basic set difference operation for sysbuild groups.
Exclusion takes precedence over inclusion when evaluating the contents of each group. For example, recall that the image my_sample
has been added to both my_group
and other_group
, but it is filtered out from another_group
. Furthermore, trying to add that image back to the group by naïvely extending it:
ExternalZephyrProject_Group(
another_group
INCLUDE my_sample
)
would not work, because the prior EXCLUDE_FROM
directive still applies with higher priority. If you do need this to work, then you should consider defining yet another group:
ExternalZephyrProject_Group(
yet_another_group
INCLUDE_FROM another_group
INCLUDE my_sample
)
In this way, yet_another_group
can successfully include my_sample
, even though another_group
excludes it. This is because group contents are evaluated in isolation, so by the time another_group
is merged in, its own inclusions and exclusions will already have been squashed. The same goes for my_group
and other_group
, which need to be resolved like this first.
Just like for individual Zephyr applications, you can add configuration or flashing dependencies for groups of these applications. See sysbuild_zephyr_application_dependencies
for more information.
Here is an example of group-wise dependencies:
sysbuild_add_dependencies(GROUP CONFIGURE my_group group_a group_b)
sysbuild_add_dependencies(GROUP FLASH my_group group_a group_b)
This will ensure that every image in group_a
and group_b
will be configured and flashed before every image in my_group
. The ordering within the groups themselves is not determined here. Moreover, groups are not treated as units; once other image-wise and group-wise dependencies are factored in, the images belonging to one group are not expected to stick together in the sorted order.
Note that my_group
is allowed to overlap with group_a
or group_b
. If two groups overlap and one group depends on the other, then the images that the two groups have in common will be placed in the middle of the sorted order, relative to the images unique to either group.
To help illustrate what this means, consider a different example:
ExternalZephyrProject_Group(
group_a
INCLUDE i_1 i_2 i_3 i_4 i_5 i_6
)
ExternalZephyrProject_Group(
group_b
INCLUDE i_1 i_2 i_3 i_7 i_8 i_9
)
sysbuild_add_dependencies(GROUP FLASH group_a group_b)
The flashing order represented by this dependency would be something like this:
┌───────────────────────────── unique to group_b
│ ┌─────── unique to group_a
i_7 i_1 i_4
- { i_8 } -> { i_2 } -> { i_5 }
- i_9 i_3 i_6
└────────────────── intersection
In terms of image-wise sysbuild dependencies, this is equivalent to:
sysbuild_add_dependencies(IMAGE FLASH i_1 i_7 i_8 i_9)
sysbuild_add_dependencies(IMAGE FLASH i_2 i_7 i_8 i_9)
sysbuild_add_dependencies(IMAGE FLASH i_3 i_7 i_8 i_9)
sysbuild_add_dependencies(IMAGE FLASH i_4 i_1 i_2 i_3 i_7 i_8 i_9)
sysbuild_add_dependencies(IMAGE FLASH i_5 i_1 i_2 i_3 i_7 i_8 i_9)
sysbuild_add_dependencies(IMAGE FLASH i_6 i_1 i_2 i_3 i_7 i_8 i_9)
If you are not sure how this works when a group has multiple dependencies, remember that the sysbuild_add_dependencies()
function is associative, so the following is true:
sysbuild_add_dependencies(GROUP CONFIGURE g_1 g_2 g_3 ... g_n)
# This is equivalent to:
sysbuild_add_dependencies(GROUP CONFIGURE g_1 g_2)
sysbuild_add_dependencies(GROUP CONFIGURE g_1 g_3)
...
sysbuild_add_dependencies(GROUP CONFIGURE g_1 g_n)
Tip
You are allowed to use sysbuild_add_dependencies()
with non-existent groups, just as well as with non-existent images.
Sysbuild reserves a few special, immutable groups, which are automatically defined for your convenience. These are:
all
. This group names all of the images to build.board_<BOARD>
. There is a group named after everyBOARD
in Zephyr, and it contains all images currently targeting that board.
As an example of how the special groups could come in handy, if you ever need to define the complement of a group, then you can define it with the help of the all
group, like this:
ExternalZephyrProject_Group(
not_my_group
INCLUDE_FROM all
EXCLUDE_FROM my_group
)
Another example is for when you have a sysbuild project which incorporates multiple boards. If you ever want the images targeting one of these boards, say reel_board
, to always be flashed first, then here is how to do it:
sysbuild_add_dependencies(GROUP FLASH all board_reel_board)
This kind of thing is not recommended in-tree, as it is likely to cause complications across Zephyr samples, but it should be fine to use in your own private project if you need it.
Since sysbuild groups are defined progressively, their contents will be unknown until all inclusion-exclusion directives have been evaluated, across successive ExternalZephyrProject_Group()
calls. This will not happen until all sysbuild modules and all sysbuild.cmake
files have already been processed. Therefore, the only place where you will be able to list group contents is within <sysbuild_module_hooks>
.
For that, sysbuild provides the following function:
sysbuild_images_list(my_group_images GROUP my_group)
It will populate the my_group_images
variable with the complete list of images belonging to my_group
. If the group is undefined, then the returned list will be empty.