From df9027a64a7fdbde0fcf6f0d3109733115b68723 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Mon, 20 Feb 2023 10:00:38 +0000 Subject: [PATCH] sysbuild: support Zephyr modules This commit extends the Zephyr module yaml scheme with additional entries for sysbuild in the build section. This allows for Zephyr modules to extend the sysbuild infrastructure by providing additional CMake and Kconfig files to be included in sysbuild. The new settings are: build: sysbuild-cmake: sysbuild-kconfig: / sysbuild-ext: | sysbuild-kconfig-ext: | those settings follow the same pattern as the equivalent Zephyr build settings but are processed by sysbuild. Signed-off-by: Jamie McCrae Signed-off-by: Torsten Rasmussen --- cmake/modules/zephyr_module.cmake | 101 ++++++++++++------ modules/Kconfig.sysbuild | 28 +++++ scripts/zephyr_module.py | 95 +++++++++++++++- share/sysbuild/CMakeLists.txt | 18 ++++ share/sysbuild/Kconfig | 6 ++ .../cmake/modules/sysbuild_kconfig.cmake | 1 - 6 files changed, 215 insertions(+), 34 deletions(-) create mode 100644 modules/Kconfig.sysbuild diff --git a/cmake/modules/zephyr_module.cmake b/cmake/modules/zephyr_module.cmake index 83d3712c76e..3dcae3ea61b 100644 --- a/cmake/modules/zephyr_module.cmake +++ b/cmake/modules/zephyr_module.cmake @@ -43,6 +43,9 @@ endif() file(MAKE_DIRECTORY ${KCONFIG_BINARY_DIR}) set(kconfig_modules_file ${KCONFIG_BINARY_DIR}/Kconfig.modules) +set(kconfig_sysbuild_file ${KCONFIG_BINARY_DIR}/Kconfig.sysbuild.modules) +set(cmake_modules_file ${CMAKE_BINARY_DIR}/zephyr_modules.txt) +set(cmake_sysbuild_file ${CMAKE_BINARY_DIR}/sysbuild_modules.txt) set(zephyr_settings_file ${CMAKE_BINARY_DIR}/zephyr_settings.txt) if(WEST) @@ -59,7 +62,9 @@ if(WEST OR ZEPHYR_MODULES) ${ZEPHYR_MODULES_ARG} ${ZEPHYR_EXTRA_MODULES_ARG} --kconfig-out ${kconfig_modules_file} - --cmake-out ${CMAKE_BINARY_DIR}/zephyr_modules.txt + --cmake-out ${cmake_modules_file} + --sysbuild-kconfig-out ${kconfig_sysbuild_file} + --sysbuild-cmake-out ${cmake_sysbuild_file} --settings-out ${zephyr_settings_file} WORKING_DIRECTORY ${ZEPHYR_BASE} ERROR_VARIABLE @@ -87,20 +92,32 @@ if(WEST OR ZEPHYR_MODULES) # Append ZEPHYR_BASE as a default ext root at lowest priority list(APPEND MODULE_EXT_ROOT ${ZEPHYR_BASE}) - if(EXISTS ${CMAKE_BINARY_DIR}/zephyr_modules.txt) - file(STRINGS ${CMAKE_BINARY_DIR}/zephyr_modules.txt zephyr_modules_txt - ENCODING UTF-8) - - set(ZEPHYR_MODULE_NAMES) - foreach(module ${zephyr_modules_txt}) - # Match "":"" for each line of file, each corresponding to - # one module. The use of quotes is required due to CMake not supporting - # lazy regexes (it supports greedy only). - string(REGEX REPLACE "\"(.*)\":\".*\":\".*\"" "\\1" module_name ${module}) - list(APPEND ZEPHYR_MODULE_NAMES ${module_name}) - endforeach() + if(EXISTS ${cmake_modules_file}) + file(STRINGS ${cmake_modules_file} zephyr_modules_txt ENCODING UTF-8) endif() + set(ZEPHYR_MODULE_NAMES) + foreach(module ${zephyr_modules_txt}) + # Match "":"" for each line of file, each corresponding to + # one module. The use of quotes is required due to CMake not supporting + # lazy regexes (it supports greedy only). + string(REGEX REPLACE "\"(.*)\":\".*\":\".*\"" "\\1" module_name ${module}) + list(APPEND ZEPHYR_MODULE_NAMES ${module_name}) + endforeach() + + if(EXISTS ${cmake_sysbuild_file}) + file(STRINGS ${cmake_sysbuild_file} sysbuild_modules_txt ENCODING UTF-8) + endif() + + set(SYSBUILD_MODULE_NAMES) + foreach(module ${sysbuild_modules_txt}) + # Match "":"" for each line of file, each corresponding to + # one module. The use of quotes is required due to CMake not supporting + # lazy regexes (it supports greedy only). + string(REGEX REPLACE "\"(.*)\":\".*\":\".*\"" "\\1" module_name ${module}) + list(APPEND SYSBUILD_MODULE_NAMES ${module_name}) + endforeach() + # MODULE_EXT_ROOT is process order which means Zephyr module roots processed # later wins. therefore we reverse the list before processing. list(REVERSE MODULE_EXT_ROOT) @@ -112,31 +129,53 @@ if(WEST OR ZEPHYR_MODULES) include(${root}/modules/modules.cmake) endforeach() - if(DEFINED zephyr_modules_txt) - foreach(module ${zephyr_modules_txt}) - # Match "":"" for each line of file, each corresponding to - # one Zephyr module. The use of quotes is required due to CMake not - # supporting lazy regexes (it supports greedy only). - string(CONFIGURE ${module} module) - string(REGEX REPLACE "\"(.*)\":\".*\":\".*\"" "\\1" module_name ${module}) - string(REGEX REPLACE "\".*\":\"(.*)\":\".*\"" "\\1" module_path ${module}) - string(REGEX REPLACE "\".*\":\".*\":\"(.*)\"" "\\1" cmake_path ${module}) + foreach(module ${zephyr_modules_txt}) + # Match "":"" for each line of file, each corresponding to + # one Zephyr module. The use of quotes is required due to CMake not + # supporting lazy regexes (it supports greedy only). + string(CONFIGURE ${module} module) + string(REGEX REPLACE "\"(.*)\":\".*\":\".*\"" "\\1" module_name ${module}) + string(REGEX REPLACE "\".*\":\"(.*)\":\".*\"" "\\1" module_path ${module}) + string(REGEX REPLACE "\".*\":\".*\":\"(.*)\"" "\\1" cmake_path ${module}) - zephyr_string(SANITIZE TOUPPER MODULE_NAME_UPPER ${module_name}) - if(NOT ${MODULE_NAME_UPPER} STREQUAL CURRENT) - set(ZEPHYR_${MODULE_NAME_UPPER}_MODULE_DIR ${module_path}) - set(ZEPHYR_${MODULE_NAME_UPPER}_CMAKE_DIR ${cmake_path}) - else() - message(FATAL_ERROR "Found Zephyr module named: ${module_name}\n\ + zephyr_string(SANITIZE TOUPPER MODULE_NAME_UPPER ${module_name}) + if(NOT ${MODULE_NAME_UPPER} STREQUAL CURRENT) + set(ZEPHYR_${MODULE_NAME_UPPER}_MODULE_DIR ${module_path}) + set(ZEPHYR_${MODULE_NAME_UPPER}_CMAKE_DIR ${cmake_path}) + else() + message(FATAL_ERROR "Found Zephyr module named: ${module_name}\n\ ${MODULE_NAME_UPPER} is a restricted name for Zephyr modules as it is used for \ \${ZEPHYR_${MODULE_NAME_UPPER}_MODULE_DIR} CMake variable.") - endif() - endforeach() - endif() + endif() + endforeach() + + foreach(module ${sysbuild_modules_txt}) + # Match "":"" for each line of file, each corresponding to + # one Zephyr module. The use of quotes is required due to CMake not + # supporting lazy regexes (it supports greedy only). + string(CONFIGURE ${module} module) + string(REGEX REPLACE "\"(.*)\":\".*\":\".*\"" "\\1" module_name ${module}) + string(REGEX REPLACE "\".*\":\"(.*)\":\".*\"" "\\1" module_path ${module}) + string(REGEX REPLACE "\".*\":\".*\":\"(.*)\"" "\\1" cmake_path ${module}) + + zephyr_string(SANITIZE TOUPPER MODULE_NAME_UPPER ${module_name}) + if(NOT ${MODULE_NAME_UPPER} STREQUAL CURRENT) + set(SYSBUILD_${MODULE_NAME_UPPER}_MODULE_DIR ${module_path}) + set(SYSBUILD_${MODULE_NAME_UPPER}_CMAKE_DIR ${cmake_path}) + else() + message(FATAL_ERROR "Found Zephyr module named: ${module_name}\n\ +${MODULE_NAME_UPPER} is a restricted name for Zephyr modules as it is used for \ +\${SYSBUILD_${MODULE_NAME_UPPER}_MODULE_DIR} CMake variable.") + endif() + endforeach() else() file(WRITE ${kconfig_modules_file} "# No west and no Zephyr modules\n" ) + file(WRITE ${kconfig_sysbuild_file} + "# No west and no Zephyr modules\n" + ) + endif() diff --git a/modules/Kconfig.sysbuild b/modules/Kconfig.sysbuild new file mode 100644 index 00000000000..517e0e335f7 --- /dev/null +++ b/modules/Kconfig.sysbuild @@ -0,0 +1,28 @@ +# Copyright (c) 2019 Intel Corporation +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 + +comment "Available modules." + +source "$(KCONFIG_BINARY_DIR)/Kconfig.sysbuild.modules" + +comment "Unavailable modules, please install those via the project manifest." + +# List of comments to display when Zephyr modules are not available, please +# use the following syntax: +# --------------------------------------------------- +# comment " module not available." +# depends on !SYSBUILD__MODULE +# +# Remember to add the following code inside the `/Kconfig file: +# --------------------------------------------------- +# config SYSBUILD__MODULE +# bool + +# This ensures that symbols are available in Kconfig for dependency checking +# and referencing, while keeping the settings themselves unavailable when the +# modules are not present in the workspace +if 0 +osource "modules/*/Kconfig.sysbuild" +endif diff --git a/scripts/zephyr_module.py b/scripts/zephyr_module.py index 65fb7c5c74d..e0cf18d5645 100755 --- a/scripts/zephyr_module.py +++ b/scripts/zephyr_module.py @@ -57,6 +57,20 @@ mapping: required: false type: bool default: false + sysbuild-cmake: + required: false + type: str + sysbuild-kconfig: + required: false + type: str + sysbuild-cmake-ext: + required: false + type: bool + default: false + sysbuild-kconfig-ext: + required: false + type: bool + default: false depends: required: false type: seq @@ -216,6 +230,40 @@ def process_cmake(module, meta): module_path.as_posix())) +def process_sysbuildcmake(module, meta): + section = meta.get('build', dict()) + module_path = PurePath(module) + module_yml = module_path.joinpath('zephyr/module.yml') + + cmake_extern = section.get('sysbuild-cmake-ext', False) + if cmake_extern: + return('\"{}\":\"{}\":\"{}\"\n' + .format(meta['name'], + module_path.as_posix(), + "${SYSBUILD_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}")) + + cmake_setting = section.get('sysbuild-cmake', None) + if not validate_setting(cmake_setting, module, 'CMakeLists.txt'): + sys.exit('ERROR: "cmake" key in {} has folder value "{}" which ' + 'does not contain a CMakeLists.txt file.' + .format(module_yml.as_posix(), cmake_setting)) + + if cmake_setting is None: + return "" + + cmake_path = os.path.join(module, cmake_setting or 'zephyr') + cmake_file = os.path.join(cmake_path, 'CMakeLists.txt') + if os.path.isfile(cmake_file): + return('\"{}\":\"{}\":\"{}\"\n' + .format(meta['name'], + module_path.as_posix(), + Path(cmake_path).resolve().as_posix())) + else: + return('\"{}\":\"{}\":\"\"\n' + .format(meta['name'], + module_path.as_posix())) + + def process_settings(module, meta): section = meta.get('build', dict()) build_settings = section.get('settings', None) @@ -260,13 +308,14 @@ def process_blobs(module, meta): return blobs -def kconfig_snippet(meta, path, kconfig_file=None, blobs=False): +def kconfig_snippet(meta, path, kconfig_file=None, blobs=False, sysbuild=False): name = meta['name'] name_sanitized = meta['name-sanitized'] snippet = [f'menu "{name} ({path.as_posix()})"', f'osource "{kconfig_file.resolve().as_posix()}"' if kconfig_file - else f'osource "$(ZEPHYR_{name_sanitized.upper()}_KCONFIG)"', + else f'osource "$(SYSBUILD_{name_sanitized.upper()}_KCONFIG)"' if sysbuild is True + else f'osource "$(ZEPHYR_{name_sanitized.upper()}_KCONFIG)"', f'config ZEPHYR_{name_sanitized.upper()}_MODULE', ' bool', ' default y', @@ -301,6 +350,30 @@ def process_kconfig(module, meta): return "" +def process_sysbuildkconfig(module, meta): + section = meta.get('build', dict()) + module_path = PurePath(module) + module_yml = module_path.joinpath('zephyr/module.yml') + kconfig_extern = section.get('sysbuild-kconfig-ext', False) + if kconfig_extern: + return kconfig_snippet(meta, module_path, sysbuild=True) + + kconfig_setting = section.get('sysbuild-kconfig', None) + if not validate_setting(kconfig_setting, module): + sys.exit('ERROR: "kconfig" key in {} has value "{}" which does ' + 'not point to a valid Kconfig file.' + .format(module_yml, kconfig_setting)) + + if kconfig_setting is None: + return "" + + kconfig_file = os.path.join(module, kconfig_setting) + if os.path.isfile(kconfig_file): + return kconfig_snippet(meta, module_path, Path(kconfig_file)) + else: + return "" + + def process_twister(module, meta): out = "" @@ -553,6 +626,12 @@ def main(): parser.add_argument('--cmake-out', help="""File to write with resulting : values to use for including in CMake""") + parser.add_argument('--sysbuild-kconfig-out', + help="""File to write with resulting KConfig import + statements.""") + parser.add_argument('--sysbuild-cmake-out', + help="""File to write with resulting : + values to use for including in CMake""") parser.add_argument('--meta-out', help="""Write a build meta YaML file containing a list of Zephyr modules and west projects. @@ -576,6 +655,8 @@ def main(): kconfig = "" cmake = "" + sysbuild_kconfig = "" + sysbuild_cmake = "" settings = "" twister = "" @@ -586,6 +667,8 @@ def main(): for module in modules: kconfig += process_kconfig(module.project, module.meta) cmake += process_cmake(module.project, module.meta) + sysbuild_kconfig += process_sysbuildkconfig(module.project, module.meta) + sysbuild_cmake += process_sysbuildcmake(module.project, module.meta) settings += process_settings(module.project, module.meta) twister += process_twister(module.project, module.meta) @@ -597,6 +680,14 @@ def main(): with open(args.cmake_out, 'w', encoding="utf-8") as fp: fp.write(cmake) + if args.sysbuild_kconfig_out: + with open(args.sysbuild_kconfig_out, 'w', encoding="utf-8") as fp: + fp.write(sysbuild_kconfig) + + if args.sysbuild_cmake_out: + with open(args.sysbuild_cmake_out, 'w', encoding="utf-8") as fp: + fp.write(sysbuild_cmake) + if args.settings_out: with open(args.settings_out, 'w', encoding="utf-8") as fp: fp.write('''\ diff --git a/share/sysbuild/CMakeLists.txt b/share/sysbuild/CMakeLists.txt index f0ee3763186..aeb0476d57d 100644 --- a/share/sysbuild/CMakeLists.txt +++ b/share/sysbuild/CMakeLists.txt @@ -30,6 +30,24 @@ 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(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 ${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/${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) + # Propagate bootloader and signing settings from this system to the MCUboot and # application image build systems. if(SB_CONFIG_BOOTLOADER_MCUBOOT) diff --git a/share/sysbuild/Kconfig b/share/sysbuild/Kconfig index 0ef8f8b8023..74c46ba2f71 100644 --- a/share/sysbuild/Kconfig +++ b/share/sysbuild/Kconfig @@ -6,6 +6,12 @@ comment "Sysbuild image configuration" osource "$(BOARD_DIR)/Kconfig.sysbuild" +menu "Modules" + +source "modules/Kconfig.sysbuild" + +endmenu + config EXPERIMENTAL bool help diff --git a/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake b/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake index 33e33fa59c2..5dbf82728a5 100644 --- a/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake +++ b/share/sysbuild/cmake/modules/sysbuild_kconfig.cmake @@ -46,7 +46,6 @@ 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")