cmake: extend zephyr_get() to handle build configurations from sysbuild

Enhance sysbuild controlled configurations.

The current scheme of passing settings using `-D` on the CMake
invocation is vulnerable to quoting and lists.

With `zephyr_get()` in place as a uniform way of handling user
controlled settings (CMake cache / environment / CMake local variable)
we have a mechanism in place for a cleaner handling of sysbuild
controlled settings.

This improves the robustness of variable passing and add the same cleans
up and simplifies the logic in sysbuild.

The Kconfig Zephyr CMake module has been updated accordingly so that
CONFIG settings are taken from the sysbuild shadow cache when sysbuild
is used as higher level build system.

Signed-off-by: Torsten Rasmussen <Torsten.Rasmussen@nordicsemi.no>
This commit is contained in:
Torsten Rasmussen 2022-08-18 16:02:50 +02:00 committed by Carles Cufí
parent 44a05e4439
commit 88ae9cbb9d
6 changed files with 144 additions and 95 deletions

View file

@ -0,0 +1,40 @@
# SPDX-License-Identifier: Apache-2.0
#
# Copyright (c) 2022, Nordic Semiconductor ASA
# Setup basic settings for a Zephyr project.
#
# Basic settings are:
# - sysbuild defined configuration settings
#
# Details for sysbuild settings:
#
# Sysbuild is a higher level build system used by Zephyr.
# Sysbuild allows users to build multiple samples for a given system.
#
# For this to work, sysbuild manages other Zephyr CMake build systems by setting
# dedicated build variables.
# This CMake modules loads the sysbuild cache variables as target properties on
# a sysbuild_cache target.
#
# This ensures that qoutes and lists are correctly preserved.
include_guard(GLOBAL)
if(SYSBUILD)
add_custom_target(sysbuild_cache)
file(STRINGS "${SYSBUILD_CACHE}" sysbuild_cache_strings)
foreach(str ${sysbuild_cache_strings})
# Using a regex for matching whole 'VAR_NAME:TYPE=VALUE' will strip semi-colons
# thus resulting in lists to become strings.
# Therefore we first fetch VAR_NAME and TYPE, and afterwards extract
# remaining of string into a value that populates the property.
# This method ensures that both quoted values and ;-separated list stays intact.
string(REGEX MATCH "([^:]*):([^=]*)=" variable_identifier ${str})
string(LENGTH ${variable_identifier} variable_identifier_length)
string(SUBSTRING "${str}" ${variable_identifier_length} -1 variable_value)
set_property(TARGET sysbuild_cache APPEND PROPERTY "SYSBUILD_CACHE:VARIABLES" "${CMAKE_MATCH_1}")
set_property(TARGET sysbuild_cache PROPERTY "${CMAKE_MATCH_1}:TYPE" "${CMAKE_MATCH_2}")
set_property(TARGET sysbuild_cache PROPERTY "${CMAKE_MATCH_1}" "${variable_value}")
endforeach()
endif()

View file

@ -35,7 +35,7 @@ else()
set(APPLICATION_CONFIG_DIR ${APPLICATION_SOURCE_DIR}) set(APPLICATION_CONFIG_DIR ${APPLICATION_SOURCE_DIR})
endif() endif()
zephyr_get(CONF_FILE) zephyr_get(CONF_FILE SYSBUILD LOCAL)
if(DEFINED CONF_FILE) if(DEFINED CONF_FILE)
# This ensures that CACHE{CONF_FILE} will be set correctly to current scope # This ensures that CACHE{CONF_FILE} will be set correctly to current scope
# variable CONF_FILE. An already current scope variable will stay the same. # variable CONF_FILE. An already current scope variable will stay the same.
@ -89,7 +89,7 @@ zephyr_file(CONF_FILES ${APPLICATION_CONFIG_DIR}/boards DTS APP_BOARD_DTS)
# The CONF_FILE variable is now set to its final value. # The CONF_FILE variable is now set to its final value.
zephyr_boilerplate_watch(CONF_FILE) zephyr_boilerplate_watch(CONF_FILE)
zephyr_get(DTC_OVERLAY_FILE) zephyr_get(DTC_OVERLAY_FILE SYSBUILD LOCAL)
if(DTC_OVERLAY_FILE) if(DTC_OVERLAY_FILE)
# DTC_OVERLAY_FILE has either been specified on the cmake CLI or is already # DTC_OVERLAY_FILE has either been specified on the cmake CLI or is already
# in the CMakeCache.txt. # in the CMakeCache.txt.

View file

@ -2316,15 +2316,23 @@ endfunction()
# Usage: # Usage:
# zephyr_get(<variable>) # zephyr_get(<variable>)
# zephyr_get(<variable> SYSBUILD [LOCAL|GLOBAL])
# #
# Return the value of <variable> as local scoped variable of same name. # Return the value of <variable> as local scoped variable of same name.
# #
# zephyr_get() is a common function to provide a uniform way of supporting # zephyr_get() is a common function to provide a uniform way of supporting
# build settings that can be set from CMakeLists.txt, CMake cache, or in # build settings that can be set from sysbuild, CMakeLists.txt, CMake cache, or
# environment. # in environment.
# #
# The order of precedence for variables defined in multiple scopes: # The order of precedence for variables defined in multiple scopes:
# - CMake cache, set by `-D<var>=<value>` or `set(<var> <val> CACHE ...)` # - Sysbuild defined when sysbuild is used.
# Sysbuild variables can be defined as global or local to specific image.
# Examples:
# - BOARD is considered a global sysbuild cache variable
# - blinky_BOARD is considered a local sysbuild cache variable only for the
# blinky image.
# If no sysbuild scope is specified, GLOBAL is assumed.
# - CMake cache, set by `-D<var>=<value>` or `set(<var> <val> CACHE ...)
# - Environment # - Environment
# - Locally in CMakeLists.txt before 'find_package(Zephyr)' # - Locally in CMakeLists.txt before 'find_package(Zephyr)'
# #
@ -2333,9 +2341,32 @@ endfunction()
# using `-DZEPHYR_TOOLCHAIN_VARIANT=<val>`, then the value from the cache is # using `-DZEPHYR_TOOLCHAIN_VARIANT=<val>`, then the value from the cache is
# returned. # returned.
function(zephyr_get variable) function(zephyr_get variable)
cmake_parse_arguments(GET_VAR "" "" "" ${ARGN}) cmake_parse_arguments(GET_VAR "" "SYSBUILD" "" ${ARGN})
if(DEFINED CACHE{${variable}}) if(DEFINED GET_VAR_SYSBUILD)
if(NOT (${GET_VAR_SYSBUILD} STREQUAL "GLOBAL" OR
${GET_VAR_SYSBUILD} STREQUAL "LOCAL")
)
message(FATAL_ERROR "zephyr_get(... SYSBUILD) requires GLOBAL or LOCAL.")
endif()
else()
set(GET_VAR_SYSBUILD "GLOBAL")
endif()
if(SYSBUILD)
get_property(sysbuild_name TARGET sysbuild_cache PROPERTY SYSBUILD_NAME)
get_property(sysbuild_main_app TARGET sysbuild_cache PROPERTY SYSBUILD_MAIN_APP)
get_property(sysbuild_${variable} TARGET sysbuild_cache PROPERTY ${sysbuild_name}_${variable})
if(NOT DEFINED sysbuild_${variable} AND
(${GET_VAR_SYSBUILD} STREQUAL "GLOBAL" OR sysbuild_main_app)
)
get_property(sysbuild_${variable} TARGET sysbuild_cache PROPERTY ${variable})
endif()
endif()
if(DEFINED sysbuild_${variable})
set(${variable} ${sysbuild_${variable}} PARENT_SCOPE)
elseif(DEFINED CACHE{${variable}})
set(${variable} $CACHE{${variable}} PARENT_SCOPE) set(${variable} $CACHE{${variable}} PARENT_SCOPE)
elseif(DEFINED ENV{${variable}}) elseif(DEFINED ENV{${variable}})
set(${variable} $ENV{${variable}} PARENT_SCOPE) set(${variable} $ENV{${variable}} PARENT_SCOPE)

View file

@ -72,7 +72,7 @@ if(CONF_FILE)
string(REPLACE " " ";" CONF_FILE_AS_LIST "${CONF_FILE}") string(REPLACE " " ";" CONF_FILE_AS_LIST "${CONF_FILE}")
endif() endif()
zephyr_get(OVERLAY_CONFIG) zephyr_get(OVERLAY_CONFIG SYSBUILD LOCAL)
if(OVERLAY_CONFIG) if(OVERLAY_CONFIG)
string(REPLACE " " ";" OVERLAY_CONFIG_AS_LIST "${OVERLAY_CONFIG}") string(REPLACE " " ";" OVERLAY_CONFIG_AS_LIST "${OVERLAY_CONFIG}")
endif() endif()
@ -181,7 +181,25 @@ endforeach()
# This feature is experimental and undocumented until it has undergone more # This feature is experimental and undocumented until it has undergone more
# user-testing. # user-testing.
unset(EXTRA_KCONFIG_OPTIONS) unset(EXTRA_KCONFIG_OPTIONS)
get_cmake_property(cache_variable_names CACHE_VARIABLES) if(SYSBUILD)
get_property(sysbuild_variable_names TARGET sysbuild_cache PROPERTY "SYSBUILD_CACHE:VARIABLES")
zephyr_get(SYSBUILD_MAIN_APP)
zephyr_get(SYSBUILD_NAME)
foreach (name ${sysbuild_variable_names})
if("${name}" MATCHES "^${SYSBUILD_NAME}_${KCONFIG_NAMESPACE}_")
string(REGEX REPLACE "^${SYSBUILD_NAME}_" "" org_name ${name})
get_property(${org_name} TARGET sysbuild_cache PROPERTY ${name})
list(APPEND cache_variable_names ${org_name})
elseif(SYSBUILD_MAIN_APP AND "${name}" MATCHES "^${KCONFIG_NAMESPACE}_")
get_property(${name} TARGET sysbuild_cache PROPERTY ${name})
list(APPEND cache_variable_names ${name})
endif()
endforeach()
else()
get_cmake_property(cache_variable_names CACHE_VARIABLES)
endif()
foreach (name ${cache_variable_names}) foreach (name ${cache_variable_names})
if("${name}" MATCHES "^CLI_${KCONFIG_NAMESPACE}_") if("${name}" MATCHES "^CLI_${KCONFIG_NAMESPACE}_")
# Variable was set by user in earlier invocation, let's append to extra # Variable was set by user in earlier invocation, let's append to extra

View file

@ -54,6 +54,9 @@ list(APPEND zephyr_cmake_modules user_cache)
list(APPEND zephyr_cmake_modules extensions) list(APPEND zephyr_cmake_modules extensions)
list(APPEND zephyr_cmake_modules version) list(APPEND zephyr_cmake_modules version)
# Load basic settings
list(APPEND zephyr_cmake_modules basic_settings)
# #
# Find tools # Find tools
# #

View file

@ -32,105 +32,60 @@ function(ExternalZephyrProject_Add)
) )
endif() endif()
set(sysbuild_vars # CMake variables which must be known by all Zephyr CMake build systems
"APP_DIR" # Those are settings which controls the build and must be known to CMake at
"SB_CONF_FILE" # invocation time, and thus cannot be passed though the sysbuild cache 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
# `<image>_CONF_FILE`, like `mcuboot_CONF_FILE`
set( set(
shared_image_variables_list shared_cmake_variables_list
CMAKE_BUILD_TYPE CMAKE_BUILD_TYPE
CMAKE_VERBOSE_MAKEFILE CMAKE_VERBOSE_MAKEFILE
BOARD
ZEPHYR_MODULES
ZEPHYR_EXTRA_MODULES
ZEPHYR_TOOLCHAIN_VARIANT
EXTRA_KCONFIG_TARGETS
) )
set(shared_image_variables_regex set(sysbuild_cache_file ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION}_sysbuild_cache.txt)
"^[^_]*_TOOLCHAIN_PATH|^[^_]*_ROOT"
)
set(app_cache_file ${CMAKE_BINARY_DIR}/CMake${ZBUILD_APPLICATION}PreloadCache.txt) get_cmake_property(sysbuild_cache CACHE_VARIABLES)
foreach(var_name ${sysbuild_cache})
if(EXISTS ${app_cache_file}) if(NOT "${var_name}" MATCHES "^CMAKE_.*")
file(STRINGS ${app_cache_file} app_cache_strings) # We don't want to pass internal CMake variables.
set(app_cache_strings_current ${app_cache_strings}) # Required CMake variable to be passed, like CMAKE_BUILD_TYPE must be
endif() # passed using `-D` on command invocation.
get_property(var_type CACHE ${var_name} PROPERTY TYPE)
get_cmake_property(variables_cached CACHE_VARIABLES) set(cache_entry "${var_name}:${var_type}=${${var_name}}")
foreach(var_name ${variables_cached}) string(REPLACE ";" "\;" cache_entry "${cache_entry}")
# Any var of the form `<app>_<var>` should be propagated. list(APPEND sysbuild_cache_strings "${cache_entry}\n")
# For example mcuboot_<VAR>=<val> ==> -D<VAR>=<val> 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() endif()
endforeach() endforeach()
list(APPEND sysbuild_cache_strings "SYSBUILD_NAME:STRING=${ZBUILD_APPLICATION}\n")
foreach(app_var_name ${application_vars}) if(ZBUILD_MAIN_APP)
string(REGEX REPLACE "^${ZBUILD_APPLICATION}_" "" var_name "${app_var_name}") list(APPEND sysbuild_cache_strings "SYSBUILD_MAIN_APP:BOOL=True\n")
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() endif()
if(DEFINED ZBUILD_BOARD) if(DEFINED ZBUILD_BOARD)
list(APPEND app_cache_entries "-DBOARD=${ZBUILD_BOARD}") # Only set image specific board if provided.
elseif(NOT ZBUILD_MAIN_APP) # The sysbuild BOARD is exported through sysbuild cache, and will be used
list(APPEND app_cache_entries "-DBOARD=${BOARD}") # unless <image>_BOARD is defined.
list(APPEND sysbuild_cache_strings "${ZBUILD_APPLICATION}_BOARD:STRING=${ZBUILD_BOARD}\n")
endif() endif()
file(WRITE ${sysbuild_cache_file}.tmp ${sysbuild_cache_strings})
zephyr_file_copy(${sysbuild_cache_file}.tmp ${sysbuild_cache_file} ONLY_IF_DIFFERENT)
set(shared_cmake_vars_argument)
foreach(shared_var ${shared_cmake_variables_list})
if(DEFINED CACHE{${ZBUILD_APPLICATION}_${shared_var}})
get_property(var_type CACHE ${ZBUILD_APPLICATION}_${shared_var} PROPERTY TYPE)
list(APPEND shared_cmake_vars_argument
"-D${shared_var}:${var_type}=$CACHE{${ZBUILD_APPLICATION}_${shared_var}}"
)
elseif(DEFINED CACHE{${shared_var}})
get_property(var_type CACHE ${shared_var} PROPERTY TYPE)
list(APPEND shared_cmake_vars_argument
"-D${shared_var}:${var_type}=$CACHE{${shared_var}}"
)
endif()
endforeach()
set(image_banner "* Running CMake for ${ZBUILD_APPLICATION} *") set(image_banner "* Running CMake for ${ZBUILD_APPLICATION} *")
string(LENGTH "${image_banner}" image_banner_width) string(LENGTH "${image_banner}" image_banner_width)
string(REPEAT "*" ${image_banner_width} image_banner_header) string(REPEAT "*" ${image_banner_width} image_banner_header)
@ -142,7 +97,9 @@ function(ExternalZephyrProject_Add)
execute_process( execute_process(
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
-G${CMAKE_GENERATOR} -G${CMAKE_GENERATOR}
${app_cache_entries} -DSYSBUILD:BOOL=True
-DSYSBUILD_CACHE:FILEPATH=${sysbuild_cache_file}
${shared_cmake_vars_argument}
-B${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION} -B${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION}
-S${ZBUILD_SOURCE_DIR} -S${ZBUILD_SOURCE_DIR}
RESULT_VARIABLE return_val RESULT_VARIABLE return_val