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)
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#   ExternalZephyrProject_Add(APPLICATION <name>
112#                             SOURCE_DIR <dir>
113#                             [BOARD <board> [BOARD_REVISION <revision>]]
114#                             [APP_TYPE <MAIN|BOOTLOADER>]
115#   )
116#
117# This function includes a Zephyr based build system into the multiimage
118# build system
119#
120# APPLICATION: <name>:       Name of the application, name will also be used for build
121#                            folder of the application
122# SOURCE_DIR <dir>:          Source directory of the application
123# BOARD <board>:             Use <board> for application build instead user defined BOARD.
124# BOARD_REVISION <revision>: Use <revision> of <board> for application (only valid if
125#                            <board> is also supplied).
126# APP_TYPE <MAIN|BOOTLOADER>: Application type.
127#                             MAIN indicates this application is the main application
128#                             and where user defined settings should be passed on as-is
129#                             except for multi image build flags.
130#                             For example, -DCONF_FILES=<files> will be passed on to the
131#                             MAIN_APP unmodified.
132#                             BOOTLOADER indicates this app is a bootloader
133# BUILD_ONLY <bool>:          Mark the application as build-only. If <bool> evaluates to
134#                             true, then this application will be excluded from flashing
135#                             and debugging.
136#
137function(ExternalZephyrProject_Add)
138  set(app_types MAIN BOOTLOADER)
139  cmake_parse_arguments(ZBUILD "" "APPLICATION;BOARD;BOARD_REVISION;SOURCE_DIR;APP_TYPE;BUILD_ONLY" "" ${ARGN})
140
141  if(ZBUILD_UNPARSED_ARGUMENTS)
142    message(FATAL_ERROR
143      "ExternalZephyrProject_Add(${ARGV0} <val> ...) given unknown arguments:"
144      " ${ZBUILD_UNPARSED_ARGUMENTS}"
145    )
146  endif()
147
148  if(TARGET ${ZBUILD_APPLICATION})
149    message(FATAL_ERROR
150      "ExternalZephyrProject_Add(APPLICATION ${ZBUILD_APPLICATION} ...) "
151      "already exists. Application names must be unique."
152    )
153  endif()
154
155  if(DEFINED ZBUILD_APP_TYPE)
156    if(NOT ZBUILD_APP_TYPE IN_LIST app_types)
157      message(FATAL_ERROR
158        "ExternalZephyrProject_Add(APP_TYPE <val> ...) given unknown type: ${ZBUILD_APP_TYPE}\n"
159        "Valid types are: ${app_types}"
160      )
161    endif()
162
163  endif()
164
165  if(NOT DEFINED SYSBUILD_CURRENT_SOURCE_DIR)
166    message(FATAL_ERROR
167      "ExternalZephyrProject_Add(${ARGV0} <val> ...) must not be called outside of"
168      " sysbuild_add_subdirectory(). SYSBUILD_CURRENT_SOURCE_DIR is undefined."
169    )
170  endif()
171  set_property(
172    DIRECTORY "${SYSBUILD_CURRENT_SOURCE_DIR}"
173    APPEND PROPERTY sysbuild_images ${ZBUILD_APPLICATION}
174  )
175  set_property(
176    GLOBAL
177    APPEND PROPERTY sysbuild_images ${ZBUILD_APPLICATION}
178  )
179
180  set(sysbuild_image_conf_dir ${APP_DIR}/sysbuild)
181  set(sysbuild_image_name_conf_dir ${APP_DIR}/sysbuild/${ZBUILD_APPLICATION})
182  # User defined `-D<image>_CONF_FILE=<file.conf>` takes precedence over anything else.
183  if (NOT ${ZBUILD_APPLICATION}_CONF_FILE)
184    if(EXISTS ${sysbuild_image_name_conf_dir})
185      set(${ZBUILD_APPLICATION}_APPLICATION_CONFIG_DIR ${sysbuild_image_name_conf_dir}
186          CACHE INTERNAL "Application configuration dir controlled by sysbuild"
187      )
188    endif()
189
190     # Check for sysbuild related configuration fragments.
191     # The contents of these are appended to the image existing configuration
192     # when user is not specifying custom fragments.
193    if(NOT "${CONF_FILE_BUILD_TYPE}" STREQUAL "")
194      set(sysbuild_image_conf_fragment ${sysbuild_image_conf_dir}/${ZBUILD_APPLICATION}_${CONF_FILE_BUILD_TYPE}.conf)
195    else()
196      set(sysbuild_image_conf_fragment ${sysbuild_image_conf_dir}/${ZBUILD_APPLICATION}.conf)
197    endif()
198
199    if (NOT (${ZBUILD_APPLICATION}_OVERLAY_CONFIG OR ${ZBUILD_APPLICATION}_EXTRA_CONF_FILE)
200        AND EXISTS ${sysbuild_image_conf_fragment}
201    )
202      set(${ZBUILD_APPLICATION}_EXTRA_CONF_FILE ${sysbuild_image_conf_fragment}
203          CACHE INTERNAL "Kconfig fragment defined by main application"
204      )
205    endif()
206
207    # Check for overlay named <ZBUILD_APPLICATION>.overlay.
208    set(sysbuild_image_dts_overlay ${sysbuild_image_conf_dir}/${ZBUILD_APPLICATION}.overlay)
209    if (NOT ${ZBUILD_APPLICATION}_DTC_OVERLAY_FILE AND EXISTS ${sysbuild_image_dts_overlay})
210      set(${ZBUILD_APPLICATION}_DTC_OVERLAY_FILE ${sysbuild_image_dts_overlay}
211          CACHE INTERNAL "devicetree overlay file defined by main application"
212      )
213    endif()
214  endif()
215
216  # Update ROOT variables with relative paths to use absolute paths based on
217  # the source application directory.
218  foreach(type MODULE_EXT BOARD SOC ARCH SCA)
219    if(DEFINED CACHE{${ZBUILD_APPLICATION}_${type}_ROOT} AND NOT IS_ABSOLUTE $CACHE{${ZBUILD_APPLICATION}_${type}_ROOT})
220      set(rel_path $CACHE{${ZBUILD_APPLICATION}_${type}_ROOT})
221      cmake_path(ABSOLUTE_PATH rel_path BASE_DIRECTORY "${ZBUILD_SOURCE_DIR}" NORMALIZE OUTPUT_VARIABLE abs_path)
222      set(${ZBUILD_APPLICATION}_${type}_ROOT ${abs_path} CACHE PATH "Sysbuild adjusted absolute path" FORCE)
223    endif()
224  endforeach()
225
226  # CMake variables which must be known by all Zephyr CMake build systems
227  # Those are settings which controls the build and must be known to CMake at
228  # invocation time, and thus cannot be passed though the sysbuild cache file.
229  set(
230    shared_cmake_variables_list
231    CMAKE_BUILD_TYPE
232    CMAKE_VERBOSE_MAKEFILE
233  )
234
235  set(sysbuild_cache_file ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION}_sysbuild_cache.txt)
236
237  set(shared_cmake_vars_argument)
238  foreach(shared_var ${shared_cmake_variables_list})
239    if(DEFINED CACHE{${ZBUILD_APPLICATION}_${shared_var}})
240      get_property(var_type  CACHE ${ZBUILD_APPLICATION}_${shared_var} PROPERTY TYPE)
241      list(APPEND shared_cmake_vars_argument
242           "-D${shared_var}:${var_type}=$CACHE{${ZBUILD_APPLICATION}_${shared_var}}"
243      )
244    elseif(DEFINED CACHE{${shared_var}})
245      get_property(var_type  CACHE ${shared_var} PROPERTY TYPE)
246      list(APPEND shared_cmake_vars_argument
247           "-D${shared_var}:${var_type}=$CACHE{${shared_var}}"
248      )
249    endif()
250  endforeach()
251
252  foreach(kconfig_target
253      menuconfig
254      hardenconfig
255      guiconfig
256      ${EXTRA_KCONFIG_TARGETS}
257      )
258
259    if(NOT ZBUILD_APP_TYPE STREQUAL "MAIN")
260      set(image_prefix "${ZBUILD_APPLICATION}_")
261    endif()
262
263    add_custom_target(${image_prefix}${kconfig_target}
264      ${CMAKE_MAKE_PROGRAM} ${kconfig_target}
265      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION}
266      USES_TERMINAL
267      )
268  endforeach()
269  include(ExternalProject)
270  set(application_binary_dir ${CMAKE_BINARY_DIR}/${ZBUILD_APPLICATION})
271  ExternalProject_Add(
272    ${ZBUILD_APPLICATION}
273    SOURCE_DIR ${ZBUILD_SOURCE_DIR}
274    BINARY_DIR ${application_binary_dir}
275    CONFIGURE_COMMAND ""
276    CMAKE_ARGS -DSYSBUILD:BOOL=True
277               -DSYSBUILD_CACHE:FILEPATH=${sysbuild_cache_file}
278               ${shared_cmake_vars_argument}
279    BUILD_COMMAND ${CMAKE_COMMAND} --build .
280    INSTALL_COMMAND ""
281    BUILD_ALWAYS True
282    USES_TERMINAL_BUILD True
283  )
284  set_property(TARGET ${ZBUILD_APPLICATION} PROPERTY APP_TYPE ${ZBUILD_APP_TYPE})
285  set_property(TARGET ${ZBUILD_APPLICATION} PROPERTY CONFIG
286               "# sysbuild controlled configuration settings\n"
287  )
288  set_target_properties(${ZBUILD_APPLICATION} PROPERTIES CACHE_FILE ${sysbuild_cache_file})
289  set_target_properties(${ZBUILD_APPLICATION} PROPERTIES KCONFIG_BINARY_DIR
290                        ${application_binary_dir}/Kconfig
291  )
292  if("${ZBUILD_APP_TYPE}" STREQUAL "MAIN")
293    set_target_properties(${ZBUILD_APPLICATION} PROPERTIES MAIN_APP True)
294  endif()
295
296  if(DEFINED ZBUILD_APP_TYPE)
297    set(image_default "${CMAKE_SOURCE_DIR}/image_configurations/${ZBUILD_APP_TYPE}_image_default.cmake")
298    set_target_properties(${ZBUILD_APPLICATION} PROPERTIES IMAGE_CONF_SCRIPT ${image_default})
299  endif()
300
301  if(DEFINED ZBUILD_BOARD)
302    # Only set image specific board if provided.
303    # The sysbuild BOARD is exported through sysbuild cache, and will be used
304    # unless <image>_BOARD is defined.
305    if(DEFINED ZBUILD_BOARD_REVISION)
306      # Use provided board revision
307      set_target_properties(${ZBUILD_APPLICATION} PROPERTIES BOARD ${ZBUILD_BOARD}@${ZBUILD_BOARD_REVISION})
308    else()
309      set_target_properties(${ZBUILD_APPLICATION} PROPERTIES BOARD ${ZBUILD_BOARD})
310    endif()
311  elseif(DEFINED ZBUILD_BOARD_REVISION)
312    message(FATAL_ERROR
313      "ExternalZephyrProject_Add(... BOARD_REVISION ${ZBUILD_BOARD_REVISION})"
314      " requires BOARD."
315    )
316  endif()
317
318  if(DEFINED ZBUILD_BUILD_ONLY)
319    set_target_properties(${ZBUILD_APPLICATION} PROPERTIES BUILD_ONLY ${ZBUILD_BUILD_ONLY})
320  endif()
321endfunction()
322
323# Usage:
324#   ExternalZephyrProject_Cmake(APPLICATION <name>)
325#
326# This function invokes the CMake configure step on an external Zephyr project
327# which has been added at an earlier stage using `ExternalZephyrProject_Add()`
328#
329# If the application is not due to ExternalZephyrProject_Add() being called,
330# then an error is raised.
331#
332# The image output files are added as target properties on the image target as:
333# ELF_OUT: property specifying the generated elf file.
334# BIN_OUT: property specifying the generated bin file.
335# HEX_OUT: property specifying the generated hex file.
336# S19_OUT: property specifying the generated s19 file.
337# UF2_OUT: property specifying the generated uf2 file.
338# EXE_OUT: property specifying the generated exe file.
339#
340# the property is only set if the image is configured to generate the output
341# format. Elf files are always created.
342#
343# APPLICATION: <name>: Name of the application.
344#
345function(ExternalZephyrProject_Cmake)
346  cmake_parse_arguments(ZCMAKE "" "APPLICATION" "" ${ARGN})
347
348  if(ZBUILD_UNPARSED_ARGUMENTS)
349    message(FATAL_ERROR
350      "ExternalZephyrProject_Cmake(${ARGV0} <val> ...) given unknown arguments:"
351      " ${ZBUILD_UNPARSED_ARGUMENTS}"
352    )
353  endif()
354
355  if(NOT DEFINED ZCMAKE_APPLICATION)
356    message(FATAL_ERROR "Missing required argument: APPLICATION")
357  endif()
358
359  if(NOT TARGET ${ZCMAKE_APPLICATION})
360    message(FATAL_ERROR
361      "${ZCMAKE_APPLICATION} does not exists. Remember to call "
362      "ExternalZephyrProject_Add(APPLICATION ${ZCMAKE_APPLICATION} ...) first."
363    )
364  endif()
365
366  set(image_banner "* Running CMake for ${ZCMAKE_APPLICATION} *")
367  string(LENGTH "${image_banner}" image_banner_width)
368  string(REPEAT "*" ${image_banner_width} image_banner_header)
369  message(STATUS "\n   ${image_banner_header}\n"
370                 "   ${image_banner}\n"
371                 "   ${image_banner_header}\n"
372  )
373
374  ExternalProject_Get_Property(${ZCMAKE_APPLICATION} SOURCE_DIR BINARY_DIR CMAKE_ARGS)
375  get_target_property(${ZCMAKE_APPLICATION}_CACHE_FILE ${ZCMAKE_APPLICATION} CACHE_FILE)
376  get_target_property(${ZCMAKE_APPLICATION}_BOARD      ${ZCMAKE_APPLICATION} BOARD)
377  get_target_property(${ZCMAKE_APPLICATION}_MAIN_APP   ${ZCMAKE_APPLICATION} MAIN_APP)
378
379  get_property(${ZCMAKE_APPLICATION}_CONF_SCRIPT TARGET ${ZCMAKE_APPLICATION}
380               PROPERTY IMAGE_CONF_SCRIPT
381  )
382
383  # Update ROOT variables with relative paths to use absolute paths based on
384  # the source application directory.
385  foreach(type MODULE_EXT BOARD SOC ARCH SCA)
386    if(DEFINED CACHE{${type}_ROOT} AND NOT IS_ABSOLUTE $CACHE{${type}_ROOT})
387      set(rel_path $CACHE{${type}_ROOT})
388      cmake_path(ABSOLUTE_PATH rel_path BASE_DIRECTORY "${APP_DIR}" NORMALIZE OUTPUT_VARIABLE abs_path)
389      set(${type}_ROOT ${abs_path} CACHE PATH "Sysbuild adjusted absolute path" FORCE)
390    endif()
391  endforeach()
392
393  get_cmake_property(sysbuild_cache CACHE_VARIABLES)
394  foreach(var_name ${sysbuild_cache})
395    if(NOT "${var_name}" MATCHES "^(CMAKE_.*|BOARD)$")
396      # Perform a dummy read to prevent a false warning about unused variables
397      # being emitted due to a cmake bug: https://gitlab.kitware.com/cmake/cmake/-/issues/24555
398      set(unused_tmp_var ${${var_name}})
399
400      # We don't want to pass internal CMake variables.
401      # Required CMake variable to be passed, like CMAKE_BUILD_TYPE must be
402      # passed using `-D` on command invocation.
403      get_property(var_type CACHE ${var_name} PROPERTY TYPE)
404      set(cache_entry "${var_name}:${var_type}=$CACHE{${var_name}}")
405      string(REPLACE ";" "\;" cache_entry "${cache_entry}")
406      list(APPEND sysbuild_cache_strings "${cache_entry}\n")
407    endif()
408  endforeach()
409  if(DEFINED BOARD_REVISION)
410    list(APPEND sysbuild_cache_strings "BOARD:STRING=${BOARD}@${BOARD_REVISION}\n")
411  else()
412    list(APPEND sysbuild_cache_strings "BOARD:STRING=${BOARD}\n")
413  endif()
414  list(APPEND sysbuild_cache_strings "SYSBUILD_NAME:STRING=${ZCMAKE_APPLICATION}\n")
415
416  if(${ZCMAKE_APPLICATION}_MAIN_APP)
417    list(APPEND sysbuild_cache_strings "SYSBUILD_MAIN_APP:BOOL=True\n")
418  endif()
419
420  if(${ZCMAKE_APPLICATION}_BOARD AND NOT DEFINED CACHE{${ZCMAKE_APPLICATION}_BOARD})
421    # Only set image specific board if provided.
422    # The sysbuild BOARD is exported through sysbuild cache, and will be used
423    # unless <image>_BOARD is defined.
424    list(APPEND sysbuild_cache_strings
425         "${ZCMAKE_APPLICATION}_BOARD:STRING=${${ZCMAKE_APPLICATION}_BOARD}\n"
426    )
427  endif()
428
429  file(WRITE ${${ZCMAKE_APPLICATION}_CACHE_FILE}.tmp ${sysbuild_cache_strings})
430  zephyr_file_copy(${${ZCMAKE_APPLICATION}_CACHE_FILE}.tmp
431                   ${${ZCMAKE_APPLICATION}_CACHE_FILE} ONLY_IF_DIFFERENT
432  )
433
434  foreach(script ${${ZCMAKE_APPLICATION}_CONF_SCRIPT})
435    include(${script})
436  endforeach()
437
438  set(dotconfigsysbuild ${BINARY_DIR}/zephyr/.config.sysbuild)
439  get_target_property(config_content ${ZCMAKE_APPLICATION} CONFIG)
440  string(CONFIGURE "${config_content}" config_content)
441  file(WRITE ${dotconfigsysbuild} ${config_content})
442
443  execute_process(
444    COMMAND ${CMAKE_COMMAND}
445      -G${CMAKE_GENERATOR}
446        ${CMAKE_ARGS}
447      -DFORCED_CONF_FILE:FILEPATH=${dotconfigsysbuild}
448      -B${BINARY_DIR}
449      -S${SOURCE_DIR}
450    RESULT_VARIABLE   return_val
451    WORKING_DIRECTORY ${BINARY_DIR}
452  )
453
454  if(return_val)
455    message(FATAL_ERROR
456            "CMake configure failed for Zephyr project: ${ZCMAKE_APPLICATION}\n"
457            "Location: ${SOURCE_DIR}"
458    )
459  endif()
460  load_cache(IMAGE ${ZCMAKE_APPLICATION} BINARY_DIR ${BINARY_DIR})
461  import_kconfig(CONFIG_ ${BINARY_DIR}/zephyr/.config TARGET ${ZCMAKE_APPLICATION})
462
463  # This custom target informs CMake how the BYPRODUCTS are generated if a target
464  # depends directly on the BYPRODUCT instead of depending on the image target.
465  get_target_property(${ZCMAKE_APPLICATION}_byproducts ${ZCMAKE_APPLICATION}_cache EXTRA_BYPRODUCTS)
466  add_custom_target(${ZCMAKE_APPLICATION}_extra_byproducts
467                    COMMAND ${CMAKE_COMMAND} -E true
468                    BYPRODUCTS ${${ZCMAKE_APPLICATION}_byproducts}
469                    DEPENDS ${ZCMAKE_APPLICATION}
470  )
471endfunction()
472
473# Usage:
474#   sysbuild_module_call(<hook> MODULES <modules> [IMAGES <images>] [EXTRA_ARGS <arguments>])
475#
476# This function invokes the sysbuild hook provided as <hook> for <modules>.
477#
478# If `IMAGES` is passed, then the provided list of of images will be passed to
479# the hook.
480#
481# `EXTRA_ARGS` can be used to pass extra arguments to the hook.
482#
483# Valid <hook> values:
484# PRE_CMAKE   : Invoke pre-CMake call for modules before CMake configure is invoked for images
485# POST_CMAKE  : Invoke post-CMake call for modules after CMake configure has been invoked for images
486# PRE_DOMAINS : Invoke pre-domains call for modules before creating domains yaml.
487# POST_DOMAINS: Invoke post-domains call for modules after creation of domains yaml.
488#
489function(sysbuild_module_call)
490  set(options "PRE_CMAKE;POST_CMAKE;PRE_DOMAINS;POST_DOMAINS")
491  set(multi_args "MODULES;IMAGES;EXTRA_ARGS")
492  cmake_parse_arguments(SMC "${options}" "${test_args}" "${multi_args}" ${ARGN})
493
494  zephyr_check_flags_required("sysbuild_module_call" SMC ${options})
495  zephyr_check_flags_exclusive("sysbuild_module_call" SMC ${options})
496
497  foreach(call ${options})
498    if(SMC_${call})
499      foreach(module ${SMC_MODULES})
500        if(COMMAND ${module}_${call})
501          cmake_language(CALL ${module}_${call} IMAGES ${SMC_IMAGES} ${SMC_EXTRA_ARGS})
502        endif()
503      endforeach()
504    endif()
505  endforeach()
506endfunction()
507
508# Usage:
509#   sysbuild_cache_set(VAR <variable> [APPEND [REMOVE_DUPLICATES]] <value>)
510#
511# This function will set the specified value of the sysbuild cache variable in
512# the CMakeCache.txt file which can then be accessed by images.
513# `VAR` specifies the variable name to set/update.
514#
515# The result will be returned in `<variable>`.
516#
517# Example use:
518#   sysbuild_cache_set(VAR ATTRIBUTES APPEND REMOVE_DUPLICATES battery)
519#     Will add the item `battery` to the `ATTRIBUTES` variable as a new element
520#     in the list in the CMakeCache and remove any duplicates from the list.
521#
522# <variable>:        Name of variable in CMake cache.
523# APPEND:            If specified then will append the supplied data to the
524#                    existing value as a list.
525# REMOVE_DUPLICATES: If specified then remove duplicate entries contained
526#                    within the list before saving to the cache.
527# <value>:           Value to set/update.
528function(sysbuild_cache_set)
529  cmake_parse_arguments(VARS "APPEND;REMOVE_DUPLICATES" "VAR" "" ${ARGN})
530
531  zephyr_check_arguments_required(sysbuild_cache_set VARS VAR)
532
533  if(NOT VARS_UNPARSED_ARGUMENTS AND VARS_APPEND)
534    # Nothing to append so do nothing
535    return()
536  elseif(VARS_REMOVE_DUPLICATES AND NOT VARS_APPEND)
537    message(FATAL_ERROR
538            "sysbuild_set(VAR <var> APPEND REMOVE_DUPLICATES ...) missing required APPEND option")
539  endif()
540
541  get_property(var_type CACHE ${VARS_VAR} PROPERTY TYPE)
542  get_property(var_help CACHE ${VARS_VAR} PROPERTY HELPSTRING)
543
544  # If the variable type is not set, use UNINITIALIZED which will not apply any
545  # specific formatting.
546  if(NOT var_type)
547    set(var_type "UNINITIALIZED")
548  endif()
549
550  if(VARS_APPEND)
551    set(var_new "$CACHE{${VARS_VAR}}")
552
553    # Search for these exact items in the existing value and prevent adding
554    # them if they are already present which avoids issues with double addition
555    # when cmake is reran.
556    string(FIND "$CACHE{${VARS_VAR}}" "${VARS_UNPARSED_ARGUMENTS}" index)
557
558    if(NOT ${index} EQUAL -1)
559      return()
560    endif()
561
562    list(APPEND var_new "${VARS_UNPARSED_ARGUMENTS}")
563
564    if(VARS_REMOVE_DUPLICATES)
565      list(REMOVE_DUPLICATES var_new)
566    endif()
567  else()
568    set(var_new "${VARS_UNPARSED_ARGUMENTS}")
569  endif()
570
571  set(${VARS_VAR} "${var_new}" CACHE "${var_type}" "${var_help}" FORCE)
572endfunction()
573
574function(set_config_bool image setting value)
575  if(${value})
576    set_property(TARGET ${image} APPEND_STRING PROPERTY CONFIG "${setting}=y\n")
577  else()
578    set_property(TARGET ${image} APPEND_STRING PROPERTY CONFIG "${setting}=n\n")
579  endif()
580endfunction()
581
582function(set_config_string image setting value)
583  set_property(TARGET ${image} APPEND_STRING PROPERTY CONFIG "${setting}=\"${value}\"\n")
584endfunction()
585
586# Usage:
587#   sysbuild_add_subdirectory(<source_dir> [<binary_dir>])
588#
589# This function extends the standard add_subdirectory() command with additional,
590# recursive processing of the sysbuild images added via <source_dir>.
591#
592# After exiting <source_dir>, this function will take every image added so far,
593# and include() its sysbuild.cmake file (if found). If more images get added at
594# this stage, their sysbuild.cmake files will be included as well, and so on.
595# This continues until all expected images have been added, before returning.
596#
597function(sysbuild_add_subdirectory source_dir)
598  if(ARGC GREATER 2)
599    message(FATAL_ERROR
600      "sysbuild_add_subdirectory(...) called with incorrect number of arguments"
601      " (expected at most 2, got ${ARGC})"
602    )
603  endif()
604  set(binary_dir ${ARGV1})
605
606  # Update SYSBUILD_CURRENT_SOURCE_DIR in this scope, to support nesting
607  # of sysbuild_add_subdirectory() and even regular add_subdirectory().
608  cmake_path(ABSOLUTE_PATH source_dir NORMALIZE OUTPUT_VARIABLE SYSBUILD_CURRENT_SOURCE_DIR)
609  add_subdirectory(${source_dir} ${binary_dir})
610
611  while(TRUE)
612    get_property(added_images DIRECTORY "${SYSBUILD_CURRENT_SOURCE_DIR}" PROPERTY sysbuild_images)
613    if(NOT added_images)
614      break()
615    endif()
616    set_property(DIRECTORY "${SYSBUILD_CURRENT_SOURCE_DIR}" PROPERTY sysbuild_images "")
617
618    foreach(image ${added_images})
619      ExternalProject_Get_property(${image} SOURCE_DIR)
620      include(${SOURCE_DIR}/sysbuild.cmake OPTIONAL)
621    endforeach()
622  endwhile()
623endfunction()
624
625# Usage:
626#   sysbuild_add_dependencies(<CONFIGURE | FLASH> <image> [<image-dependency> ...])
627#
628# This function makes an image depend on other images in the configuration or
629# flashing order. Each image named "<image-dependency>" will be ordered before
630# the image named "<image>".
631#
632# CONFIGURE: Add CMake configuration dependencies. This will determine the order
633#            in which `ExternalZephyrProject_Cmake()` will be called.
634# FLASH:     Add flashing dependencies. This will determine the order in which
635#            all images will appear in `domains.yaml`.
636#
637function(sysbuild_add_dependencies dependency_type image)
638  set(valid_dependency_types CONFIGURE FLASH)
639  if(NOT dependency_type IN_LIST valid_dependency_types)
640    list(JOIN valid_dependency_types ", " valid_dependency_types)
641    message(FATAL_ERROR "sysbuild_add_dependencies(...) dependency type "
642                        "${dependency_type} must be one of the following: "
643                        "${valid_dependency_types}"
644    )
645  endif()
646
647  if(NOT TARGET ${image})
648    message(FATAL_ERROR
649      "${image} does not exist. Remember to call "
650      "ExternalZephyrProject_Add(APPLICATION ${image} ...) first."
651    )
652  endif()
653
654  get_target_property(image_is_build_only ${image} BUILD_ONLY)
655  if(image_is_build_only AND dependency_type STREQUAL "FLASH")
656    message(FATAL_ERROR
657      "sysbuild_add_dependencies(...) cannot add FLASH dependencies to "
658      "BUILD_ONLY image ${image}."
659    )
660  endif()
661
662  set(property_name ${dependency_type}_DEPENDS)
663  set_property(TARGET ${image} APPEND PROPERTY ${property_name} ${ARGN})
664endfunction()
665
666# Usage:
667#   sysbuild_images_order(<variable> <CONFIGURE | FLASH> IMAGES <images>)
668#
669# This function will sort the provided `<images>` to satisfy the dependencies
670# specified using `sysbuild_add_dependencies()`. The result will be returned in
671# `<variable>`.
672#
673function(sysbuild_images_order variable dependency_type)
674  cmake_parse_arguments(SIS "" "" "IMAGES" ${ARGN})
675  zephyr_check_arguments_required_all("sysbuild_images_order" SIS IMAGES)
676
677  set(valid_dependency_types CONFIGURE FLASH)
678  if(NOT dependency_type IN_LIST valid_dependency_types)
679    list(JOIN valid_dependency_types ", " valid_dependency_types)
680    message(FATAL_ERROR "sysbuild_images_order(...) dependency type "
681                        "${dependency_type} must be one of the following: "
682                        "${valid_dependency_types}"
683    )
684  endif()
685
686  set(property_name ${dependency_type}_DEPENDS)
687  topological_sort(TARGETS ${SIS_IMAGES} PROPERTY_NAME ${property_name} RESULT sorted)
688  set(${variable} ${sorted} PARENT_SCOPE)
689endfunction()
690