1# Copyright (c) 2021-2023 Nordic Semiconductor
2#
3# SPDX-License-Identifier: Apache-2.0
4
5# Usage:
6#   load_cache(IMAGE <image> BINARY_DIR <dir>)
7#
8# This function will load the CMakeCache.txt file from the binary directory
9# given by the BINARY_DIR argument.
10#
11# All CMake cache variables are stored in a custom target which is identified by
12# the name given as value to the IMAGE argument.
13#
14# IMAGE:      image name identifying the cache for later sysbuild_get() lookup calls.
15# BINARY_DIR: binary directory (build dir) containing the CMakeCache.txt file to load.
16function(load_cache)
17  set(single_args IMAGE BINARY_DIR)
18  cmake_parse_arguments(LOAD_CACHE "" "${single_args}" "" ${ARGN})
19
20  if(NOT TARGET ${LOAD_CACHE_IMAGE}_cache)
21    add_custom_target(${LOAD_CACHE_IMAGE}_cache)
22  endif()
23  file(STRINGS "${LOAD_CACHE_BINARY_DIR}/CMakeCache.txt" cache_strings ENCODING UTF-8)
24  foreach(str ${cache_strings})
25    # Using a regex for matching whole 'VAR_NAME:TYPE=VALUE' will strip semi-colons
26    # thus resulting in lists to become strings.
27    # Therefore we first fetch VAR_NAME and TYPE, and afterwards extract
28    # remaining of string into a value that populates the property.
29    # This method ensures that both quoted values and ;-separated list stays intact.
30    string(REGEX MATCH "([^:]*):([^=]*)=" variable_identifier ${str})
31    if(NOT "${variable_identifier}" STREQUAL "")
32      string(LENGTH ${variable_identifier} variable_identifier_length)
33      string(SUBSTRING "${str}" ${variable_identifier_length} -1 variable_value)
34      set_property(TARGET ${LOAD_CACHE_IMAGE}_cache APPEND PROPERTY "CACHE:VARIABLES" "${CMAKE_MATCH_1}")
35      set_property(TARGET ${LOAD_CACHE_IMAGE}_cache PROPERTY "${CMAKE_MATCH_1}:TYPE" "${CMAKE_MATCH_2}")
36      set_property(TARGET ${LOAD_CACHE_IMAGE}_cache PROPERTY "${CMAKE_MATCH_1}" "${variable_value}")
37      if("${CMAKE_MATCH_1}" MATCHES "^BYPRODUCT_.*")
38        set_property(TARGET ${LOAD_CACHE_IMAGE}_cache APPEND
39                     PROPERTY "EXTRA_BYPRODUCTS" "${variable_value}"
40        )
41      endif()
42    endif()
43  endforeach()
44endfunction()
45
46# Usage:
47#   sysbuild_get(<variable> IMAGE <image> [VAR <image-variable>] <KCONFIG|CACHE>)
48#
49# This function will return the variable found in the CMakeCache.txt file
50# identified by image.
51# If `VAR` is provided, the name given as parameter will be looked up, but if
52# `VAR` is not given, the `<variable>` name provided will be used both for
53# lookup and value return.
54#
55# The result will be returned in `<variable>`.
56#
57# Example use:
58#   sysbuild_get(PROJECT_NAME IMAGE my_sample CACHE)
59#     will lookup PROJECT_NAME from the CMakeCache identified by `my_sample` and
60#     and return the value in the local variable `PROJECT_NAME`.
61#
62#   sysbuild_get(my_sample_PROJECT_NAME IMAGE my_sample VAR PROJECT_NAME CACHE)
63#     will lookup PROJECT_NAME from the CMakeCache identified by `my_sample` and
64#     and return the value in the local variable `my_sample_PROJECT_NAME`.
65#
66#   sysbuild_get(my_sample_CONFIG_FOO IMAGE my_sample VAR CONFIG_FOO KCONFIG)
67#     will lookup CONFIG_FOO from the KConfig identified by `my_sample` and
68#     and return the value in the local variable `my_sample_CONFIG_FOO`.
69#
70# <variable>: variable used for returning CMake cache value. Also used as lookup
71#             variable if `VAR` is not provided.
72# IMAGE:      image name identifying the cache to use for variable lookup.
73# VAR:        name of the CMake cache variable name to lookup.
74# KCONFIG:    Flag indicating that a Kconfig setting should be fetched.
75# CACHE:      Flag indicating that a CMake cache variable should be fetched.
76function(sysbuild_get variable)
77  cmake_parse_arguments(GET_VAR "CACHE;KCONFIG" "IMAGE;VAR" "" ${ARGN})
78
79  if(NOT DEFINED GET_VAR_IMAGE)
80    message(FATAL_ERROR "sysbuild_get(...) requires IMAGE.")
81  endif()
82
83  if(DEFINED ${variable} AND NOT DEFINED GET_VAR_VAR)
84    message(WARNING "Return variable ${variable} already defined with a value. "
85                    "sysbuild_get(${variable} ...) may overwrite existing value. "
86		    "Please use sysbuild_get(<variable> ... VAR <image-variable>) "
87		    "where <variable> is undefined."
88    )
89  endif()
90
91  if(NOT DEFINED GET_VAR_VAR)
92    set(GET_VAR_VAR ${variable})
93  endif()
94
95  if(GET_VAR_KCONFIG)
96    set(variable_target ${GET_VAR_IMAGE})
97  elseif(GET_VAR_CACHE)
98    set(variable_target ${GET_VAR_IMAGE}_cache)
99  else()
100    message(WARNING "<CACHE> or <KCONFIG> not specified, defaulting to CACHE")
101    set(variable_target ${GET_VAR_IMAGE}_cache)
102  endif()
103
104  get_property(${GET_VAR_IMAGE}_${GET_VAR_VAR} TARGET ${variable_target} PROPERTY ${GET_VAR_VAR})
105  if(DEFINED ${GET_VAR_IMAGE}_${GET_VAR_VAR})
106    set(${variable} ${${GET_VAR_IMAGE}_${GET_VAR_VAR}} PARENT_SCOPE)
107  endif()
108endfunction()
109
110# Usage:
111#   sysbuild_cache(CREATE APPLICATION <name> [CMAKE_RERUN])
112#
113# This function works on the sysbuild cache for sysbuild managed applications.
114#
115# Arguments:
116# CREATE            : Create or update existing sysbuild cache file for the application.
117#                     The sysbuild cache is only updated if it contain changes.
118# APPLICATION <name>: Name of the application.
119# CMAKE_RERUN       : Force a CMake rerun for the application during next build
120#                     invocation if the sysbuild cache has changed. It is
121#                     advised to always use this flag. Not using this flag can
122#                     reduce build time, but only do so if application is
123#                     guaranteed to be up-to-date.
124#
125function(sysbuild_cache)
126  cmake_parse_arguments(SB_CACHE "CREATE;CMAKE_RERUN" "APPLICATION" "" ${ARGN})
127  zephyr_check_arguments_required(sysbuild_cache SB_CACHE APPLICATION)
128  zephyr_check_flags_required(sysbuild_cache SB_CACHE CREATE)
129
130  get_target_property(${SB_CACHE_APPLICATION}_MAIN_APP ${SB_CACHE_APPLICATION} MAIN_APP)
131  get_cmake_property(sysbuild_cache CACHE_VARIABLES)
132
133  foreach(var_name ${sysbuild_cache})
134    if(NOT "${var_name}" MATCHES "^(CMAKE_.*|BOARD)$")
135      # Perform a dummy read to prevent a false warning about unused variables
136      # being emitted due to a cmake bug: https://gitlab.kitware.com/cmake/cmake/-/issues/24555
137      set(unused_tmp_var ${${var_name}})
138
139      # We don't want to pass internal CMake variables.
140      # Required CMake variable to be passed, like CMAKE_BUILD_TYPE must be
141      # passed using `-D` on command invocation.
142      get_property(var_type CACHE ${var_name} PROPERTY TYPE)
143      set(cache_entry "${var_name}:${var_type}=$CACHE{${var_name}}")
144      string(REPLACE ";" "\;" cache_entry "${cache_entry}")
145      list(APPEND sysbuild_cache_strings "${cache_entry}\n")
146    endif()
147  endforeach()
148  if(DEFINED BOARD_REVISION)
149    list(APPEND sysbuild_cache_strings "BOARD:STRING=${BOARD}@${BOARD_REVISION}${BOARD_QUALIFIERS}\n")
150  else()
151    list(APPEND sysbuild_cache_strings "BOARD:STRING=${BOARD}${BOARD_QUALIFIERS}\n")
152  endif()
153  list(APPEND sysbuild_cache_strings "SYSBUILD_NAME:STRING=${SB_CACHE_APPLICATION}\n")
154
155  if(${SB_CACHE_APPLICATION}_MAIN_APP)
156    list(APPEND sysbuild_cache_strings "SYSBUILD_MAIN_APP:BOOL=True\n")
157  endif()
158
159  if(${SB_CACHE_APPLICATION}_BOARD AND NOT DEFINED CACHE{${SB_CACHE_APPLICATION}_BOARD})
160    # Only set image specific board if provided.
161    # The sysbuild BOARD is exported through sysbuild cache, and will be used
162    # unless <image>_BOARD is defined.
163    list(APPEND sysbuild_cache_strings
164         "${SB_CACHE_APPLICATION}_BOARD:STRING=${${SB_CACHE_APPLICATION}_BOARD}\n"
165    )
166  endif()
167
168  get_target_property(${SB_CACHE_APPLICATION}_CACHE_FILE ${SB_CACHE_APPLICATION} CACHE_FILE)
169  file(WRITE ${${SB_CACHE_APPLICATION}_CACHE_FILE}.tmp ${sysbuild_cache_strings})
170  if(SB_CACHE_CMAKE_RERUN)
171    execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files
172                    ${${SB_CACHE_APPLICATION}_CACHE_FILE}.tmp
173                    ${${SB_CACHE_APPLICATION}_CACHE_FILE}
174                    RESULT_VARIABLE compare_res
175    )
176    if(NOT compare_res EQUAL 0)
177      zephyr_file_copy(${${SB_CACHE_APPLICATION}_CACHE_FILE}.tmp
178                       ${${SB_CACHE_APPLICATION}_CACHE_FILE}
179      )
180      ExternalProject_Get_Property(${SB_CACHE_APPLICATION} BINARY_DIR)
181      file(TOUCH_NOCREATE ${BINARY_DIR}/CMakeCache.txt)
182    endif()
183  else()
184    zephyr_file_copy(${${SB_CACHE_APPLICATION}_CACHE_FILE}.tmp
185                     ${${SB_CACHE_APPLICATION}_CACHE_FILE} ONLY_IF_DIFFERENT
186    )
187  endif()
188
189endfunction()
190
191# Usage:
192#   ExternalZephyrProject_Add(APPLICATION <name>
193#                             SOURCE_DIR <dir>
194#                             [BOARD <board> [BOARD_REVISION <revision>]]
195#                             [APP_TYPE <MAIN|BOOTLOADER>]
196#   )
197#
198# This function includes a Zephyr based build system into the multiimage
199# build system
200#
201# APPLICATION: <name>:       Name of the application, name will also be used for build
202#                            folder of the application
203# SOURCE_DIR <dir>:          Source directory of the application
204# BOARD <board>:             Use <board> for application build instead user defined BOARD.
205# BOARD_REVISION <revision>: Use <revision> of <board> for application (only valid if
206#                            <board> is also supplied).
207# APP_TYPE <MAIN|BOOTLOADER>: Application type.
208#                             MAIN indicates this application is the main application
209#                             and where user defined settings should be passed on as-is
210#                             except for multi image build flags.
211#                             For example, -DCONF_FILES=<files> will be passed on to the
212#                             MAIN_APP unmodified.
213#                             BOOTLOADER indicates this app is a bootloader
214# BUILD_ONLY <bool>:          Mark the application as build-only. If <bool> evaluates to
215#                             true, then this application will be excluded from flashing
216#                             and debugging.
217#
218function(ExternalZephyrProject_Add)
219  set(app_types MAIN BOOTLOADER FIRMWARE_LOADER)
220  cmake_parse_arguments(ZBUILD "" "APPLICATION;BOARD;BOARD_REVISION;SOURCE_DIR;APP_TYPE;BUILD_ONLY" "" ${ARGN})
221
222  if(ZBUILD_UNPARSED_ARGUMENTS)
223    message(FATAL_ERROR
224      "ExternalZephyrProject_Add(${ARGV0} <val> ...) given unknown arguments:"
225      " ${ZBUILD_UNPARSED_ARGUMENTS}"
226    )
227  endif()
228
229  if(TARGET ${ZBUILD_APPLICATION})
230    message(FATAL_ERROR
231      "ExternalZephyrProject_Add(APPLICATION ${ZBUILD_APPLICATION} ...) "
232      "already exists. Application names must be unique."
233    )
234  endif()
235
236  if(DEFINED ZBUILD_APP_TYPE)
237    if(NOT ZBUILD_APP_TYPE IN_LIST app_types)
238      message(FATAL_ERROR
239        "ExternalZephyrProject_Add(APP_TYPE <val> ...) given unknown type: ${ZBUILD_APP_TYPE}\n"
240        "Valid types are: ${app_types}"
241      )
242    endif()
243
244  endif()
245
246  if(NOT DEFINED SYSBUILD_CURRENT_SOURCE_DIR)
247    message(FATAL_ERROR
248      "ExternalZephyrProject_Add(${ARGV0} <val> ...) must not be called outside of"
249      " sysbuild_add_subdirectory(). SYSBUILD_CURRENT_SOURCE_DIR is undefined."
250    )
251  endif()
252  set_property(
253    DIRECTORY "${SYSBUILD_CURRENT_SOURCE_DIR}"
254    APPEND PROPERTY sysbuild_images ${ZBUILD_APPLICATION}
255  )
256  set_property(
257    GLOBAL
258    APPEND PROPERTY sysbuild_images ${ZBUILD_APPLICATION}
259  )
260
261  set(sysbuild_image_conf_dir ${APP_DIR}/sysbuild)
262  set(sysbuild_image_name_conf_dir ${APP_DIR}/sysbuild/${ZBUILD_APPLICATION})
263  # User defined `-D<image>_CONF_FILE=<file.conf>` takes precedence over anything else.
264  if(NOT ${ZBUILD_APPLICATION}_CONF_FILE)
265    if(EXISTS ${sysbuild_image_name_conf_dir})
266      set(${ZBUILD_APPLICATION}_APPLICATION_CONFIG_DIR ${sysbuild_image_name_conf_dir}
267          CACHE INTERNAL "Application configuration dir controlled by sysbuild"
268      )
269    endif()
270
271    # Check for sysbuild related configuration fragments.
272    # The contents of these are appended to the image existing configuration
273    # when user is not specifying custom fragments.
274    zephyr_file(CONF_FILES ${sysbuild_image_conf_dir} KCONF sysbuild_image_conf_fragment
275                NAMES ${ZBUILD_APPLICATION}.conf SUFFIX ${FILE_SUFFIX}
276    )
277
278    if(NOT (${ZBUILD_APPLICATION}_OVERLAY_CONFIG OR ${ZBUILD_APPLICATION}_EXTRA_CONF_FILE)
279        AND EXISTS ${sysbuild_image_conf_fragment}
280    )
281      set(${ZBUILD_APPLICATION}_EXTRA_CONF_FILE ${sysbuild_image_conf_fragment}
282          CACHE INTERNAL "Kconfig fragment defined by main application"
283      )
284    endif()
285
286    if(NOT ${ZBUILD_APPLICATION}_DTC_OVERLAY_FILE)
287      # Check for overlay named <ZBUILD_APPLICATION>.overlay.
288      set(sysbuild_image_dts_overlay_files ${sysbuild_image_conf_dir}/${ZBUILD_APPLICATION}.overlay)
289
290      # Check for overlay named <ZBUILD_APPLICATION>_<FILE_SUFFIX>.overlay.
291      if(FILE_SUFFIX)
292        list(PREPEND sysbuild_image_dts_overlay_files ${sysbuild_image_conf_dir}/${ZBUILD_APPLICATION}_${FILE_SUFFIX}.overlay)
293      endif()
294
295      foreach(overlay_file ${sysbuild_image_dts_overlay_files})
296        if(EXISTS ${overlay_file})
297          set(${ZBUILD_APPLICATION}_DTC_OVERLAY_FILE ${overlay_file}
298            CACHE INTERNAL "devicetree overlay file defined by main application"
299          )
300          break()
301        endif()
302      endforeach()
303    endif()
304  endif()
305
306  # Update ROOT variables with relative paths to use absolute paths based on
307  # the source application directory.
308  foreach(type MODULE_EXT BOARD SOC ARCH SCA)
309    if(DEFINED CACHE{${ZBUILD_APPLICATION}_${type}_ROOT} AND NOT IS_ABSOLUTE $CACHE{${ZBUILD_APPLICATION}_${type}_ROOT})
310      set(rel_path $CACHE{${ZBUILD_APPLICATION}_${type}_ROOT})
311      cmake_path(ABSOLUTE_PATH rel_path BASE_DIRECTORY "${ZBUILD_SOURCE_DIR}" NORMALIZE OUTPUT_VARIABLE abs_path)
312      set(${ZBUILD_APPLICATION}_${type}_ROOT ${abs_path} CACHE PATH "Sysbuild adjusted absolute path" FORCE)
313    endif()
314  endforeach()
315
316  # CMake variables which must be known by all Zephyr CMake build systems
317  # Those are settings which controls the build and must be known to CMake at
318  # invocation time, and thus cannot be passed though the sysbuild cache file.
319  set(
320    shared_cmake_variables_list
321    CMAKE_BUILD_TYPE
322    CMAKE_VERBOSE_MAKEFILE
323  )
324
325  set(sysbuild_cache_file ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION}_sysbuild_cache.txt)
326
327  set(shared_cmake_vars_argument)
328  foreach(shared_var ${shared_cmake_variables_list})
329    if(DEFINED CACHE{${ZBUILD_APPLICATION}_${shared_var}})
330      get_property(var_type  CACHE ${ZBUILD_APPLICATION}_${shared_var} PROPERTY TYPE)
331      list(APPEND shared_cmake_vars_argument
332           "-D${shared_var}:${var_type}=$CACHE{${ZBUILD_APPLICATION}_${shared_var}}"
333      )
334    elseif(DEFINED CACHE{${shared_var}})
335      get_property(var_type  CACHE ${shared_var} PROPERTY TYPE)
336      list(APPEND shared_cmake_vars_argument
337           "-D${shared_var}:${var_type}=$CACHE{${shared_var}}"
338      )
339    endif()
340  endforeach()
341
342  foreach(kconfig_target
343      menuconfig
344      hardenconfig
345      guiconfig
346      $CACHE{EXTRA_KCONFIG_TARGETS}
347      )
348
349    if(NOT ZBUILD_APP_TYPE STREQUAL "MAIN")
350      set(image_prefix "${ZBUILD_APPLICATION}_")
351    endif()
352
353    add_custom_target(${image_prefix}${kconfig_target}
354      ${CMAKE_MAKE_PROGRAM} ${kconfig_target}
355      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION}
356      USES_TERMINAL
357      )
358  endforeach()
359
360  set(list_separator ",")
361  set(image_extra_kconfig_targets "-DEXTRA_KCONFIG_TARGETS=$CACHE{EXTRA_KCONFIG_TARGETS}")
362  string(REPLACE ";" "${list_separator}" image_extra_kconfig_targets "${image_extra_kconfig_targets}")
363  foreach(target $CACHE{EXTRA_KCONFIG_TARGETS})
364    list(APPEND image_extra_kconfig_targets
365         -DEXTRA_KCONFIG_TARGET_COMMAND_FOR_${target}=$CACHE{EXTRA_KCONFIG_TARGET_COMMAND_FOR_${target}}
366    )
367  endforeach()
368
369  include(ExternalProject)
370  set(application_binary_dir ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION})
371  ExternalProject_Add(
372    ${ZBUILD_APPLICATION}
373    SOURCE_DIR ${ZBUILD_SOURCE_DIR}
374    BINARY_DIR ${application_binary_dir}
375    CONFIGURE_COMMAND ""
376    LIST_SEPARATOR "${list_separator}"
377    CMAKE_ARGS -DSYSBUILD:BOOL=True
378               -DSYSBUILD_CACHE:FILEPATH=${sysbuild_cache_file}
379               ${shared_cmake_vars_argument}
380               ${image_extra_kconfig_targets}
381    BUILD_COMMAND ${CMAKE_COMMAND} --build .
382    INSTALL_COMMAND ""
383    BUILD_ALWAYS True
384    USES_TERMINAL_BUILD True
385  )
386  set_property(TARGET ${ZBUILD_APPLICATION} PROPERTY APP_TYPE ${ZBUILD_APP_TYPE})
387  set_property(TARGET ${ZBUILD_APPLICATION} PROPERTY CONFIG
388               "# sysbuild controlled configuration settings\n"
389  )
390  set_target_properties(${ZBUILD_APPLICATION} PROPERTIES CACHE_FILE ${sysbuild_cache_file})
391  set_target_properties(${ZBUILD_APPLICATION} PROPERTIES KCONFIG_BINARY_DIR
392                        ${application_binary_dir}/Kconfig
393  )
394  if("${ZBUILD_APP_TYPE}" STREQUAL "MAIN")
395    set_target_properties(${ZBUILD_APPLICATION} PROPERTIES MAIN_APP True)
396  endif()
397
398  set(image_default "${CMAKE_SOURCE_DIR}/image_configurations/ALL_image_default.cmake")
399
400  if(DEFINED ZBUILD_APP_TYPE)
401    list(APPEND image_default "${CMAKE_SOURCE_DIR}/image_configurations/${ZBUILD_APP_TYPE}_image_default.cmake")
402    set(image_default_dtc_overlay "${CMAKE_SOURCE_DIR}/image_configurations/${ZBUILD_APP_TYPE}_image_default.overlay")
403
404    if(EXISTS ${image_default_dtc_overlay})
405      if(NOT ${image_default_dtc_overlay} IN_LIST ${ZBUILD_APPLICATION}_EXTRA_DTC_OVERLAY_FILE)
406        list(APPEND ${ZBUILD_APPLICATION}_EXTRA_DTC_OVERLAY_FILE ${image_default_dtc_overlay})
407        set(${ZBUILD_APPLICATION}_EXTRA_DTC_OVERLAY_FILE
408            ${${ZBUILD_APPLICATION}_EXTRA_DTC_OVERLAY_FILE}
409            CACHE INTERNAL "Application extra DTC overlay file" FORCE
410        )
411      endif()
412    endif()
413  endif()
414
415  set_target_properties(${ZBUILD_APPLICATION} PROPERTIES IMAGE_CONF_SCRIPT "${image_default}")
416
417  if(DEFINED ZBUILD_BOARD)
418    # Only set image specific board if provided.
419    # The sysbuild BOARD is exported through sysbuild cache, and will be used
420    # unless <image>_BOARD is defined.
421    if(DEFINED ZBUILD_BOARD_REVISION)
422      # Use provided board revision, HWMv2 requires adding version to the board, split elements
423      # up, attach version, then reassemble into a complete string
424      string(REPLACE "/" ";" split_board_qualifiers "${ZBUILD_BOARD}")
425      list(GET split_board_qualifiers 0 target_board)
426      set(target_board ${target_board}@${ZBUILD_BOARD_REVISION})
427      list(REMOVE_AT split_board_qualifiers 0)
428      list(PREPEND split_board_qualifiers ${target_board})
429      string(REPLACE ";" "/" board_qualifiers "${split_board_qualifiers}")
430      set_target_properties(${ZBUILD_APPLICATION} PROPERTIES BOARD ${board_qualifiers})
431      set(split_board_qualifiers)
432      set(board_qualifiers)
433    else()
434      set_target_properties(${ZBUILD_APPLICATION} PROPERTIES BOARD ${ZBUILD_BOARD})
435    endif()
436  elseif(DEFINED ZBUILD_BOARD_REVISION)
437    message(FATAL_ERROR
438      "ExternalZephyrProject_Add(... BOARD_REVISION ${ZBUILD_BOARD_REVISION})"
439      " requires BOARD."
440    )
441  endif()
442
443  if(DEFINED ZBUILD_BUILD_ONLY)
444    set_target_properties(${ZBUILD_APPLICATION} PROPERTIES BUILD_ONLY ${ZBUILD_BUILD_ONLY})
445  endif()
446endfunction()
447
448# Usage:
449#   ExternalZephyrProject_Cmake(APPLICATION <name>)
450#
451# This function invokes the CMake configure step on an external Zephyr project
452# which has been added at an earlier stage using `ExternalZephyrProject_Add()`
453#
454# If the application is not due to ExternalZephyrProject_Add() being called,
455# then an error is raised.
456#
457# The image output files are added as target properties on the image target as:
458# ELF_OUT: property specifying the generated elf file.
459# BIN_OUT: property specifying the generated bin file.
460# HEX_OUT: property specifying the generated hex file.
461# S19_OUT: property specifying the generated s19 file.
462# UF2_OUT: property specifying the generated uf2 file.
463# EXE_OUT: property specifying the generated exe file.
464#
465# the property is only set if the image is configured to generate the output
466# format. Elf files are always created.
467#
468# APPLICATION: <name>: Name of the application.
469#
470function(ExternalZephyrProject_Cmake)
471  cmake_parse_arguments(ZCMAKE "" "APPLICATION" "" ${ARGN})
472
473  if(ZBUILD_UNPARSED_ARGUMENTS)
474    message(FATAL_ERROR
475      "ExternalZephyrProject_Cmake(${ARGV0} <val> ...) given unknown arguments:"
476      " ${ZBUILD_UNPARSED_ARGUMENTS}"
477    )
478  endif()
479
480  if(NOT DEFINED ZCMAKE_APPLICATION)
481    message(FATAL_ERROR "Missing required argument: APPLICATION")
482  endif()
483
484  if(NOT TARGET ${ZCMAKE_APPLICATION})
485    message(FATAL_ERROR
486      "${ZCMAKE_APPLICATION} does not exists. Remember to call "
487      "ExternalZephyrProject_Add(APPLICATION ${ZCMAKE_APPLICATION} ...) first."
488    )
489  endif()
490
491  set(image_banner "* Running CMake for ${ZCMAKE_APPLICATION} *")
492  string(LENGTH "${image_banner}" image_banner_width)
493  string(REPEAT "*" ${image_banner_width} image_banner_header)
494  message(STATUS "\n   ${image_banner_header}\n"
495                 "   ${image_banner}\n"
496                 "   ${image_banner_header}\n"
497  )
498
499  ExternalProject_Get_Property(${ZCMAKE_APPLICATION} SOURCE_DIR BINARY_DIR CMAKE_ARGS LIST_SEPARATOR)
500  get_target_property(${ZCMAKE_APPLICATION}_BOARD      ${ZCMAKE_APPLICATION} BOARD)
501
502  get_property(${ZCMAKE_APPLICATION}_CONF_SCRIPT TARGET ${ZCMAKE_APPLICATION}
503               PROPERTY IMAGE_CONF_SCRIPT
504  )
505
506  sysbuild_cache(CREATE APPLICATION ${ZCMAKE_APPLICATION})
507
508  foreach(script ${${ZCMAKE_APPLICATION}_CONF_SCRIPT})
509    include(${script})
510  endforeach()
511
512  set(dotconfigsysbuild ${BINARY_DIR}/zephyr/.config.sysbuild)
513  get_target_property(config_content ${ZCMAKE_APPLICATION} CONFIG)
514  string(CONFIGURE "${config_content}" config_content)
515  file(WRITE ${dotconfigsysbuild} ${config_content})
516
517  string(REPLACE "${LIST_SEPARATOR}" "\\;" CMAKE_ARGS "${CMAKE_ARGS}")
518  execute_process(
519    COMMAND ${CMAKE_COMMAND}
520      -G${CMAKE_GENERATOR}
521        ${CMAKE_ARGS}
522      -DFORCED_CONF_FILE:FILEPATH=${dotconfigsysbuild}
523      -B${BINARY_DIR}
524      -S${SOURCE_DIR}
525    RESULT_VARIABLE   return_val
526    WORKING_DIRECTORY ${BINARY_DIR}
527  )
528
529  if(return_val)
530    message(FATAL_ERROR
531            "CMake configure failed for Zephyr project: ${ZCMAKE_APPLICATION}\n"
532            "Location: ${SOURCE_DIR}"
533    )
534  endif()
535  load_cache(IMAGE ${ZCMAKE_APPLICATION} BINARY_DIR ${BINARY_DIR})
536  import_kconfig(CONFIG_ ${BINARY_DIR}/zephyr/.config TARGET ${ZCMAKE_APPLICATION})
537  zephyr_dt_import(EDT_PICKLE_FILE ${BINARY_DIR}/zephyr/edt.pickle TARGET ${ZCMAKE_APPLICATION})
538
539  # This custom target informs CMake how the BYPRODUCTS are generated if a target
540  # depends directly on the BYPRODUCT instead of depending on the image target.
541  get_target_property(${ZCMAKE_APPLICATION}_byproducts ${ZCMAKE_APPLICATION}_cache EXTRA_BYPRODUCTS)
542  add_custom_target(${ZCMAKE_APPLICATION}_extra_byproducts
543                    COMMAND ${CMAKE_COMMAND} -E true
544                    BYPRODUCTS ${${ZCMAKE_APPLICATION}_byproducts}
545                    DEPENDS ${ZCMAKE_APPLICATION}
546  )
547endfunction()
548
549# Usage:
550#   sysbuild_module_call(<hook> MODULES <modules> IMAGES <images> [IMAGE <image>] [EXTRA_ARGS <arguments>])
551#
552# This function invokes the sysbuild hook provided as <hook> for <modules>.
553#
554# `IMAGES` contains the list of images to the hook, if `IMAGE` is passed, this will be provided
555# to the hook.
556#
557# `EXTRA_ARGS` can be used to pass extra arguments to the hook.
558#
559# Valid <hook> values:
560# PRE_CMAKE       : Invoke pre-CMake call for modules before CMake configure is invoked for images
561# POST_CMAKE      : Invoke post-CMake call for modules after CMake configure has been invoked for
562# PRE_IMAGE_CMAKE : Invoke pre-CMake call for modules before CMake configure is invoked for each
563#                   image
564# POST_IMAGE_CMAKE: Invoke post-CMake call for modules after CMake configure has been invoked for
565#                   each image
566# PRE_DOMAINS     : Invoke pre-domains call for modules before creating domains yaml
567# POST_DOMAINS    : Invoke post-domains call for modules after creation of domains yaml
568#
569# For the `PRE_IMAGE_CMAKE` and `POST_IMAGE_CMAKE` hooks, `IMAGE` is provided
570#
571function(sysbuild_module_call)
572  set(options "PRE_CMAKE;POST_CMAKE;PRE_IMAGE_CMAKE;POST_IMAGE_CMAKE;PRE_DOMAINS;POST_DOMAINS")
573  set(multi_args "MODULES;IMAGES;IMAGE;EXTRA_ARGS")
574  cmake_parse_arguments(SMC "${options}" "${test_args}" "${multi_args}" ${ARGN})
575
576  zephyr_check_flags_required("sysbuild_module_call" SMC ${options})
577  zephyr_check_flags_exclusive("sysbuild_module_call" SMC ${options})
578
579  if(NOT DEFINED SMC_IMAGES)
580    message(FATAL_ERROR
581            "sysbuild_module_call(...) missing required IMAGES option")
582  endif()
583
584  if(DEFINED SMC_IMAGE)
585    set(IMAGE_ARG IMAGE ${SMC_IMAGE})
586  elseif(SMC_PRE_IMAGE_CMAKE)
587    message(FATAL_ERROR
588            "sysbuild_module_call(PRE_IMAGE_CMAKE ...) missing required IMAGE option")
589  elseif(SMC_POST_IMAGE_CMAKE)
590    message(FATAL_ERROR
591            "sysbuild_module_call(POST_IMAGE_CMAKE ...) missing required IMAGE option")
592  endif()
593
594  foreach(call ${options})
595    if(SMC_${call})
596      foreach(module ${SMC_MODULES})
597        if(COMMAND ${module}_${call})
598          cmake_language(CALL ${module}_${call} IMAGES ${SMC_IMAGES} ${IMAGE_ARG} ${SMC_EXTRA_ARGS})
599        endif()
600      endforeach()
601    endif()
602  endforeach()
603endfunction()
604
605# Usage:
606#   sysbuild_cache_set(VAR <variable> [APPEND [REMOVE_DUPLICATES]] <value>)
607#
608# This function will set the specified value of the sysbuild cache variable in
609# the CMakeCache.txt file which can then be accessed by images.
610# `VAR` specifies the variable name to set/update.
611#
612# The result will be returned in `<variable>`.
613#
614# Example use:
615#   sysbuild_cache_set(VAR ATTRIBUTES APPEND REMOVE_DUPLICATES battery)
616#     Will add the item `battery` to the `ATTRIBUTES` variable as a new element
617#     in the list in the CMakeCache and remove any duplicates from the list.
618#
619# <variable>:        Name of variable in CMake cache.
620# APPEND:            If specified then will append the supplied data to the
621#                    existing value as a list.
622# REMOVE_DUPLICATES: If specified then remove duplicate entries contained
623#                    within the list before saving to the cache.
624# <value>:           Value to set/update.
625function(sysbuild_cache_set)
626  cmake_parse_arguments(VARS "APPEND;REMOVE_DUPLICATES" "VAR" "" ${ARGN})
627
628  zephyr_check_arguments_required(sysbuild_cache_set VARS VAR)
629
630  if(NOT VARS_UNPARSED_ARGUMENTS AND VARS_APPEND)
631    # Nothing to append so do nothing
632    return()
633  elseif(VARS_REMOVE_DUPLICATES AND NOT VARS_APPEND)
634    message(FATAL_ERROR
635            "sysbuild_cache_set(VAR <var> APPEND REMOVE_DUPLICATES ...) missing required APPEND option")
636  endif()
637
638  get_property(var_type CACHE ${VARS_VAR} PROPERTY TYPE)
639  get_property(var_help CACHE ${VARS_VAR} PROPERTY HELPSTRING)
640
641  # If the variable type is not set, use UNINITIALIZED which will not apply any
642  # specific formatting.
643  if(NOT var_type)
644    set(var_type "UNINITIALIZED")
645  endif()
646
647  if(VARS_APPEND)
648    set(var_new "$CACHE{${VARS_VAR}}")
649
650    # Search for these exact items in the existing value and prevent adding
651    # them if they are already present which avoids issues with double addition
652    # when cmake is reran.
653    if("${VARS_UNPARSED_ARGUMENTS}" IN_LIST var_new)
654      return()
655    endif()
656
657    list(APPEND var_new "${VARS_UNPARSED_ARGUMENTS}")
658
659    if(VARS_REMOVE_DUPLICATES)
660      list(REMOVE_DUPLICATES var_new)
661    endif()
662  else()
663    set(var_new "${VARS_UNPARSED_ARGUMENTS}")
664  endif()
665
666  set(${VARS_VAR} "${var_new}" CACHE "${var_type}" "${var_help}" FORCE)
667endfunction()
668
669function(set_config_bool image setting value)
670  if(${value})
671    set_property(TARGET ${image} APPEND_STRING PROPERTY CONFIG "${setting}=y\n")
672  else()
673    set_property(TARGET ${image} APPEND_STRING PROPERTY CONFIG "${setting}=n\n")
674  endif()
675endfunction()
676
677function(set_config_string image setting value)
678  set_property(TARGET ${image} APPEND_STRING PROPERTY CONFIG "${setting}=\"${value}\"\n")
679endfunction()
680
681function(set_config_int image setting value)
682  set_property(TARGET ${image} APPEND_STRING PROPERTY CONFIG "${setting}=${value}\n")
683endfunction()
684
685# Usage:
686#   sysbuild_add_subdirectory(<source_dir> [<binary_dir>])
687#
688# This function extends the standard add_subdirectory() command with additional,
689# recursive processing of the sysbuild images added via <source_dir>.
690#
691# After exiting <source_dir>, this function will take every image added so far,
692# and include() its sysbuild.cmake file (if found). If more images get added at
693# this stage, their sysbuild.cmake files will be included as well, and so on.
694# This continues until all expected images have been added, before returning.
695#
696function(sysbuild_add_subdirectory source_dir)
697  if(ARGC GREATER 2)
698    message(FATAL_ERROR
699      "sysbuild_add_subdirectory(...) called with incorrect number of arguments"
700      " (expected at most 2, got ${ARGC})"
701    )
702  endif()
703  set(binary_dir ${ARGN})
704
705  # Update SYSBUILD_CURRENT_SOURCE_DIR in this scope, to support nesting
706  # of sysbuild_add_subdirectory() and even regular add_subdirectory().
707  cmake_path(ABSOLUTE_PATH source_dir NORMALIZE OUTPUT_VARIABLE SYSBUILD_CURRENT_SOURCE_DIR)
708  add_subdirectory(${source_dir} ${binary_dir})
709
710  while(TRUE)
711    get_property(added_images DIRECTORY "${SYSBUILD_CURRENT_SOURCE_DIR}" PROPERTY sysbuild_images)
712    if(NOT added_images)
713      break()
714    endif()
715    set_property(DIRECTORY "${SYSBUILD_CURRENT_SOURCE_DIR}" PROPERTY sysbuild_images "")
716
717    foreach(image ${added_images})
718      ExternalProject_Get_property(${image} SOURCE_DIR)
719      include(${SOURCE_DIR}/sysbuild.cmake OPTIONAL)
720    endforeach()
721  endwhile()
722endfunction()
723
724# Usage:
725#   sysbuild_add_dependencies(<CONFIGURE | FLASH> <image> [<image-dependency> ...])
726#
727# This function makes an image depend on other images in the configuration or
728# flashing order. Each image named "<image-dependency>" will be ordered before
729# the image named "<image>".
730#
731# CONFIGURE: Add CMake configuration dependencies. This will determine the order
732#            in which `ExternalZephyrProject_Cmake()` will be called.
733# FLASH:     Add flashing dependencies. This will determine the order in which
734#            all images will appear in `domains.yaml`.
735#
736function(sysbuild_add_dependencies dependency_type image)
737  set(valid_dependency_types CONFIGURE FLASH)
738  if(NOT dependency_type IN_LIST valid_dependency_types)
739    list(JOIN valid_dependency_types ", " valid_dependency_types)
740    message(FATAL_ERROR "sysbuild_add_dependencies(...) dependency type "
741                        "${dependency_type} must be one of the following: "
742                        "${valid_dependency_types}"
743    )
744  endif()
745
746  if(NOT TARGET ${image})
747    message(FATAL_ERROR
748      "${image} does not exist. Remember to call "
749      "ExternalZephyrProject_Add(APPLICATION ${image} ...) first."
750    )
751  endif()
752
753  get_target_property(image_is_build_only ${image} BUILD_ONLY)
754  if(image_is_build_only AND dependency_type STREQUAL "FLASH")
755    message(FATAL_ERROR
756      "sysbuild_add_dependencies(...) cannot add FLASH dependencies to "
757      "BUILD_ONLY image ${image}."
758    )
759  endif()
760
761  set(property_name ${dependency_type}_DEPENDS)
762  set_property(TARGET ${image} APPEND PROPERTY ${property_name} ${ARGN})
763endfunction()
764
765# Usage:
766#   sysbuild_images_order(<variable> <CONFIGURE | FLASH> IMAGES <images>)
767#
768# This function will sort the provided `<images>` to satisfy the dependencies
769# specified using `sysbuild_add_dependencies()`. The result will be returned in
770# `<variable>`.
771#
772function(sysbuild_images_order variable dependency_type)
773  cmake_parse_arguments(SIS "" "" "IMAGES" ${ARGN})
774  zephyr_check_arguments_required_all("sysbuild_images_order" SIS IMAGES)
775
776  set(valid_dependency_types CONFIGURE FLASH)
777  if(NOT dependency_type IN_LIST valid_dependency_types)
778    list(JOIN valid_dependency_types ", " valid_dependency_types)
779    message(FATAL_ERROR "sysbuild_images_order(...) dependency type "
780                        "${dependency_type} must be one of the following: "
781                        "${valid_dependency_types}"
782    )
783  endif()
784
785  set(property_name ${dependency_type}_DEPENDS)
786  topological_sort(TARGETS ${SIS_IMAGES} PROPERTY_NAME ${property_name} RESULT sorted)
787  set(${variable} ${sorted} PARENT_SCOPE)
788endfunction()
789