From 9d6cc39d6fcdaa79fbb19ef6dc16f40422bdbebd Mon Sep 17 00:00:00 2001 From: Torsten Rasmussen Date: Wed, 22 Jun 2022 11:05:49 +0200 Subject: [PATCH] cmake: initial sysbuild / multi image support This is the initial commit with system build, sysbuild. Using CMake as infrastructure together with the Zephyr sysbuild allows us to support a convenient way of building a sample and allow for extra images to be built as part of a larger system. It uses Kconfig for configuration of image builds. This allows for future extension with additional build images. Signed-off-by: Torsten Rasmussen --- CODEOWNERS | 1 + share/sysbuild/CMakeLists.txt | 42 ++++ share/sysbuild/Kconfig | 34 ++++ .../cmake/modules/sysbuild_extensions.cmake | 187 ++++++++++++++++++ .../cmake/modules/sysbuild_kconfig.cmake | 60 ++++++ 5 files changed, 324 insertions(+) create mode 100644 share/sysbuild/CMakeLists.txt create mode 100644 share/sysbuild/Kconfig create mode 100644 share/sysbuild/cmake/modules/sysbuild_extensions.cmake create mode 100644 share/sysbuild/cmake/modules/sysbuild_kconfig.cmake diff --git a/CODEOWNERS b/CODEOWNERS index e8722d1e901..667a4a4573a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -725,6 +725,7 @@ scripts/build/gen_image_info.py @tejlmand /scripts/build/uf2conv.py @petejohanson /scripts/build/user_wordsize.py @cfriedt /scripts/valgrind.supp @aescolar @daor-oti +/share/sysbuild/ @tejlmand /share/zephyr-package/ @tejlmand /share/zephyrunittest-package/ @tejlmand /subsys/bluetooth/ @alwa-nordic @jhedberg @Vudentz diff --git a/share/sysbuild/CMakeLists.txt b/share/sysbuild/CMakeLists.txt new file mode 100644 index 00000000000..d30d629e1fe --- /dev/null +++ b/share/sysbuild/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20) + +if(NOT DEFINED APP_DIR) + message(FATAL_ERROR "No main application specified") +endif() + +# This will update the APP_DIR cache variable to PATH type and apply a comment. +# If APP_DIR is a relative path, then CMake will adjust to absolute path based +# on current working dir. +set(APP_DIR ${APP_DIR} CACHE PATH "Main Application Source Directory") + +# Add sysbuild/cmake/modules to CMAKE_MODULE_PATH which allows us to integrate +# sysbuild CMake modules with general Zephyr CMake modules. +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) +# List of Zephyr and sysbuild CMake modules we need for sysbuild. +# Note: sysbuild_kconfig will internally load kconfig CMake module. +set(zephyr_modules extensions sysbuild_extensions python west root zephyr_module boards shields sysbuild_kconfig) + +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) + +# This adds the primary application to the build. +ExternalZephyrProject_Add( + APPLICATION ${app_name} + SOURCE_DIR ${APP_DIR} + MAIN_APP +) + +# This allows for board and app specific images to be included. +include(${BOARD_DIR}/sysbuild.cmake OPTIONAL) +include(${APP_DIR}/sysbuild.cmake OPTIONAL) diff --git a/share/sysbuild/Kconfig b/share/sysbuild/Kconfig new file mode 100644 index 00000000000..ce49acff286 --- /dev/null +++ b/share/sysbuild/Kconfig @@ -0,0 +1,34 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +comment "Sysbuild image configuration" + +osource "$(BOARD_DIR)/Kconfig.sysbuild" + +config EXPERIMENTAL + bool + help + Symbol that must be selected by a feature if it is considered to be + at an experimental implementation stage. + +config WARN_EXPERIMENTAL + bool + prompt "Warn on experimental usage" + help + Print a warning when the Kconfig tree is parsed if any experimental + features are enabled. + +config DEPRECATED + bool + help + Symbol that must be selected by a feature or module if it is + considered to be deprecated. + +config WARN_DEPRECATED + bool + default y + prompt "Warn on deprecated usage" + help + Print a warning when the Kconfig tree is parsed if any deprecated + features are enabled. diff --git a/share/sysbuild/cmake/modules/sysbuild_extensions.cmake b/share/sysbuild/cmake/modules/sysbuild_extensions.cmake new file mode 100644 index 00000000000..e9b841bf8c2 --- /dev/null +++ b/share/sysbuild/cmake/modules/sysbuild_extensions.cmake @@ -0,0 +1,187 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +# Usage: +# ExternalZephyrProject_Add(APPLICATION +# SOURCE_DIR +# [BOARD ] +# [MAIN_APP] +# ) +# +# This function includes a Zephyr based build system into the multiimage +# build system +# +# APPLICATION: : Name of the application, name will also be used for build +# folder of the application +# SOURCE_DIR : Source directory of the application +# BOARD : Use for application build instead user defined BOARD. +# MAIN_APP: Flag indicating this application is the main application +# and where user defined settings should be passed on as-is +# except for multi image build flags. +# For example, -DCONF_FILES= will be passed on to the +# MAIN_APP unmodified. +# +function(ExternalZephyrProject_Add) + cmake_parse_arguments(ZBUILD "MAIN_APP" "APPLICATION;BOARD;SOURCE_DIR" "" ${ARGN}) + + if(ZBUILD_UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "ExternalZephyrProject_Add(${ARGV0} ...) given unknown arguments:" + " ${ZBUILD_UNPARSED_ARGUMENTS}" + ) + endif() + + set(sysbuild_vars + "APP_DIR" + "SB_CONF_FILE" + ) + + # General variables that should be propagated to all Zephyr builds, for example: + # - ZEPHYR_MODULES / ZEPHYR_EXTRA_MODULES + # - ZEPHYR_TOOLCHAIN_VARIANT + # - *_TOOLCHAIN_PATH + # - *_ROOT + # etc. + # Note: setting vars on a single image can be done by using + # `_CONF_FILE`, like `mcuboot_CONF_FILE` + set( + shared_image_variables_list + CMAKE_BUILD_TYPE + CMAKE_VERBOSE_MAKEFILE + BOARD + ZEPHYR_MODULES + ZEPHYR_EXTRA_MODULES + ZEPHYR_TOOLCHAIN_VARIANT + EXTRA_KCONFIG_TARGETS + ) + + set(shared_image_variables_regex + "^[^_]*_TOOLCHAIN_PATH|^[^_]*_ROOT" + ) + + set(app_cache_file ${CMAKE_BINARY_DIR}/CMake${ZBUILD_APPLICATION}PreloadCache.txt) + + if(EXISTS ${app_cache_file}) + file(STRINGS ${app_cache_file} app_cache_strings) + set(app_cache_strings_current ${app_cache_strings}) + endif() + + get_cmake_property(variables_cached CACHE_VARIABLES) + foreach(var_name ${variables_cached}) + # Any var of the form `_` should be propagated. + # For example mcuboot_= ==> -D= for mcuboot build. + if("${var_name}" MATCHES "^${ZBUILD_APPLICATION}_.*") + list(APPEND application_vars ${var_name}) + continue() + endif() + + # This means there is a match to another image than current one, ignore. + if("${var_name}" MATCHES "^.*_CONFIG_.*") + continue() + endif() + + # sysbuild reserved namespace. + if(var_name IN_LIST sysbuild_vars OR "${var_name}" MATCHES "^SB_CONFIG_.*") + continue() + endif() + + if("${var_name}" MATCHES "^CONFIG_.*") + if(ZBUILD_MAIN_APP) + list(APPEND application_vars ${var_name}) + endif() + continue() + endif() + + if(var_name IN_LIST shared_image_variables_list) + list(APPEND application_vars ${var_name}) + continue() + endif() + + if("${var_name}" MATCHES "${shared_image_variables_regex}") + list(APPEND application_vars ${var_name}) + endif() + endforeach() + + foreach(app_var_name ${application_vars}) + string(REGEX REPLACE "^${ZBUILD_APPLICATION}_" "" var_name "${app_var_name}") + get_property(var_type CACHE ${app_var_name} PROPERTY TYPE) + set(new_cache_entry "${var_name}:${var_type}=${${app_var_name}}") + if(NOT new_cache_entry IN_LIST app_cache_strings) + # This entry does not exists, let's see if it has been updated. + foreach(entry ${app_cache_strings}) + if("${entry}" MATCHES "^${var_name}:.*") + list(REMOVE_ITEM app_cache_strings "${entry}") + break() + endif() + endforeach() + list(APPEND app_cache_strings "${var_name}:${var_type}=${${app_var_name}}") + list(APPEND app_cache_entries "-D${var_name}:${var_type}=${${app_var_name}}") + endif() + endforeach() + + if(NOT "${app_cache_strings_current}" STREQUAL "${app_cache_strings}") + string(REPLACE ";" "\n" app_cache_strings "${app_cache_strings}") + file(WRITE ${app_cache_file} ${app_cache_strings}) + endif() + + if(DEFINED ZBUILD_BOARD) + list(APPEND app_cache_entries "-DBOARD=${ZBUILD_BOARD}") + elseif(NOT ZBUILD_MAIN_APP) + list(APPEND app_cache_entries "-DBOARD=${BOARD}") + endif() + + set(image_banner "* Running CMake for ${ZBUILD_APPLICATION} *") + string(LENGTH "${image_banner}" image_banner_width) + string(REPEAT "*" ${image_banner_width} image_banner_header) + message(STATUS "\n ${image_banner_header}\n" + " ${image_banner}\n" + " ${image_banner_header}\n" + ) + + execute_process( + COMMAND ${CMAKE_COMMAND} + -G${CMAKE_GENERATOR} + ${app_cache_entries} + -B${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION} + -S${ZBUILD_SOURCE_DIR} + RESULT_VARIABLE return_val + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) + + if(return_val) + message(FATAL_ERROR + "CMake configure failed for Zephyr project: ${ZBUILD_APPLICATION}\n" + "Location: ${ZBUILD_SOURCE_DIR}" + ) + endif() + + foreach(kconfig_target + menuconfig + hardenconfig + guiconfig + ${EXTRA_KCONFIG_TARGETS} + ) + + if(NOT ZBUILD_MAIN_APP) + set(image_prefix "${ZBUILD_APPLICATION}_") + endif() + + add_custom_target(${image_prefix}${kconfig_target} + ${CMAKE_MAKE_PROGRAM} ${kconfig_target} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION} + USES_TERMINAL + ) + endforeach() + include(ExternalProject) + ExternalProject_Add( + ${ZBUILD_APPLICATION} + SOURCE_DIR ${ZBUILD_SOURCE_DIR} + BINARY_DIR ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION} + CONFIGURE_COMMAND "" + BUILD_COMMAND ${CMAKE_COMMAND} --build . + INSTALL_COMMAND "" + BUILD_ALWAYS True + USES_TERMINAL_BUILD True + ) +endfunction() diff --git a/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake b/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake new file mode 100644 index 00000000000..add0bb8ac04 --- /dev/null +++ b/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake @@ -0,0 +1,60 @@ +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_sysbuild_menuconfig + ${ZEPHYR_BASE}/scripts/kconfig/menuconfig.py +) + +set(EXTRA_KCONFIG_TARGET_COMMAND_FOR_sysbuild_guiconfig + ${ZEPHYR_BASE}/scripts/kconfig/guiconfig.py +) + +set(KCONFIG_TARGETS sysbuild_menuconfig sysbuild_guiconfig) +list(TRANSFORM EXTRA_KCONFIG_TARGETS PREPEND "sysbuild_") + +if(DEFINED SB_CONF_FILE) + # SB_CONF_FILE already set so nothing to do. +elseif(DEFINED ENV{SB_CONF_FILE}) + set(SB_CONF_FILE $ENV{SB_CONF_FILE}) +elseif(EXISTS ${APP_DIR}/sysbuild.conf) + set(SB_CONF_FILE ${APP_DIR}/sysbuild.conf) +else() + # Because SYSBuild is opt-in feature, then it is permitted to not have a + # SYSBuild dedicated configuration file. +endif() + +if(DEFINED SB_CONF_FILE AND NOT IS_ABSOLUTE SB_CONF_FILE) + cmake_path(ABSOLUTE_PATH SB_CONF_FILE BASE_DIRECTORY ${APP_DIR} OUTPUT_VARIABLE SB_CONF_FILE) +endif() + +if(DEFINED SB_CONF_FILE AND NOT DEFINED CACHE{SB_CONF_FILE}) + # We only want to set this in cache it has been defined and is not already there. + set(SB_CONF_FILE ${SB_CONF_FILE} CACHE STRING "If desired, you can build the application with \ + SYSbuild configuration settings specified in an alternate .conf file using this parameter. \ + These settings will override the settings in the application’s SYSBuild config file or its \ + default .conf file. Multiple files may be listed, e.g. SB_CONF_FILE=\"sys1.conf sys2.conf\"") +endif() + +if(NOT DEFINED SB_CONF_FILE) + # If there is no SB_CONF_FILE, then use empty.conf to make kconfiglib happy. + # Not adding it to CMake cache ensures that a later created sysbuild.conf + # will be automatically detected. + set(SB_CONF_FILE ${CMAKE_CURRENT_BINARY_DIR}/empty.conf) +endif() + +# Empty files to make kconfig.py happy. +file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/empty.conf) +set(APPLICATION_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(KCONFIG_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) +set(AUTOCONF_H ${CMAKE_CURRENT_BINARY_DIR}/autoconf.h) +set(CONF_FILE ${SB_CONF_FILE}) +set(BOARD_DEFCONFIG "${CMAKE_CURRENT_BINARY_DIR}/empty.conf") +list(APPEND ZEPHYR_KCONFIG_MODULES_DIR BOARD=${BOARD}) +set(KCONFIG_NAMESPACE SB_CONFIG) + +if(EXISTS ${APP_DIR}/Kconfig.sysbuild) + set(KCONFIG_ROOT ${APP_DIR}/Kconfig.sysbuild) +endif() +include(${ZEPHYR_BASE}/cmake/modules/kconfig.cmake) +set(CONF_FILE)