Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sysbuild: Image ordering #57884

Merged
merged 5 commits into from Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 70 additions & 0 deletions cmake/modules/extensions.cmake
Expand Up @@ -3005,6 +3005,76 @@ function(target_byproducts)
)
endfunction()

# Usage:
# topological_sort(TARGETS <target> [<target> ...]
# PROPERTY_NAME <property>
# RESULT <out-variable>)
#
# This function performs topological sorting of CMake targets using a specific
# <property>, which dictates target dependencies. A fatal error occurs if the
# provided dependencies cannot be met, e.g., if they contain cycles.
#
# TARGETS: List of target names.
# PROPERTY_NAME: Name of the target property to be used when sorting. For every
# target listed in TARGETS, this property must contain a list
# (possibly empty) of other targets, which this target depends on
# for a particular purpose. The property must not contain any
# target which is not also found in TARGETS.
# RESULT: Output variable, where the topologically sorted list of target
# names will be returned.
#
function(topological_sort)
cmake_parse_arguments(TS "" "RESULT;PROPERTY_NAME" "TARGETS" ${ARGN})

set(dep_targets)
set(start_targets)
set(sorted_targets)

foreach(target ${TS_TARGETS})
get_target_property(${target}_dependencies ${target} ${TS_PROPERTY_NAME})

if(${target}_dependencies)
list(APPEND dep_targets ${target})
else()
list(APPEND start_targets ${target})
endif()
endforeach()

while(TRUE)
list(POP_FRONT start_targets node)
list(APPEND sorted_targets ${node})
set(to_remove)
foreach(target ${dep_targets})
if("${node}" IN_LIST ${target}_dependencies)
list(REMOVE_ITEM ${target}_dependencies ${node})
if(NOT ${target}_dependencies)
list(APPEND start_targets ${target})
list(APPEND to_remove ${target})
endif()
endif()
endforeach()

foreach(target ${to_remove})
list(REMOVE_ITEM dep_targets ${target})
endforeach()
if(NOT start_targets)
break()
endif()
endwhile()

if(dep_targets)
foreach(target ${dep_targets})
get_target_property(deps ${target} ${TS_PROPERTY_NAME})
list(JOIN deps " " deps)
list(APPEND dep_string "${target} depends on: ${deps}")
endforeach()
list(JOIN dep_string "\n" dep_string)
message(FATAL_ERROR "Unmet or cyclic dependencies:\n${dep_string}")
endif()

set(${TS_RESULT} "${sorted_targets}" PARENT_SCOPE)
endfunction()

########################################################
# 4. Devicetree extensions
########################################################
Expand Down
44 changes: 44 additions & 0 deletions doc/build/sysbuild/index.rst
Expand Up @@ -639,6 +639,50 @@ configuration files for ``my_sample`` will be ignored.
This give you full control on how images are configured when integrating those
with ``application``.

.. _sysbuild_zephyr_application_dependencies:

Adding dependencies among Zephyr applications
=============================================

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.
You can use the ``sysbuild_add_dependencies()`` function to make adjustments to
this order, according to your needs. Its usage is similar to the standard
``add_dependencies()`` function in CMake.

Here is an example of adding configuration dependencies for ``my_sample``:

.. code-block:: cmake

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:

.. code-block:: cmake

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.

.. note::

Adding flashing dependencies is not allowed for build-only applications.
If ``my_sample`` had been created with ``BUILD_ONLY TRUE``, then the above
call to ``sysbuild_add_dependencies()`` would have produced an error.

Adding non-Zephyr applications to sysbuild
******************************************

Expand Down
10 changes: 7 additions & 3 deletions samples/drivers/ipm/ipm_mcux/sysbuild.cmake
Expand Up @@ -9,9 +9,13 @@ ExternalZephyrProject_Add(
BOARD ${SB_CONFIG_IPM_REMOTE_BOARD}
)

# Add a dependency so that the remote sample will be built and flashed first
# Add dependencies so that the remote sample will be built first
# This is required because some primary cores need information from the
# remote core's build, such as the output image's LMA
add_dependencies(ipm_mcux ipm_mcux_remote)
# Place remote image first in the image list
set(IMAGES "ipm_mcux_remote" ${IMAGES})
sysbuild_add_dependencies(CONFIGURE ipm_mcux ipm_mcux_remote)

if(SB_CONFIG_BOOTLOADER_MCUBOOT)
# Make sure MCUboot is flashed first
sysbuild_add_dependencies(FLASH ipm_mcux_remote mcuboot)
endif()
10 changes: 7 additions & 3 deletions samples/subsys/ipc/openamp/sysbuild.cmake
Expand Up @@ -9,9 +9,13 @@ ExternalZephyrProject_Add(
BOARD ${SB_CONFIG_OPENAMP_REMOTE_BOARD}
)

# Add a dependency so that the remote sample will be built and flashed first
# Add dependencies so that the remote sample will be built first
# This is required because some primary cores need information from the
# remote core's build, such as the output image's LMA
add_dependencies(openamp openamp_remote)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so, if it's meant to control the order in which those images are built (not configured).

# Place remote image first in the image list
set(IMAGES "openamp_remote" ${IMAGES})
sysbuild_add_dependencies(CONFIGURE openamp openamp_remote)

if(SB_CONFIG_BOOTLOADER_MCUBOOT)
# Make sure MCUboot is flashed first
sysbuild_add_dependencies(FLASH openamp_remote mcuboot)
endif()
61 changes: 5 additions & 56 deletions share/sysbuild/CMakeLists.txt
Expand Up @@ -24,68 +24,17 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE} COMPONENTS ${zephyr_modules

project(sysbuild LANGUAGES)

# Global list of images enabled in this multi image build system.
set(IMAGES)

get_filename_component(APP_DIR ${APP_DIR} ABSOLUTE)
get_filename_component(app_name ${APP_DIR} NAME)

# Include zephyr modules generated sysbuild CMake file.
foreach(SYSBUILD_CURRENT_MODULE_NAME ${SYSBUILD_MODULE_NAMES})
# Note the second, binary_dir parameter requires the added
# subdirectory to have its own, local cmake target(s). If not then
# this binary_dir is created but stays empty. Object files land in
# the main binary dir instead.
# https://cmake.org/pipermail/cmake/2019-June/069547.html
zephyr_string(SANITIZE TOUPPER MODULE_NAME_UPPER ${SYSBUILD_CURRENT_MODULE_NAME})
if(NOT ${SYSBUILD_${MODULE_NAME_UPPER}_CMAKE_DIR} STREQUAL "")
set(SYSBUILD_CURRENT_MODULE_DIR ${SYSBUILD_${MODULE_NAME_UPPER}_MODULE_DIR})
set(SYSBUILD_CURRENT_CMAKE_DIR ${SYSBUILD_${MODULE_NAME_UPPER}_CMAKE_DIR})
add_subdirectory(${SYSBUILD_CURRENT_CMAKE_DIR} ${CMAKE_BINARY_DIR}/modules/${SYSBUILD_CURRENT_MODULE_NAME})
endif()
endforeach()
# Done processing modules, clear SYSBUILD_CURRENT_MODULE_DIR and SYSBUILD_CURRENT_CMAKE_DIR.
set(SYSBUILD_CURRENT_MODULE_DIR)
set(SYSBUILD_CURRENT_CMAKE_DIR)

# This adds the primary application to the build.
ExternalZephyrProject_Add(
APPLICATION ${app_name}
SOURCE_DIR ${APP_DIR}
APP_TYPE MAIN
)
list(APPEND IMAGES "${app_name}")
set(DEFAULT_IMAGE "${app_name}")

add_subdirectory(bootloader)

# This allows for board and app specific images to be included.
include(${BOARD_DIR}/sysbuild.cmake OPTIONAL)

# This allows image specific sysbuild.cmake to be processed.
list(LENGTH IMAGES images_length)
while(NOT "${images_length}" EQUAL "${processed_length}")
foreach(image ${IMAGES})
if(NOT image IN_LIST images_sysbuild_processed)
ExternalProject_Get_property(${image} SOURCE_DIR)
include(${SOURCE_DIR}/sysbuild.cmake OPTIONAL)
list(APPEND images_sysbuild_processed ${image})
endif()
endforeach()

list(LENGTH IMAGES images_length)
list(LENGTH images_sysbuild_processed processed_length_new)

# Check for any duplicate entries in image names to prevent an infinite loop
if("${processed_length_new}" EQUAL "${processed_length}")
# Image length was different than processed length, but no new images are processed.
message(FATAL_ERROR "A duplicate image name was provided, image names must be unique.")
endif()
set(processed_length ${processed_length_new})
endwhile()
# This is where all Zephyr applications are added to the multi-image build.
sysbuild_add_subdirectory(images)

get_property(IMAGES GLOBAL PROPERTY sysbuild_images)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMAGES was named upper case because it could be used further down the tree of CMake files.

With the new approach of sysbuild_add_subdirectory() combined with the global property then we could consider to use a new (lower case) name for this variable. If so, we should probably take a closer look at domains.cmake

No need for changing in this PR, but worth considering when extending sysbuild, or if refactoring domains.cmake in future.

sysbuild_module_call(PRE_CMAKE MODULES ${SYSBUILD_MODULE_NAMES} IMAGES ${IMAGES})
foreach(image ${IMAGES})
sysbuild_images_order(IMAGES_CONFIGURATION_ORDER CONFIGURE IMAGES ${IMAGES})
foreach(image ${IMAGES_CONFIGURATION_ORDER})
ExternalZephyrProject_Cmake(APPLICATION ${image})
endforeach()
sysbuild_module_call(POST_CMAKE MODULES ${SYSBUILD_MODULE_NAMES} IMAGES ${IMAGES})
Expand Down
2 changes: 1 addition & 1 deletion share/sysbuild/Kconfig
Expand Up @@ -39,4 +39,4 @@ config WARN_DEPRECATED
Print a warning when the Kconfig tree is parsed if any deprecated
features are enabled.

rsource "bootloader/Kconfig"
rsource "images/Kconfig"
4 changes: 3 additions & 1 deletion share/sysbuild/cmake/domains.cmake
Expand Up @@ -2,10 +2,12 @@
#
# SPDX-License-Identifier: Apache-2.0

sysbuild_images_order(IMAGES_FLASHING_ORDER FLASH IMAGES ${IMAGES})

set(domains_yaml "default: ${DEFAULT_IMAGE}")
set(domains_yaml "${domains_yaml}\nbuild_dir: ${CMAKE_BINARY_DIR}")
set(domains_yaml "${domains_yaml}\ndomains:")
foreach(image ${IMAGES})
foreach(image ${IMAGES_FLASHING_ORDER})
get_target_property(image_is_build_only ${image} BUILD_ONLY)
if(image_is_build_only)
continue()
Expand Down