| # SPDX-License-Identifier: Apache-2.0 |
| |
| include_guard(GLOBAL) |
| |
| include(user_cache) |
| |
| # Dependencies on CMake modules from the CMake distribution. |
| include(CheckCCompilerFlag) |
| include(CheckCXXCompilerFlag) |
| |
| ######################################################## |
| # Table of contents |
| ######################################################## |
| # 1. Zephyr-aware extensions |
| # 1.1. zephyr_* |
| # 1.2. zephyr_library_* |
| # 1.2.1 zephyr_interface_library_* |
| # 1.3. generate_inc_* |
| # 1.4. board_* |
| # 1.5. Misc. |
| # 2. Kconfig-aware extensions |
| # 2.1 Misc |
| # 3. CMake-generic extensions |
| # 3.1. *_ifdef |
| # 3.2. *_ifndef |
| # 3.3. *_option compiler compatibility checks |
| # 3.3.1 Toolchain integration |
| # 3.4. Debugging CMake |
| # 3.5. File system management |
| # 4. Devicetree extensions |
| # 4.1 dt_* |
| # 4.2. *_if_dt_node |
| # 4.3 zephyr_dt_* |
| # 5. Zephyr linker functions |
| # 5.1. zephyr_linker* |
| # 6 Function helper macros |
| # 7 Linkable loadable extensions (llext) |
| # 7.1 llext_* configuration functions |
| # 7.2 add_llext_* build control functions |
| |
| ######################################################## |
| # 1. Zephyr-aware extensions |
| ######################################################## |
| # 1.1. zephyr_* |
| # |
| # The following methods are for modifying the CMake library[0] called |
| # "zephyr". zephyr is a catch-all CMake library for source files that |
| # can be built purely with the include paths, defines, and other |
| # compiler flags that all zephyr source files use. |
| # [0] https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html |
| # |
| # Example usage: |
| # zephyr_sources( |
| # random_esp32.c |
| # utils.c |
| # ) |
| # |
| # Is short for: |
| # target_sources(zephyr PRIVATE |
| # ${CMAKE_CURRENT_SOURCE_DIR}/random_esp32.c |
| # ${CMAKE_CURRENT_SOURCE_DIR}/utils.c |
| # ) |
| # |
| # As a very high-level introduction here are two call graphs that are |
| # purposely minimalistic and incomplete. |
| # |
| # zephyr_library_cc_option() |
| # | |
| # v |
| # zephyr_library_compile_options() --> target_compile_options() |
| # |
| # |
| # zephyr_cc_option() ---> target_cc_option() |
| # | |
| # v |
| # zephyr_cc_option_fallback() ---> target_cc_option_fallback() |
| # | |
| # v |
| # zephyr_compile_options() ---> target_compile_options() |
| # |
| |
| |
| # https://cmake.org/cmake/help/latest/command/target_sources.html |
| function(zephyr_sources) |
| foreach(arg ${ARGV}) |
| if(IS_DIRECTORY ${arg}) |
| message(FATAL_ERROR "zephyr_sources() was called on a directory") |
| endif() |
| target_sources(zephyr PRIVATE ${arg}) |
| endforeach() |
| endfunction() |
| |
| # https://cmake.org/cmake/help/latest/command/target_include_directories.html |
| function(zephyr_include_directories) |
| target_include_directories(zephyr_interface INTERFACE ${ARGV}) |
| endfunction() |
| |
| # https://cmake.org/cmake/help/latest/command/target_include_directories.html |
| function(zephyr_system_include_directories) |
| target_include_directories(zephyr_interface SYSTEM INTERFACE ${ARGV}) |
| endfunction() |
| |
| # https://cmake.org/cmake/help/latest/command/target_compile_definitions.html |
| function(zephyr_compile_definitions) |
| target_compile_definitions(zephyr_interface INTERFACE ${ARGV}) |
| endfunction() |
| |
| # https://cmake.org/cmake/help/latest/command/target_compile_options.html |
| function(zephyr_compile_options) |
| target_compile_options(zephyr_interface INTERFACE ${ARGV}) |
| endfunction() |
| |
| # https://cmake.org/cmake/help/latest/command/target_link_libraries.html |
| function(zephyr_link_libraries) |
| target_link_libraries(zephyr_interface INTERFACE ${ARGV}) |
| endfunction() |
| |
| function(zephyr_libc_link_libraries) |
| set_property(TARGET zephyr_interface APPEND PROPERTY LIBC_LINK_LIBRARIES ${ARGV}) |
| endfunction() |
| |
| # See this file section 3.1. target_cc_option |
| function(zephyr_cc_option) |
| foreach(arg ${ARGV}) |
| target_cc_option(zephyr_interface INTERFACE ${arg}) |
| endforeach() |
| endfunction() |
| |
| function(zephyr_cc_option_fallback option1 option2) |
| target_cc_option_fallback(zephyr_interface INTERFACE ${option1} ${option2}) |
| endfunction() |
| |
| function(zephyr_ld_options) |
| target_ld_options(zephyr_interface INTERFACE ${ARGV}) |
| endfunction() |
| |
| # Getter functions for extracting build information from |
| # zephyr_interface. Returning lists, and strings is supported, as is |
| # requesting specific categories of build information (defines, |
| # includes, options). |
| # |
| # The naming convention follows: |
| # zephyr_get_${build_information}_for_lang${format}(lang x [STRIP_PREFIX]) |
| # Where |
| # the argument 'x' is written with the result |
| # and |
| # ${build_information} can be one of |
| # - include_directories # -I directories |
| # - system_include_directories # -isystem directories |
| # - compile_definitions # -D'efines |
| # - compile_options # misc. compiler flags |
| # and |
| # ${format} can be |
| # - the empty string '', signifying that it should be returned as a list |
| # - _as_string signifying that it should be returned as a string |
| # and |
| # ${lang} can be one of |
| # - C |
| # - CXX |
| # - ASM |
| # |
| # STRIP_PREFIX |
| # |
| # By default the result will be returned ready to be passed directly |
| # to a compiler, e.g. prefixed with -D, or -I, but it is possible to |
| # omit this prefix by specifying 'STRIP_PREFIX' . This option has no |
| # effect for 'compile_options'. |
| # |
| # e.g. |
| # zephyr_get_include_directories_for_lang(ASM x) |
| # writes "-Isome_dir;-Isome/other/dir" to x |
| |
| function(zephyr_get_include_directories_for_lang_as_string lang i) |
| zephyr_get_include_directories_for_lang(${lang} list_of_flags DELIMITER " " ${ARGN}) |
| |
| convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) |
| |
| set(${i} ${str_of_flags} PARENT_SCOPE) |
| endfunction() |
| |
| function(zephyr_get_system_include_directories_for_lang_as_string lang i) |
| zephyr_get_system_include_directories_for_lang(${lang} list_of_flags DELIMITER " " ${ARGN}) |
| |
| convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) |
| |
| set(${i} ${str_of_flags} PARENT_SCOPE) |
| endfunction() |
| |
| function(zephyr_get_compile_definitions_for_lang_as_string lang i) |
| zephyr_get_compile_definitions_for_lang(${lang} list_of_flags DELIMITER " " ${ARGN}) |
| |
| convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) |
| |
| set(${i} ${str_of_flags} PARENT_SCOPE) |
| endfunction() |
| |
| function(zephyr_get_compile_options_for_lang_as_string lang i) |
| zephyr_get_compile_options_for_lang(${lang} list_of_flags DELIMITER " ") |
| |
| convert_list_of_flags_to_string_of_flags(list_of_flags str_of_flags) |
| |
| set(${i} ${str_of_flags} PARENT_SCOPE) |
| endfunction() |
| |
| function(zephyr_get_include_directories_for_lang lang i) |
| zephyr_get_parse_args(args ${ARGN}) |
| get_property(flags TARGET zephyr_interface PROPERTY INTERFACE_INCLUDE_DIRECTORIES) |
| |
| process_flags(${lang} flags output_list) |
| string(REPLACE ";" "$<SEMICOLON>" genexp_output_list "${output_list}") |
| |
| if(NOT ARGN) |
| set(result_output_list "-I$<JOIN:${genexp_output_list},$<SEMICOLON>-I>") |
| elseif(args_STRIP_PREFIX) |
| # The list has no prefix, so don't add it. |
| set(result_output_list ${output_list}) |
| elseif(args_DELIMITER) |
| set(result_output_list "-I$<JOIN:${genexp_output_list},${args_DELIMITER}-I>") |
| endif() |
| set(${i} ${result_output_list} PARENT_SCOPE) |
| endfunction() |
| |
| function(zephyr_get_system_include_directories_for_lang lang i) |
| zephyr_get_parse_args(args ${ARGN}) |
| get_property(flags TARGET zephyr_interface PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) |
| |
| process_flags(${lang} flags output_list) |
| string(REPLACE ";" "$<SEMICOLON>" genexp_output_list "${output_list}") |
| |
| set_ifndef(args_DELIMITER "$<SEMICOLON>") |
| set(result_output_list "$<$<BOOL:${genexp_output_list}>:-isystem$<JOIN:${genexp_output_list},${args_DELIMITER}-isystem>>") |
| |
| set(${i} ${result_output_list} PARENT_SCOPE) |
| endfunction() |
| |
| function(zephyr_get_compile_definitions_for_lang lang i) |
| zephyr_get_parse_args(args ${ARGN}) |
| get_property(flags TARGET zephyr_interface PROPERTY INTERFACE_COMPILE_DEFINITIONS) |
| |
| process_flags(${lang} flags output_list) |
| string(REPLACE ";" "$<SEMICOLON>" genexp_output_list "${output_list}") |
| |
| set_ifndef(args_DELIMITER "$<SEMICOLON>") |
| set(result_output_list "-D$<JOIN:${genexp_output_list},${args_DELIMITER}-D>") |
| |
| set(${i} ${result_output_list} PARENT_SCOPE) |
| endfunction() |
| |
| function(zephyr_get_compile_options_for_lang lang i) |
| zephyr_get_parse_args(args ${ARGN}) |
| get_property(flags TARGET zephyr_interface PROPERTY INTERFACE_COMPILE_OPTIONS) |
| |
| process_flags(${lang} flags output_list) |
| string(REPLACE ";" "$<SEMICOLON>" genexp_output_list "${output_list}") |
| |
| set_ifndef(args_DELIMITER "$<SEMICOLON>") |
| set(result_output_list "$<JOIN:${genexp_output_list},${args_DELIMITER}>") |
| |
| set(${i} ${result_output_list} PARENT_SCOPE) |
| endfunction() |
| |
| # This function writes a dict to it's output parameter |
| # 'return_dict'. The dict has information about the parsed arguments, |
| # |
| # Usage: |
| # zephyr_get_parse_args(foo ${ARGN}) |
| # print(foo_STRIP_PREFIX) # foo_STRIP_PREFIX might be set to 1 |
| function(zephyr_get_parse_args return_dict) |
| foreach(x ${ARGN}) |
| if(DEFINED single_argument) |
| set(${single_argument} ${x} PARENT_SCOPE) |
| unset(single_argument) |
| else() |
| if(x STREQUAL STRIP_PREFIX) |
| set(${return_dict}_STRIP_PREFIX 1 PARENT_SCOPE) |
| elseif(x STREQUAL NO_SPLIT) |
| set(${return_dict}_NO_SPLIT 1 PARENT_SCOPE) |
| elseif(x STREQUAL DELIMITER) |
| set(single_argument ${return_dict}_DELIMITER) |
| endif() |
| endif() |
| endforeach() |
| endfunction() |
| |
| function(process_flags lang input output) |
| # The flags might contains compile language generator expressions that |
| # look like this: |
| # $<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions> |
| # $<$<COMPILE_LANGUAGE:CXX>:$<OTHER_EXPRESSION>> |
| # |
| # Flags that don't specify a language like this apply to all |
| # languages. |
| # |
| # See COMPILE_LANGUAGE in |
| # https://cmake.org/cmake/help/v3.3/manual/cmake-generator-expressions.7.html |
| # |
| # To deal with this, we apply a regex to extract the flag and also |
| # to find out if the language matches. |
| # |
| # If this doesn't work out we might need to ban the use of |
| # COMPILE_LANGUAGE and instead partition C, CXX, and ASM into |
| # different libraries |
| set(languages C CXX ASM) |
| |
| set(tmp_list "") |
| |
| foreach(flag ${${input}}) |
| set(is_compile_lang_generator_expression 0) |
| foreach(l ${languages}) |
| if(flag MATCHES "<COMPILE_LANGUAGE:${l}>:([^>]+)>") |
| set(updated_flag ${CMAKE_MATCH_1}) |
| set(is_compile_lang_generator_expression 1) |
| if(${l} STREQUAL ${lang}) |
| # This test will match in case there are more generator expressions in the flag. |
| # As example: $<$<COMPILE_LANGUAGE:C>:$<OTHER_EXPRESSION>> |
| # $<$<OTHER_EXPRESSION:$<COMPILE_LANGUAGE:C>:something>> |
| string(REGEX MATCH "(\\\$<)[^\\\$]*(\\\$<)[^\\\$]*(\\\$<)" IGNORE_RESULT ${flag}) |
| if(CMAKE_MATCH_2) |
| # Nested generator expressions are used, just substitute `$<COMPILE_LANGUAGE:${l}>` to `1` |
| string(REGEX REPLACE "\\\$<COMPILE_LANGUAGE:${l}>" "1" updated_flag ${flag}) |
| endif() |
| list(APPEND tmp_list ${updated_flag}) |
| break() |
| endif() |
| endif() |
| endforeach() |
| |
| if(NOT is_compile_lang_generator_expression) |
| # SHELL is used to avoid de-duplication, but when process flags |
| # then this tag must be removed to return real compile/linker flags. |
| if(flag MATCHES "SHELL:[ ]*(.*)") |
| separate_arguments(flag UNIX_COMMAND ${CMAKE_MATCH_1}) |
| endif() |
| # Flags may be placed inside generator expression, therefore any flag |
| # which is not already a generator expression must have commas converted. |
| if(NOT flag MATCHES "\\\$<.*>") |
| string(REPLACE "," "$<COMMA>" flag "${flag}") |
| endif() |
| list(APPEND tmp_list ${flag}) |
| endif() |
| endforeach() |
| |
| set(${output} ${tmp_list} PARENT_SCOPE) |
| endfunction() |
| |
| function(convert_list_of_flags_to_string_of_flags ptr_list_of_flags string_of_flags) |
| # Convert the list to a string so we can do string replace |
| # operations on it and replace the ";" list separators with a |
| # whitespace so the flags are spaced out |
| string(REPLACE ";" " " locally_scoped_string_of_flags "${${ptr_list_of_flags}}") |
| |
| # Set the output variable in the parent scope |
| set(${string_of_flags} ${locally_scoped_string_of_flags} PARENT_SCOPE) |
| endfunction() |
| |
| macro(get_property_and_add_prefix result target property prefix) |
| zephyr_get_parse_args(args ${ARGN}) |
| |
| if(args_STRIP_PREFIX) |
| set(maybe_prefix "") |
| else() |
| set(maybe_prefix ${prefix}) |
| endif() |
| |
| get_property(target_property TARGET ${target} PROPERTY ${property}) |
| foreach(x ${target_property}) |
| list(APPEND ${result} ${maybe_prefix}${x}) |
| endforeach() |
| endmacro() |
| |
| # 1.2 zephyr_library_* |
| # |
| # Zephyr libraries use CMake's library concept and a set of |
| # assumptions about how zephyr code is organized to cut down on |
| # boilerplate code. |
| # |
| # A Zephyr library can be constructed by the function zephyr_library |
| # or zephyr_library_named. The constructors create a CMake library |
| # with a name accessible through the variable ZEPHYR_CURRENT_LIBRARY. |
| # |
| # The variable ZEPHYR_CURRENT_LIBRARY should seldom be needed since |
| # the zephyr libraries have methods that modify the libraries. These |
| # methods have the signature: zephyr_library_<target-function> |
| # |
| # The methods are wrappers around the CMake target_* functions. See |
| # https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html for |
| # documentation on the underlying target_* functions. |
| # |
| # The methods modify the CMake target_* API to reduce boilerplate; |
| # PRIVATE is assumed |
| # The target is assumed to be ZEPHYR_CURRENT_LIBRARY |
| # |
| # When a flag that is given through the zephyr_* API conflicts with |
| # the zephyr_library_* API then precedence will be given to the |
| # zephyr_library_* API. In other words, local configuration overrides |
| # global configuration. |
| |
| # Constructor with a directory-inferred name |
| macro(zephyr_library) |
| zephyr_library_get_current_dir_lib_name(${ZEPHYR_BASE} lib_name) |
| zephyr_library_named(${lib_name}) |
| endmacro() |
| |
| # Determines what the current directory's lib name would be according to the |
| # provided base and writes it to the argument "lib_name" |
| macro(zephyr_library_get_current_dir_lib_name base lib_name) |
| # Remove the prefix (/home/sebo/zephyr/driver/serial/CMakeLists.txt => driver/serial/CMakeLists.txt) |
| file(RELATIVE_PATH name ${base} ${CMAKE_CURRENT_LIST_FILE}) |
| |
| # Remove the filename (driver/serial/CMakeLists.txt => driver/serial) |
| get_filename_component(name ${name} DIRECTORY) |
| |
| # Replace / with __ (driver/serial => driver__serial) |
| string(REGEX REPLACE "/" "__" name ${name}) |
| |
| # Replace : with __ (C:/zephyrproject => C____zephyrproject) |
| string(REGEX REPLACE ":" "__" name ${name}) |
| |
| set(${lib_name} ${name}) |
| endmacro() |
| |
| # Constructor with an explicitly given name. |
| macro(zephyr_library_named name) |
| # This is a macro because we need add_library() to be executed |
| # within the scope of the caller. |
| set(ZEPHYR_CURRENT_LIBRARY ${name}) |
| add_library(${name} STATIC "") |
| |
| zephyr_append_cmake_library(${name}) |
| |
| target_link_libraries(${name} PUBLIC zephyr_interface) |
| endmacro() |
| |
| # Provides amend functionality to a Zephyr library for out-of-tree usage. |
| # |
| # When called from a Zephyr module, the corresponding zephyr library defined |
| # within Zephyr will be looked up. |
| # |
| # Note, in order to ensure correct library when amending, the folder structure in the |
| # Zephyr module must resemble the structure used in Zephyr, as example: |
| # |
| # Example: to amend the zephyr library created in |
| # ZEPHYR_BASE/drivers/entropy/CMakeLists.txt |
| # add the following file: |
| # ZEPHYR_MODULE/drivers/entropy/CMakeLists.txt |
| # with content: |
| # zephyr_library_amend() |
| # zephyr_library_sources(...) |
| # |
| # It is also possible to use generator expression when amending to Zephyr |
| # libraries. |
| # |
| # For example, in case it is required to expose the Zephyr library's folder as |
| # include path then the following is possible: |
| # zephyr_library_amend() |
| # zephyr_library_include_directories($<TARGET_PROPERTY:SOURCE_DIR>) |
| # |
| # See the CMake documentation for more target properties or generator |
| # expressions. |
| # |
| macro(zephyr_library_amend) |
| # This is a macro because we need to ensure the ZEPHYR_CURRENT_LIBRARY and |
| # following zephyr_library_* calls are executed within the scope of the |
| # caller. |
| if(NOT ZEPHYR_CURRENT_MODULE_DIR) |
| message(FATAL_ERROR "Function only available for Zephyr modules.") |
| endif() |
| |
| zephyr_library_get_current_dir_lib_name(${ZEPHYR_CURRENT_MODULE_DIR} lib_name) |
| |
| set(ZEPHYR_CURRENT_LIBRARY ${lib_name}) |
| endmacro() |
| |
| function(zephyr_link_interface interface) |
| target_link_libraries(${interface} INTERFACE zephyr_interface) |
| endfunction() |
| |
| # |
| # zephyr_library versions of normal CMake target_<func> functions |
| # Note, paths passed to this function must be relative in order |
| # to support the library relocation feature of zephyr_code_relocate |
| # |
| function(zephyr_library_sources source) |
| target_sources(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${source} ${ARGN}) |
| endfunction() |
| |
| function(zephyr_library_include_directories) |
| target_include_directories(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${ARGN}) |
| endfunction() |
| |
| function(zephyr_library_link_libraries item) |
| target_link_libraries(${ZEPHYR_CURRENT_LIBRARY} PUBLIC ${item} ${ARGN}) |
| endfunction() |
| |
| function(zephyr_library_compile_definitions item) |
| target_compile_definitions(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${item} ${ARGN}) |
| endfunction() |
| |
| function(zephyr_library_compile_options item) |
| # The compiler is relied upon for sane behaviour when flags are in |
| # conflict. Compilers generally give precedence to flags given late |
| # on the command line. So to ensure that zephyr_library_* flags are |
| # placed late on the command line we create a dummy interface |
| # library and link with it to obtain the flags. |
| # |
| # Linking with a dummy interface library will place flags later on |
| # the command line than the the flags from zephyr_interface because |
| # zephyr_interface will be the first interface library that flags |
| # are taken from. |
| |
| string(MD5 uniqueness "${ARGV}") |
| set(lib_name options_interface_lib_${uniqueness}) |
| |
| if (NOT TARGET ${lib_name}) |
| # Create the unique target only if it doesn't exist. |
| add_library( ${lib_name} INTERFACE) |
| target_compile_options(${lib_name} INTERFACE ${item} ${ARGN}) |
| endif() |
| |
| target_link_libraries(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${lib_name}) |
| endfunction() |
| |
| function(zephyr_library_cc_option) |
| foreach(option ${ARGV}) |
| string(MAKE_C_IDENTIFIER check${option} check) |
| zephyr_check_compiler_flag(C ${option} ${check}) |
| |
| if(${${check}}) |
| zephyr_library_compile_options(${option}) |
| endif() |
| endforeach() |
| endfunction() |
| |
| function(zephyr_library_add_dependencies) |
| add_dependencies(${ZEPHYR_CURRENT_LIBRARY} ${ARGN}) |
| endfunction() |
| |
| # Add the existing CMake library 'library' to the global list of |
| # Zephyr CMake libraries. This is done automatically by the |
| # constructor but must be called explicitly on CMake libraries that do |
| # not use a zephyr library constructor. |
| function(zephyr_append_cmake_library library) |
| if(TARGET zephyr_prebuilt) |
| message(WARNING |
| "zephyr_library() or zephyr_library_named() called in Zephyr CMake " |
| "application mode. `${library}` will not be treated as a Zephyr library." |
| "To create a Zephyr library in Zephyr CMake kernel mode consider " |
| "creating a Zephyr module. See more here: " |
| "https://docs.zephyrproject.org/latest/guides/modules.html" |
| ) |
| endif() |
| set_property(GLOBAL APPEND PROPERTY ZEPHYR_LIBS ${library}) |
| endfunction() |
| |
| # Add the imported library 'library_name', located at 'library_path' to the |
| # global list of Zephyr CMake libraries. |
| function(zephyr_library_import library_name library_path) |
| add_library(${library_name} STATIC IMPORTED GLOBAL) |
| set_target_properties(${library_name} |
| PROPERTIES IMPORTED_LOCATION |
| ${library_path} |
| ) |
| zephyr_append_cmake_library(${library_name}) |
| endfunction() |
| |
| # Place the current zephyr library in the application memory partition. |
| # |
| # The partition argument is the name of the partition where the library shall |
| # be placed. |
| # |
| # Note: Ensure the given partition has been defined using |
| # K_APPMEM_PARTITION_DEFINE in source code. |
| function(zephyr_library_app_memory partition) |
| set_property(TARGET zephyr_property_target |
| APPEND PROPERTY COMPILE_OPTIONS |
| "-l" $<TARGET_FILE_NAME:${ZEPHYR_CURRENT_LIBRARY}> "${partition}") |
| endfunction() |
| |
| # Configure a Zephyr library specific property. |
| # |
| # Usage: |
| # zephyr_library_property(<property> <value>) |
| # |
| # Current Zephyr library specific properties that are supported: |
| # ALLOW_EMPTY <TRUE:FALSE>: Allow a Zephyr library to be empty. |
| # An empty Zephyr library will generate a CMake |
| # configure time warning unless `ALLOW_EMPTY` is TRUE. |
| function(zephyr_library_property) |
| set(single_args "ALLOW_EMPTY") |
| cmake_parse_arguments(LIB_PROP "" "${single_args}" "" ${ARGN}) |
| |
| if(LIB_PROP_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_library_property(${ARGV0} ...) given unknown arguments: ${FILE_UNPARSED_ARGUMENTS}") |
| endif() |
| |
| foreach(arg ${single_args}) |
| if(DEFINED LIB_PROP_${arg}) |
| set_property(TARGET ${ZEPHYR_CURRENT_LIBRARY} PROPERTY ${arg} ${LIB_PROP_${arg}}) |
| endif() |
| endforeach() |
| endfunction() |
| |
| # 1.2.1 zephyr_interface_library_* |
| # |
| # A Zephyr interface library is a thin wrapper over a CMake INTERFACE |
| # library. The most important responsibility of this abstraction is to |
| # ensure that when a user KConfig-enables a library then the header |
| # files of this library will be accessible to the 'app' library. |
| # |
| # This is done because when a user uses Kconfig to enable a library he |
| # expects to be able to include its header files and call its |
| # functions out-of-the box. |
| # |
| # A Zephyr interface library should be used when there exists some |
| # build information (include directories, defines, compiler flags, |
| # etc.) that should be applied to a set of Zephyr libraries and 'app' |
| # might be one of these libraries. |
| # |
| # Zephyr libraries must explicitly call |
| # zephyr_library_link_libraries(<interface_library>) to use this build |
| # information. 'app' is treated as a special case for usability |
| # reasons; a Kconfig option (CONFIG_APP_LINK_WITH_<interface_library>) |
| # should exist for each interface_library and will determine if 'app' |
| # links with the interface_library. |
| # |
| # This API has a constructor like the zephyr_library API has, but it |
| # does not have wrappers over the other cmake target functions. |
| macro(zephyr_interface_library_named name) |
| add_library(${name} INTERFACE) |
| set_property(GLOBAL APPEND PROPERTY ZEPHYR_INTERFACE_LIBS ${name}) |
| endmacro() |
| |
| # 1.3 generate_inc_* |
| |
| # These functions are useful if there is a need to generate a file |
| # that can be included into the application at build time. The file |
| # can also be compressed automatically when embedding it. |
| # |
| # See tests/application_development/gen_inc_file for an example of |
| # usage. |
| function(generate_inc_file |
| source_file # The source file to be converted to hex |
| generated_file # The generated file |
| ) |
| add_custom_command( |
| OUTPUT ${generated_file} |
| COMMAND |
| ${PYTHON_EXECUTABLE} |
| ${ZEPHYR_BASE}/scripts/build/file2hex.py |
| ${ARGN} # Extra arguments are passed to file2hex.py |
| --file ${source_file} |
| > ${generated_file} # Does pipe redirection work on Windows? |
| DEPENDS ${source_file} |
| WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} |
| ) |
| endfunction() |
| |
| function(generate_inc_file_for_gen_target |
| target # The cmake target that depends on the generated file |
| source_file # The source file to be converted to hex |
| generated_file # The generated file |
| gen_target # The generated file target we depend on |
| # Any additional arguments are passed on to file2hex.py |
| ) |
| generate_inc_file(${source_file} ${generated_file} ${ARGN}) |
| |
| # Ensure 'generated_file' is generated before 'target' by creating a |
| # dependency between the two targets |
| |
| add_dependencies(${target} ${gen_target}) |
| endfunction() |
| |
| function(generate_inc_file_for_target |
| target # The cmake target that depends on the generated file |
| source_file # The source file to be converted to hex |
| generated_file # The generated file |
| # Any additional arguments are passed on to file2hex.py |
| ) |
| # Ensure 'generated_file' is generated before 'target' by creating a |
| # 'custom_target' for it and setting up a dependency between the two |
| # targets |
| |
| # But first create a unique name for the custom target |
| generate_unique_target_name_from_filename(${generated_file} generated_target_name) |
| |
| add_custom_target(${generated_target_name} DEPENDS ${generated_file}) |
| generate_inc_file_for_gen_target(${target} ${source_file} ${generated_file} ${generated_target_name} ${ARGN}) |
| endfunction() |
| |
| # 1.4. board_* |
| # |
| # This section is for extensions related to Zephyr board handling. |
| # |
| # Zephyr board extensions current contains: |
| # - Board runners |
| # - Board revision |
| |
| # Zephyr board runners: |
| # Zephyr board runner extension functions control Zephyr's board runners |
| # from the build system. The Zephyr build system has targets for |
| # flashing and debugging supported boards. These are wrappers around a |
| # "runner" Python subpackage that is part of Zephyr's "west" tool. |
| # |
| # This section provides glue between CMake and the Python code that |
| # manages the runners. |
| |
| function(_board_check_runner_type type) # private helper |
| if (NOT (("${type}" STREQUAL "FLASH") OR ("${type}" STREQUAL "DEBUG"))) |
| message(FATAL_ERROR "invalid type ${type}; should be FLASH or DEBUG") |
| endif() |
| endfunction() |
| |
| # This function sets the runner for the board unconditionally. It's |
| # meant to be used from application CMakeLists.txt files. |
| # |
| # NOTE: Usually board_set_xxx_ifnset() is best in board.cmake files. |
| # This lets the user set the runner at cmake time, or in their |
| # own application's CMakeLists.txt. |
| # |
| # Usage: |
| # board_set_runner(FLASH pyocd) |
| # |
| # This would set the board's flash runner to "pyocd". |
| # |
| # In general, "type" is FLASH or DEBUG, and "runner" is the name of a |
| # runner. |
| function(board_set_runner type runner) |
| _board_check_runner_type(${type}) |
| if (DEFINED BOARD_${type}_RUNNER) |
| message(STATUS "overriding ${type} runner ${BOARD_${type}_RUNNER}; it's now ${runner}") |
| endif() |
| set(BOARD_${type}_RUNNER ${runner} PARENT_SCOPE) |
| endfunction() |
| |
| # This macro is like board_set_runner(), but will only make a change |
| # if that runner is currently not set. |
| # |
| # See also board_set_flasher_ifnset() and board_set_debugger_ifnset(). |
| macro(board_set_runner_ifnset type runner) |
| _board_check_runner_type(${type}) |
| # This is a macro because set_ifndef() works at parent scope. |
| # If this were a function, that would be this function's scope, |
| # which wouldn't work. |
| set_ifndef(BOARD_${type}_RUNNER ${runner}) |
| endmacro() |
| |
| # A convenience macro for board_set_runner(FLASH ${runner}). |
| macro(board_set_flasher runner) |
| board_set_runner(FLASH ${runner}) |
| endmacro() |
| |
| # A convenience macro for board_set_runner(DEBUG ${runner}). |
| macro(board_set_debugger runner) |
| board_set_runner(DEBUG ${runner}) |
| endmacro() |
| |
| # A convenience macro for board_set_runner_ifnset(FLASH ${runner}). |
| macro(board_set_flasher_ifnset runner) |
| board_set_runner_ifnset(FLASH ${runner}) |
| endmacro() |
| |
| # A convenience macro for board_set_runner_ifnset(DEBUG ${runner}). |
| macro(board_set_debugger_ifnset runner) |
| board_set_runner_ifnset(DEBUG ${runner}) |
| endmacro() |
| |
| # This function is intended for board.cmake files and application |
| # CMakeLists.txt files. |
| # |
| # Usage from board.cmake files: |
| # board_runner_args(runner "--some-arg=val1" "--another-arg=val2") |
| # |
| # The build system will then ensure the command line used to |
| # create the runner contains: |
| # --some-arg=val1 --another-arg=val2 |
| # |
| # Within application CMakeLists.txt files, ensure that all calls to |
| # board_runner_args() are part of a macro named app_set_runner_args(), |
| # like this, which is defined before calling 'find_package(Zephyr)': |
| # macro(app_set_runner_args) |
| # board_runner_args(runner "--some-app-setting=value") |
| # endmacro() |
| # |
| # The build system tests for the existence of the macro and will |
| # invoke it at the appropriate time if it is defined. |
| # |
| # Any explicitly provided settings given by this function override |
| # defaults provided by the build system. |
| function(board_runner_args runner) |
| string(MAKE_C_IDENTIFIER ${runner} runner_id) |
| # Note the "_EXPLICIT_" here, and see below. |
| set_property(GLOBAL APPEND PROPERTY BOARD_RUNNER_ARGS_EXPLICIT_${runner_id} ${ARGN}) |
| endfunction() |
| |
| # This function is intended for internal use by |
| # boards/common/runner.board.cmake files. |
| # |
| # Basic usage: |
| # board_finalize_runner_args(runner) |
| # |
| # This ensures the build system captures all arguments added in any |
| # board_runner_args() calls, and otherwise finishes registering a |
| # runner for use. |
| # |
| # Extended usage: |
| # board_runner_args(runner "--some-arg=default-value") |
| # |
| # This provides common or default values for arguments. These are |
| # placed before board_runner_args() calls, so they generally take |
| # precedence, except for arguments which can be given multiple times |
| # (use these with caution). |
| function(board_finalize_runner_args runner) |
| # If the application provided a macro to add additional runner |
| # arguments, handle them. |
| if(COMMAND app_set_runner_args) |
| app_set_runner_args() |
| endif() |
| |
| # Retrieve the list of explicitly set arguments. |
| string(MAKE_C_IDENTIFIER ${runner} runner_id) |
| get_property(explicit GLOBAL PROPERTY "BOARD_RUNNER_ARGS_EXPLICIT_${runner_id}") |
| |
| # Note no _EXPLICIT_ here. This property contains the final list. |
| set_property(GLOBAL APPEND PROPERTY BOARD_RUNNER_ARGS_${runner_id} |
| # Default arguments from the common runner file come first. |
| ${ARGN} |
| # Arguments explicitly given with board_runner_args() come |
| # next, so they take precedence over the common runner file. |
| ${explicit} |
| # Arguments given via the CMake cache come last of all. Users |
| # can provide variables in this way from the CMake command line. |
| ${BOARD_RUNNER_ARGS_${runner_id}} |
| ) |
| |
| # Add the finalized runner to the global property list. |
| set_property(GLOBAL APPEND PROPERTY ZEPHYR_RUNNERS ${runner}) |
| endfunction() |
| |
| function(board_set_rimage_target target) |
| set(RIMAGE_TARGET ${target} CACHE STRING "rimage target") |
| zephyr_check_cache(RIMAGE_TARGET) |
| endfunction() |
| |
| # Zephyr board revision: |
| # |
| # This section provides a function for revision checking. |
| |
| # Usage: |
| # board_check_revision(FORMAT <LETTER | NUMBER | MAJOR.MINOR.PATCH> |
| # [EXACT] |
| # [DEFAULT_REVISION <revision>] |
| # [HIGHEST_REVISION <revision>] |
| # ) |
| # |
| # Zephyr board extension function. |
| # |
| # This function can be used in `boards/<board>/revision.cmake` to check a user |
| # requested revision against available board revisions. |
| # |
| # The function will check the revision from `-DBOARD=<board>@<revision>` that |
| # is provided by the user according to the arguments. |
| # When `EXACT` is not specified, this function will set the Zephyr build system |
| # variable `ACTIVE_BOARD_REVISION` with the selected revision. |
| # |
| # FORMAT <LETTER | NUMBER | MAJOR.MINOR.PATCH>: Specify the revision format. |
| # LETTER: Revision format is a single letter from A - Z. |
| # NUMBER: Revision format is a single integer number. |
| # MAJOR.MINOR.PATCH: Revision format is three numbers, separated by `.`, |
| # `x.y.z`. Trailing zeroes may be omitted on the |
| # command line, which means: |
| # 1.0.0 == 1.0 == 1 |
| # |
| # OPTIONAL: Revision specifier is optional. If revision is not provided the base |
| # board will be used. If both `EXACT` and `OPTIONAL` are given, then |
| # specifying the revision is optional, but if it is given then the |
| # `EXACT` requirements apply. Mutually exclusive with `DEFAULT_REVISION`. |
| # |
| # EXACT: Revision is required to be an exact match. As example, available revisions are: |
| # 0.1.0 and 0.3.0, and user provides 0.2.0, then an error is reported |
| # when `EXACT` is given. |
| # If `EXACT` is not provided, then closest lower revision will be selected |
| # as the active revision, which in the example will be `0.1.0`. |
| # |
| # DEFAULT_REVISION: Provides a default revision to use when user has not selected |
| # a revision number. If no default revision is provided then |
| # user will be printed with an error if no revision is given |
| # on the command line. |
| # |
| # HIGHEST_REVISION: Allows to specify highest valid revision for a board. |
| # This can be used to ensure that a newer board cannot be used |
| # with an older Zephyr. As example, if current board supports |
| # revisions 0.x.0-0.99.99 and 1.0.0-1.99.99, and it is expected |
| # that current board implementation will not work with board |
| # revision 2.0.0, then HIGHEST_REVISION can be set to 1.99.99, |
| # and user will be printed with an error if using |
| # `<board>@2.0.0` or higher. |
| # This field is not needed when `EXACT` is used. |
| # |
| # VALID_REVISIONS: A list of valid revisions for this board. |
| # If this argument is not provided, then each Kconfig fragment |
| # of the form ``<board>_<revision>.conf`` in the board folder |
| # will be used as a valid revision for the board. |
| # |
| function(board_check_revision) |
| set(options OPTIONAL EXACT) |
| set(single_args FORMAT DEFAULT_REVISION HIGHEST_REVISION) |
| set(multi_args VALID_REVISIONS) |
| cmake_parse_arguments(BOARD_REV "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| |
| string(TOUPPER ${BOARD_REV_FORMAT} BOARD_REV_FORMAT) |
| |
| if(DEFINED BOARD_REV_DEFAULT_REVISION AND BOARD_REV_OPTIONAL) |
| message(FATAL_ERROR "Arguments BOARD_REVISION and OPTIONAL are mutually exclusive") |
| endif() |
| |
| if(NOT DEFINED BOARD_REVISION) |
| if(BOARD_REV_OPTIONAL) |
| return() |
| elseif(DEFINED BOARD_REV_DEFAULT_REVISION) |
| set(BOARD_REVISION ${BOARD_REV_DEFAULT_REVISION}) |
| set(BOARD_REVISION ${BOARD_REVISION} PARENT_SCOPE) |
| else() |
| message(FATAL_ERROR "No board revision specified, Board: `${BOARD}` \ |
| requires a revision. Please use: `-DBOARD=${BOARD}@<revision>`") |
| endif() |
| endif() |
| |
| if(DEFINED BOARD_REV_HIGHEST_REVISION) |
| if(((BOARD_REV_FORMAT STREQUAL LETTER) AND |
| (BOARD_REVISION STRGREATER BOARD_REV_HIGHEST_REVISION)) OR |
| ((BOARD_REV_FORMAT STREQUAL NUMBER) AND |
| (BOARD_REVISION GREATER BOARD_REV_HIGHEST_REVISION)) OR |
| ((BOARD_REV_FORMAT MATCHES "^MAJOR\.MINOR\.PATCH$") AND |
| (BOARD_REVISION VERSION_GREATER BOARD_REV_HIGHEST_REVISION)) |
| ) |
| message(FATAL_ERROR "Board revision `${BOARD_REVISION}` greater than \ |
| highest supported revision `${BOARD_REV_HIGHEST_REVISION}`. \ |
| Please specify a valid board revision.") |
| endif() |
| endif() |
| |
| if(BOARD_REV_FORMAT STREQUAL LETTER) |
| set(revision_regex "([A-Z])") |
| elseif(BOARD_REV_FORMAT STREQUAL NUMBER) |
| set(revision_regex "([0-9]+)") |
| elseif(BOARD_REV_FORMAT MATCHES "^MAJOR\.MINOR\.PATCH$") |
| set(revision_regex "((0|[1-9][0-9]*)(\.[0-9]+)(\.[0-9]+))") |
| # We allow loose <board>@<revision> typing on command line. |
| # so append missing zeroes. |
| if(BOARD_REVISION MATCHES "((0|[1-9][0-9]*)(\.[0-9]+)?(\.[0-9]+)?)") |
| if(NOT CMAKE_MATCH_3) |
| set(BOARD_REVISION ${BOARD_REVISION}.0) |
| set(BOARD_REVISION ${BOARD_REVISION} PARENT_SCOPE) |
| endif() |
| if(NOT CMAKE_MATCH_4) |
| set(BOARD_REVISION ${BOARD_REVISION}.0) |
| set(BOARD_REVISION ${BOARD_REVISION} PARENT_SCOPE) |
| endif() |
| endif() |
| else() |
| message(FATAL_ERROR "Invalid format specified for \ |
| `board_check_revision(FORMAT <LETTER | NUMBER | MAJOR.MINOR.PATCH>)`") |
| endif() |
| |
| if(NOT (BOARD_REVISION MATCHES "^${revision_regex}$")) |
| message(FATAL_ERROR "Invalid revision format used for `${BOARD_REVISION}`. \ |
| Board `${BOARD}` uses revision format: ${BOARD_REV_FORMAT}.") |
| endif() |
| |
| if(NOT DEFINED BOARD_REV_VALID_REVISIONS) |
| file(GLOB revision_candidates LIST_DIRECTORIES false RELATIVE ${BOARD_DIR} |
| ${BOARD_DIR}/${BOARD}_*.conf |
| ) |
| string(REPLACE "." "_" underscore_revision_regex ${revision_regex}) |
| set(file_revision_regex "${BOARD}_${underscore_revision_regex}.conf") |
| foreach(candidate ${revision_candidates}) |
| if(${candidate} MATCHES "${file_revision_regex}") |
| string(REPLACE "_" "." FOUND_BOARD_REVISION ${CMAKE_MATCH_1}) |
| list(APPEND BOARD_REV_VALID_REVISIONS ${FOUND_BOARD_REVISION}) |
| endif() |
| endforeach() |
| endif() |
| |
| if(${BOARD_REVISION} IN_LIST BOARD_REV_VALID_REVISIONS) |
| # Found exact match. |
| return() |
| endif() |
| |
| if(NOT BOARD_REV_EXACT) |
| foreach(TEST_REVISION ${BOARD_REV_VALID_REVISIONS}) |
| if((BOARD_REV_FORMAT MATCHES "^MAJOR\.MINOR\.PATCH$") AND |
| (${BOARD_REVISION} VERSION_GREATER_EQUAL ${TEST_REVISION}) AND |
| (${TEST_REVISION} VERSION_GREATER_EQUAL "${ACTIVE_BOARD_REVISION}") |
| ) |
| set(ACTIVE_BOARD_REVISION ${TEST_REVISION}) |
| elseif((BOARD_REV_FORMAT STREQUAL LETTER) AND |
| (${BOARD_REVISION} STRGREATER ${TEST_REVISION}) AND |
| (${TEST_REVISION} STRGREATER "${ACTIVE_BOARD_REVISION}") |
| ) |
| set(ACTIVE_BOARD_REVISION ${TEST_REVISION}) |
| elseif((BOARD_REV_FORMAT STREQUAL NUMBER) AND |
| (${BOARD_REVISION} GREATER ${TEST_REVISION}) AND |
| (${TEST_REVISION} GREATER "${ACTIVE_BOARD_REVISION}") |
| ) |
| set(ACTIVE_BOARD_REVISION ${TEST_REVISION}) |
| endif() |
| endforeach() |
| endif() |
| |
| if(BOARD_REV_EXACT OR NOT DEFINED ACTIVE_BOARD_REVISION) |
| message(FATAL_ERROR "Board revision `${BOARD_REVISION}` for board \ |
| `${BOARD}` not found. Please specify a valid board revision.") |
| endif() |
| |
| set(ACTIVE_BOARD_REVISION ${ACTIVE_BOARD_REVISION} PARENT_SCOPE) |
| endfunction() |
| |
| # 1.5. Misc. |
| |
| # zephyr_check_compiler_flag is a part of Zephyr's toolchain |
| # infrastructure. It should be used when testing toolchain |
| # capabilities and it should normally be used in place of the |
| # functions: |
| # |
| # check_compiler_flag |
| # check_c_compiler_flag |
| # check_cxx_compiler_flag |
| # |
| # See check_compiler_flag() for API documentation as it has the same |
| # API. |
| # |
| # It is implemented as a wrapper on top of check_compiler_flag, which |
| # again wraps the CMake-builtin's check_c_compiler_flag and |
| # check_cxx_compiler_flag. |
| # |
| # It takes time to check for compatibility of flags against toolchains |
| # so we cache the capability test results in USER_CACHE_DIR (This |
| # caching comes in addition to the caching that CMake does in the |
| # build folder's CMakeCache.txt) |
| function(zephyr_check_compiler_flag lang option check) |
| # Check if the option is covered by any hardcoded check before doing |
| # an automated test. |
| zephyr_check_compiler_flag_hardcoded(${lang} "${option}" _${check} exists) |
| if(exists) |
| set(${check} ${_${check}} PARENT_SCOPE) |
| return() |
| endif() |
| |
| # Locate the cache directory |
| set_ifndef( |
| ZEPHYR_TOOLCHAIN_CAPABILITY_CACHE_DIR |
| ${USER_CACHE_DIR}/ToolchainCapabilityDatabase |
| ) |
| |
| # The toolchain capability database/cache is maintained as a |
| # directory of files. The filenames in the directory are keys, and |
| # the file contents are the values in this key-value store. |
| |
| # We need to create a unique key wrt. testing the toolchain |
| # capability. This key must include everything that can affect the |
| # toolchain test. |
| # |
| # Also, to fit the key into a filename we calculate the MD5 sum of |
| # the key. |
| |
| # The 'cacheformat' must be bumped if a bug in the caching mechanism |
| # is detected and all old keys must be invalidated. |
| set(cacheformat 3) |
| |
| set(key_string "") |
| set(key_string "${key_string}${cacheformat}_") |
| set(key_string "${key_string}${TOOLCHAIN_SIGNATURE}_") |
| set(key_string "${key_string}${lang}_") |
| set(key_string "${key_string}${option}_") |
| set(key_string "${key_string}${CMAKE_REQUIRED_FLAGS}_") |
| |
| string(MD5 key "${key_string}") |
| |
| # Check the cache |
| set(key_path ${ZEPHYR_TOOLCHAIN_CAPABILITY_CACHE_DIR}/${key}) |
| if(EXISTS ${key_path}) |
| file(READ |
| ${key_path} # File to be read |
| key_value # Output variable |
| LIMIT 1 # Read at most 1 byte ('0' or '1') |
| ) |
| |
| set(${check} ${key_value} PARENT_SCOPE) |
| return() |
| endif() |
| |
| # Flags that start with -Wno-<warning> can not be tested by |
| # check_compiler_flag, they will always pass, but -W<warning> can be |
| # tested, so to test -Wno-<warning> flags we test -W<warning> |
| # instead. |
| if("${option}" MATCHES "-Wno-(.*)") |
| string(REPLACE "-Wno-" "-W" possibly_translated_option "${option}") |
| else() |
| set(possibly_translated_option ${option}) |
| endif() |
| |
| check_compiler_flag(${lang} "${possibly_translated_option}" inner_check) |
| |
| set(${check} ${inner_check} PARENT_SCOPE) |
| |
| # Populate the cache |
| if(NOT (EXISTS ${key_path})) |
| |
| # This is racy. As often with race conditions, this one can easily be |
| # made worse and demonstrated with a simple delay: |
| # execute_process(COMMAND "sleep" "5") |
| # Delete the cache, add the sleep above and run twister with a |
| # large number of JOBS. Once it's done look at the log.txt file |
| # below and you will see that concurrent cmake processes created the |
| # same files multiple times. |
| |
| # While there are a number of reasons why this race seems both very |
| # unlikely and harmless, let's play it safe anyway and write to a |
| # private, temporary file first. All modern filesystems seem to |
| # support at least one atomic rename API and cmake's file(RENAME |
| # ...) officially leverages that. |
| string(RANDOM LENGTH 8 tempsuffix) |
| |
| file( |
| WRITE |
| "${key_path}_tmp_${tempsuffix}" |
| ${inner_check} |
| ) |
| file( |
| RENAME |
| "${key_path}_tmp_${tempsuffix}" "${key_path}" |
| ) |
| |
| # Populate a metadata file (only intended for trouble shooting) |
| # with information about the hash, the toolchain capability |
| # result, and the toolchain test. |
| file( |
| APPEND |
| ${ZEPHYR_TOOLCHAIN_CAPABILITY_CACHE_DIR}/log.txt |
| "${inner_check} ${key} ${key_string}\n" |
| ) |
| endif() |
| endfunction() |
| |
| function(zephyr_check_compiler_flag_hardcoded lang option check exists) |
| # Various flags that are not supported for CXX may not be testable |
| # because they would produce a warning instead of an error during |
| # the test. Exclude them by toolchain-specific blocklist. |
| if((${lang} STREQUAL CXX) AND ("${option}" IN_LIST CXX_EXCLUDED_OPTIONS)) |
| set(${check} 0 PARENT_SCOPE) |
| set(${exists} 1 PARENT_SCOPE) |
| else() |
| # There does not exist a hardcoded check for this option. |
| set(${exists} 0 PARENT_SCOPE) |
| endif() |
| endfunction(zephyr_check_compiler_flag_hardcoded) |
| |
| # zephyr_linker_sources(<location> [SORT_KEY <sort_key>] <files>) |
| # |
| # <files> is one or more .ld formatted files whose contents will be |
| # copied/included verbatim into the given <location> in the global linker.ld. |
| # Preprocessor directives work inside <files>. Relative paths are resolved |
| # relative to the calling file, like zephyr_sources(). |
| # Subsequent calls to zephyr_linker_sources with the same file(s) will remove |
| # these from the original location. Only the last call is considered. |
| # <location> is one of |
| # NOINIT Inside the noinit output section. |
| # RWDATA Inside the data output section. |
| # RODATA Inside the rodata output section. |
| # ROM_START Inside the first output section of the image. This option is |
| # currently only available on ARM Cortex-M, ARM Cortex-R, |
| # x86, ARC, openisa_rv32m1, and RISC-V. |
| # RAM_SECTIONS Inside the RAMABLE_REGION GROUP, not initialized. |
| # DATA_SECTIONS Inside the RAMABLE_REGION GROUP, initialized. |
| # RAMFUNC_SECTION Inside the RAMFUNC RAMABLE_REGION GROUP, not initialized. |
| # NOCACHE_SECTION Inside the NOCACHE section |
| # ITCM_SECTION Inside the itcm section. |
| # DTCM_SECTION Inside the dtcm data section. |
| # SECTIONS Near the end of the file. Don't use this when linking into |
| # RAMABLE_REGION, use RAM_SECTIONS instead. |
| # PINNED_RODATA Similar to RODATA but pinned in memory. |
| # PINNED_RAM_SECTIONS |
| # Similar to RAM_SECTIONS but pinned in memory. |
| # PINNED_DATA_SECTIONS |
| # Similar to DATA_SECTIONS but pinned in memory. |
| # <sort_key> is an optional key to sort by inside of each location. The key must |
| # be alphanumeric, and the keys are sorted alphabetically. If no key is |
| # given, the key 'default' is used. Keys are case-sensitive. |
| # |
| # Use NOINIT, RWDATA, and RODATA unless they don't work for your use case. |
| # |
| # When placing into NOINIT, RWDATA, RODATA, ROM_START, RAMFUNC_SECTION, |
| # NOCACHE_SECTION, DTCM_SECTION or ITCM_SECTION the contents of the files will |
| # be placed inside an output section, so assume the section definition is |
| # already present, e.g.: |
| # _mysection_start = .; |
| # KEEP(*(.mysection)); |
| # _mysection_end = .; |
| # _mysection_size = ABSOLUTE(_mysection_end - _mysection_start); |
| # |
| # When placing into SECTIONS, RAM_SECTIONS or DATA_SECTIONS, the files must |
| # instead define their own output sections to achieve the same thing: |
| # SECTION_PROLOGUE(.mysection,,) |
| # { |
| # _mysection_start = .; |
| # KEEP(*(.mysection)) |
| # _mysection_end = .; |
| # } GROUP_LINK_IN(ROMABLE_REGION) |
| # _mysection_size = _mysection_end - _mysection_start; |
| # |
| # Note about the above examples: If the first example was used with RODATA, and |
| # the second with SECTIONS, the two examples do the same thing from a user |
| # perspective. |
| # |
| # Friendly reminder: Beware of the different ways the location counter ('.') |
| # behaves inside vs. outside section definitions. |
| function(zephyr_linker_sources location) |
| # Set up the paths to the destination files. These files are #included inside |
| # the global linker.ld. |
| set(snippet_base "${__build_dir}/include/generated") |
| set(sections_path "${snippet_base}/snippets-sections.ld") |
| set(ram_sections_path "${snippet_base}/snippets-ram-sections.ld") |
| set(data_sections_path "${snippet_base}/snippets-data-sections.ld") |
| set(rom_start_path "${snippet_base}/snippets-rom-start.ld") |
| set(noinit_path "${snippet_base}/snippets-noinit.ld") |
| set(rwdata_path "${snippet_base}/snippets-rwdata.ld") |
| set(rodata_path "${snippet_base}/snippets-rodata.ld") |
| set(ramfunc_path "${snippet_base}/snippets-ramfunc-section.ld") |
| set(nocache_path "${snippet_base}/snippets-nocache-section.ld") |
| set(itcm_path "${snippet_base}/snippets-itcm-section.ld") |
| set(dtcm_path "${snippet_base}/snippets-dtcm-section.ld") |
| |
| set(pinned_ram_sections_path "${snippet_base}/snippets-pinned-ram-sections.ld") |
| set(pinned_data_sections_path "${snippet_base}/snippets-pinned-data-sections.ld") |
| set(pinned_rodata_path "${snippet_base}/snippets-pinned-rodata.ld") |
| |
| # Clear destination files if this is the first time the function is called. |
| get_property(cleared GLOBAL PROPERTY snippet_files_cleared) |
| if (NOT DEFINED cleared) |
| file(WRITE ${sections_path} "") |
| file(WRITE ${ram_sections_path} "") |
| file(WRITE ${data_sections_path} "") |
| file(WRITE ${rom_start_path} "") |
| file(WRITE ${noinit_path} "") |
| file(WRITE ${rwdata_path} "") |
| file(WRITE ${rodata_path} "") |
| file(WRITE ${ramfunc_path} "") |
| file(WRITE ${nocache_path} "") |
| file(WRITE ${itcm_path} "") |
| file(WRITE ${dtcm_path} "") |
| file(WRITE ${pinned_ram_sections_path} "") |
| file(WRITE ${pinned_data_sections_path} "") |
| file(WRITE ${pinned_rodata_path} "") |
| set_property(GLOBAL PROPERTY snippet_files_cleared true) |
| endif() |
| |
| # Choose destination file, based on the <location> argument. |
| if ("${location}" STREQUAL "SECTIONS") |
| set(snippet_path "${sections_path}") |
| elseif("${location}" STREQUAL "RAM_SECTIONS") |
| set(snippet_path "${ram_sections_path}") |
| elseif("${location}" STREQUAL "DATA_SECTIONS") |
| set(snippet_path "${data_sections_path}") |
| elseif("${location}" STREQUAL "ROM_START") |
| set(snippet_path "${rom_start_path}") |
| elseif("${location}" STREQUAL "NOINIT") |
| set(snippet_path "${noinit_path}") |
| elseif("${location}" STREQUAL "RWDATA") |
| set(snippet_path "${rwdata_path}") |
| elseif("${location}" STREQUAL "RODATA") |
| set(snippet_path "${rodata_path}") |
| elseif("${location}" STREQUAL "RAMFUNC_SECTION") |
| set(snippet_path "${ramfunc_path}") |
| elseif("${location}" STREQUAL "NOCACHE_SECTION") |
| set(snippet_path "${nocache_path}") |
| elseif("${location}" STREQUAL "ITCM_SECTION") |
| dt_has_chosen(HAS_ITCM PROPERTY "zephyr,itcm") |
| if(NOT HAS_ITCM) |
| message(FATAL_ERROR "Trying to link snippet into itcm but no itcm available. " |
| "Add `zephyr,itcm` to the device tree if supported on your device or choose another " |
| "location.") |
| endif() |
| set(snippet_path "${itcm_path}") |
| elseif("${location}" STREQUAL "DTCM_SECTION") |
| dt_has_chosen(HAS_DTCM PROPERTY "zephyr,dtcm") |
| if(NOT HAS_DTCM) |
| message(FATAL_ERROR "Trying to link snippet into dtcm but no dtcm available. " |
| "Add `zephyr,dtcm` to the device tree if supported on your device or choose another " |
| "location.") |
| endif() |
| set(snippet_path "${dtcm_path}") |
| elseif("${location}" STREQUAL "PINNED_RAM_SECTIONS") |
| set(snippet_path "${pinned_ram_sections_path}") |
| elseif("${location}" STREQUAL "PINNED_DATA_SECTIONS") |
| set(snippet_path "${pinned_data_sections_path}") |
| elseif("${location}" STREQUAL "PINNED_RODATA") |
| set(snippet_path "${pinned_rodata_path}") |
| else() |
| message(fatal_error "Must choose valid location for linker snippet.") |
| endif() |
| |
| cmake_parse_arguments(L "" "SORT_KEY" "" ${ARGN}) |
| set(SORT_KEY default) |
| if(DEFINED L_SORT_KEY) |
| set(SORT_KEY ${L_SORT_KEY}) |
| endif() |
| |
| foreach(file IN ITEMS ${L_UNPARSED_ARGUMENTS}) |
| # Resolve path. |
| if(IS_ABSOLUTE ${file}) |
| set(path ${file}) |
| else() |
| set(path ${CMAKE_CURRENT_SOURCE_DIR}/${file}) |
| endif() |
| |
| if(IS_DIRECTORY ${path}) |
| message(FATAL_ERROR "zephyr_linker_sources() was called on a directory") |
| endif() |
| |
| # Find the relative path to the linker file from the include folder. |
| file(RELATIVE_PATH relpath ${ZEPHYR_BASE}/include ${path}) |
| |
| # Create strings to be written into the file |
| set (include_str "/* Sort key: \"${SORT_KEY}\" */#include \"${relpath}\"") |
| |
| # Remove line from other snippet file, if already used |
| get_property(old_path GLOBAL PROPERTY "snippet_files_used_${relpath}") |
| if (DEFINED old_path) |
| file(STRINGS ${old_path} lines) |
| list(FILTER lines EXCLUDE REGEX ${relpath}) |
| string(REPLACE ";" "\n;" lines "${lines}") # Add newline to each line. |
| file(WRITE ${old_path} ${lines} "\n") |
| endif() |
| set_property(GLOBAL PROPERTY "snippet_files_used_${relpath}" ${snippet_path}) |
| |
| # Add new line to existing lines, sort them, and write them back. |
| file(STRINGS ${snippet_path} lines) # Get current lines (without newlines). |
| list(APPEND lines ${include_str}) |
| list(SORT lines) |
| string(REPLACE ";" "\n;" lines "${lines}") # Add newline to each line. |
| file(WRITE ${snippet_path} ${lines} "\n") |
| endforeach() |
| endfunction(zephyr_linker_sources) |
| |
| # Helper macro for conditionally calling zephyr_code_relocate() when a |
| # specific Kconfig symbol is enabled. See zephyr_code_relocate() description |
| # for supported arguments. |
| macro(zephyr_code_relocate_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_code_relocate(${ARGN}) |
| endif() |
| endmacro() |
| |
| # Helper function for CONFIG_CODE_DATA_RELOCATION |
| # This function may either be invoked with a list of files, or a library |
| # name to relocate. |
| # |
| # The FILES directive will relocate a list of files (wildcards supported) |
| # This directive will relocate file1. and file2.c to SRAM: |
| # zephyr_code_relocate(FILES file1.c file2.c LOCATION SRAM) |
| # Note, files can also be passed as a comma separated list to support using |
| # cmake generator arguments |
| # |
| # The LIBRARY directive will relocate a library |
| # This directive will relocate the target my_lib to SRAM: |
| # zephyr_code_relocate(LIBRARY my_lib SRAM) |
| # |
| # The following optional arguments are supported: |
| # - NOCOPY: this flag indicates that the file data does not need to be copied |
| # at boot time (For example, for flash XIP). |
| # - NOKEEP: suppress the generation of KEEP() statements in the linker script, |
| # to allow any unused code in the given files/library to be discarded. |
| # - PHDR [program_header]: add program header. Used on Xtensa platforms. |
| function(zephyr_code_relocate) |
| set(options NOCOPY NOKEEP) |
| set(single_args LIBRARY LOCATION PHDR) |
| set(multi_args FILES) |
| cmake_parse_arguments(CODE_REL "${options}" "${single_args}" |
| "${multi_args}" ${ARGN}) |
| # Argument validation |
| if(CODE_REL_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_code_relocate(${ARGV0} ...) " |
| "given unknown arguments: ${CODE_REL_UNPARSED_ARGUMENTS}") |
| endif() |
| if((NOT CODE_REL_FILES) AND (NOT CODE_REL_LIBRARY)) |
| message(FATAL_ERROR |
| "zephyr_code_relocate() requires either FILES or LIBRARY be provided") |
| endif() |
| if(CODE_REL_FILES AND CODE_REL_LIBRARY) |
| message(FATAL_ERROR "zephyr_code_relocate() only accepts " |
| "one argument between FILES and LIBRARY") |
| endif() |
| if(NOT CODE_REL_LOCATION) |
| message(FATAL_ERROR "zephyr_code_relocate() requires a LOCATION argument") |
| endif() |
| if(CODE_REL_LIBRARY) |
| # Use cmake generator expression to convert library to file list, |
| # supporting relative and absolute paths |
| set(genex_src_dir "$<TARGET_PROPERTY:${CODE_REL_LIBRARY},SOURCE_DIR>") |
| set(genex_src_list "$<TARGET_PROPERTY:${CODE_REL_LIBRARY},SOURCES>") |
| |
| if(CMAKE_HOST_WIN32) |
| # Note that this assumes windows absolute filenames start with a letter and colon, this does |
| # not support \\x network paths and is untested under the likes of msys2/cygwin |
| set(src_list_abs "$<FILTER:${genex_src_list},INCLUDE,^[A-Za-z]\:>") |
| set(src_list_rel "$<FILTER:${genex_src_list},EXCLUDE,^[A-Za-z]\:>") |
| else() |
| set(src_list_abs "$<FILTER:${genex_src_list},INCLUDE,^/>") |
| set(src_list_rel "$<FILTER:${genex_src_list},EXCLUDE,^/>") |
| endif() |
| |
| set(src_list "${genex_src_dir}/$<JOIN:${src_list_rel},$<SEMICOLON>${genex_src_dir}/>") |
| set(nonempty_src_list "$<$<BOOL:${src_list_rel}>:${src_list}>") |
| set(sep_list "$<$<AND:$<BOOL:${src_list_abs}>,$<BOOL:${src_list_rel}>>:$<SEMICOLON>>") |
| set(file_list "${src_list_abs}${sep_list}${nonempty_src_list}") |
| else() |
| # Check if CODE_REL_FILES is a generator expression, if so leave it |
| # untouched. |
| string(GENEX_STRIP "${CODE_REL_FILES}" no_genex) |
| if(CODE_REL_FILES STREQUAL no_genex) |
| # no generator expression in CODE_REL_FILES, check if list of files |
| # is absolute |
| foreach(file ${CODE_REL_FILES}) |
| if(NOT IS_ABSOLUTE ${file}) |
| set(file ${CMAKE_CURRENT_SOURCE_DIR}/${file}) |
| endif() |
| list(APPEND file_list ${file}) |
| endforeach() |
| else() |
| # Generator expression is present in file list. Leave the list untouched. |
| set(file_list ${CODE_REL_FILES}) |
| endif() |
| endif() |
| if(NOT CODE_REL_NOCOPY) |
| set(flag_list COPY) |
| else() |
| set(flag_list NOCOPY) |
| endif() |
| if(CODE_REL_NOKEEP) |
| list(APPEND flag_list NOKEEP) |
| endif() |
| if(CODE_REL_PHDR) |
| set(CODE_REL_LOCATION "${CODE_REL_LOCATION}\ :${CODE_REL_PHDR}") |
| endif() |
| # We use the "|" character to separate code relocation directives, instead of |
| # using set_property(APPEND) to produce a ";"-separated CMake list. This way, |
| # each directive can embed multiple CMake lists, representing flags and files, |
| # the latter of which can come from generator expressions. |
| get_property(code_rel_str TARGET code_data_relocation_target |
| PROPERTY COMPILE_DEFINITIONS) |
| set_property(TARGET code_data_relocation_target |
| PROPERTY COMPILE_DEFINITIONS |
| "${code_rel_str}|${CODE_REL_LOCATION}:${flag_list}:${file_list}") |
| endfunction() |
| |
| # Usage: |
| # check_dtc_flag("-Wtest" DTC_WARN_TEST) |
| # |
| # Writes 1 to the output variable 'ok' if |
| # the flag is supported, otherwise writes 0. |
| # |
| # using |
| function(check_dtc_flag flag ok) |
| execute_process( |
| COMMAND |
| ${DTC} ${flag} -v |
| ERROR_QUIET |
| OUTPUT_QUIET |
| RESULT_VARIABLE dtc_check_ret |
| ) |
| if (dtc_check_ret EQUAL 0) |
| set(${ok} 1 PARENT_SCOPE) |
| else() |
| set(${ok} 0 PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Function to round number to next power of two. |
| # |
| # Usage: |
| # pow2round(<variable>) |
| # |
| # Example: |
| # set(test 2) |
| # pow2round(test) |
| # # test is still 2 |
| # |
| # set(test 5) |
| # pow2round(test) |
| # # test is now 8 |
| # |
| # Arguments: |
| # n = Variable containing the number to round |
| function(pow2round n) |
| math(EXPR x "${${n}} & (${${n}} - 1)") |
| if(${x} EQUAL 0) |
| return() |
| endif() |
| |
| math(EXPR ${n} "${${n}} | (${${n}} >> 1)") |
| math(EXPR ${n} "${${n}} | (${${n}} >> 2)") |
| math(EXPR ${n} "${${n}} | (${${n}} >> 4)") |
| math(EXPR ${n} "${${n}} | (${${n}} >> 8)") |
| math(EXPR ${n} "${${n}} | (${${n}} >> 16)") |
| math(EXPR ${n} "${${n}} | (${${n}} >> 32)") |
| math(EXPR ${n} "${${n}} + 1") |
| set(${n} ${${n}} PARENT_SCOPE) |
| endfunction() |
| |
| # Function to create a build string based on BOARD, BOARD_REVISION, and BUILD |
| # type. |
| # |
| # This is a common function to ensure that build strings are always created |
| # in a uniform way. |
| # A single string is returned containing the full build string constructed from |
| # all arguments. |
| # |
| # When MERGE is supplied a list of build strings will be returned with the full |
| # build string as first item in the list. |
| # The full order of build strings returned in the list will be: |
| # - Normalized board target build string, this includes qualifiers and revision |
| # - Build string with board variants removed in addition |
| # - Build string with cpuset removed in addition |
| # - Build string with soc removed in addition |
| # |
| # If BUILD is supplied, then build type will be appended to each entry in the |
| # list above. |
| # If REVISION is supplied or obtained as system wide setting a build string |
| # with the sanitized revision string will be added in addition to the |
| # non-revisioned entry for each entry. |
| # |
| # Usage: |
| # zephyr_build_string(<out-variable> |
| # BOARD <board> |
| # [SHORT <out-variable>] |
| # [BOARD_QUALIFIERS <qualifiers>] |
| # [BOARD_REVISION <revision>] |
| # [BUILD <type>] |
| # [MERGE [REVERSE]] |
| # ) |
| # zephyr_build_string(<out-variable> |
| # BOARD_QUALIFIERS <qualifiers> |
| # [BUILD <type>] |
| # [MERGE [REVERSE]] |
| # ) |
| # |
| # <out-variable>: Output variable where the build string will be returned. |
| # SHORT <out-variable>: Output variable where the shortened build string will be returned. |
| # BOARD <board>: Board name to use when creating the build string. |
| # BOARD_REVISION <revision>: Board revision to use when creating the build string. |
| # BUILD <type>: Build type to use when creating the build string. |
| # MERGE: Return a list of build strings instead of a single build string. |
| # REVERSE: Reverse the list before returning it. |
| # |
| # Examples |
| # calling |
| # zephyr_build_string(build_string BOARD alpha BUILD debug) |
| # will return the string `alpha_debug` in `build_string` parameter. |
| # |
| # calling |
| # zephyr_build_string(build_string BOARD alpha BOARD_REVISION 1.0.0 BUILD debug) |
| # will return the string `alpha_1_0_0_debug` in `build_string` parameter. |
| # |
| # calling |
| # zephyr_build_string(build_string BOARD alpha BOARD_QUALIFIERS /soc/bar) |
| # will return the string `alpha_soc_bar` in `build_string` parameter. |
| # |
| # calling |
| # zephyr_build_string(build_string BOARD alpha BOARD_REVISION 1.0.0 BOARD_QUALIFIERS /soc/bar MERGE) |
| # will return a list of the following strings |
| # `alpha_soc_bar_1_0_0;alpha_soc_bar` in `build_string` parameter. |
| # |
| # calling |
| # zephyr_build_string(build_string SHORT short_build_string BOARD alpha BOARD_REVISION 1.0.0 BOARD_QUALIFIERS /soc/bar MERGE) |
| # will return two lists of the following strings |
| # `alpha_soc_bar_1_0_0;alpha_soc_bar` in `build_string` parameter. |
| # `alpha_bar_1_0_0;alpha_bar` in `short_build_string` parameter. |
| # |
| # calling |
| # zephyr_build_string(build_string BOARD_QUALIFIERS /soc/bar/foo) |
| # will return the string `soc_bar_foo` in `build_string` parameter. |
| # |
| function(zephyr_build_string outvar) |
| set(options MERGE REVERSE) |
| set(single_args BOARD BOARD_QUALIFIERS BOARD_REVISION BUILD SHORT) |
| |
| cmake_parse_arguments(BUILD_STR "${options}" "${single_args}" "" ${ARGN}) |
| if(BUILD_STR_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR |
| "zephyr_build_string(${ARGV0} <val> ...) given unknown arguments:" |
| " ${BUILD_STR_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| if(DEFINED BUILD_STR_BOARD_REVISION AND NOT BUILD_STR_BOARD) |
| message(FATAL_ERROR |
| "zephyr_build_string(${ARGV0} <list> BOARD_REVISION ${BUILD_STR_BOARD_REVISION} ...)" |
| " given without BOARD argument, please specify BOARD" |
| ) |
| endif() |
| |
| if(DEFINED BUILD_STR_BOARD_REVISION AND NOT DEFINED BUILD_STR_BOARD) |
| message(FATAL_ERROR |
| "zephyr_build_string(${ARGV0} <list> BOARD_REVISION ${BUILD_STR_BOARD_REVISION} ...)" |
| " given without BOARD argument, these must be used together" |
| ) |
| endif() |
| |
| if(DEFINED BUILD_STR_SHORT AND NOT DEFINED BUILD_STR_BOARD) |
| message(FATAL_ERROR |
| "zephyr_build_string(${ARGV0} <list> SHORT ${BUILD_STR_SHORT} ...)" |
| " given without BOARD argument, these must be used together" |
| ) |
| endif() |
| |
| string(REPLACE "/" ";" str_segment_list "${BUILD_STR_BOARD_QUALIFIERS}") |
| string(REPLACE "." "_" revision_string "${BUILD_STR_BOARD_REVISION}") |
| |
| string(JOIN "_" ${outvar} ${BUILD_STR_BOARD} ${str_segment_list} ${revision_string} ${BUILD_STR_BUILD}) |
| |
| if(BUILD_STR_MERGE) |
| string(JOIN "_" variant_string ${BUILD_STR_BOARD} ${str_segment_list} ${BUILD_STR_BUILD}) |
| |
| if(NOT "${variant_string}" IN_LIST ${outvar}) |
| list(APPEND ${outvar} "${variant_string}") |
| endif() |
| endif() |
| |
| if(BUILD_STR_REVERSE) |
| list(REVERSE ${outvar}) |
| endif() |
| list(REMOVE_DUPLICATES ${outvar}) |
| |
| if(BUILD_STR_SHORT AND BUILD_STR_BOARD_QUALIFIERS) |
| string(REGEX REPLACE "^/[^/]*(.*)" "\\1" shortened_qualifiers "${BOARD_QUALIFIERS}") |
| string(REPLACE "/" ";" str_short_segment_list "${shortened_qualifiers}") |
| string(JOIN "_" ${BUILD_STR_SHORT} |
| ${BUILD_STR_BOARD} ${str_short_segment_list} ${revision_string} ${BUILD_STR_BUILD} |
| ) |
| if(BUILD_STR_MERGE) |
| string(JOIN "_" variant_string ${BUILD_STR_BOARD} ${str_short_segment_list} ${BUILD_STR_BUILD}) |
| |
| if(NOT "${variant_string}" IN_LIST ${BUILD_STR_SHORT}) |
| list(APPEND ${BUILD_STR_SHORT} "${variant_string}") |
| endif() |
| endif() |
| |
| if(BUILD_STR_REVERSE) |
| list(REVERSE ${BUILD_STR_SHORT}) |
| endif() |
| list(REMOVE_DUPLICATES ${BUILD_STR_SHORT}) |
| set(${BUILD_STR_SHORT} ${${BUILD_STR_SHORT}} PARENT_SCOPE) |
| endif() |
| |
| # This updates the provided outvar in parent scope (callers scope) |
| set(${outvar} ${${outvar}} PARENT_SCOPE) |
| endfunction() |
| |
| # Function to add one or more directories to the include list passed to the syscall generator. |
| function(zephyr_syscall_include_directories) |
| foreach(one_dir ${ARGV}) |
| if(EXISTS ${one_dir}) |
| set(include_dir ${one_dir}) |
| elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${one_dir}) |
| set(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/${one_dir}) |
| else() |
| message(FATAL_ERROR "Syscall include directory not found: ${one_dir}") |
| endif() |
| |
| target_include_directories( |
| syscalls_interface INTERFACE |
| ${include_dir} |
| ) |
| add_dependencies( |
| syscalls_interface |
| ${include_dir} |
| ) |
| |
| unset(include_dir) |
| endforeach() |
| endfunction() |
| |
| # Function to add header file(s) to the list to be passed to syscall generator. |
| function(zephyr_syscall_header) |
| foreach(one_file ${ARGV}) |
| if(EXISTS ${one_file}) |
| set(header_file ${one_file}) |
| elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${one_file}) |
| set(header_file ${CMAKE_CURRENT_SOURCE_DIR}/${one_file}) |
| else() |
| message(FATAL_ERROR "Syscall header file not found: ${one_file}") |
| endif() |
| |
| target_sources( |
| syscalls_interface INTERFACE |
| ${header_file} |
| ) |
| add_dependencies( |
| syscalls_interface |
| ${header_file} |
| ) |
| |
| unset(header_file) |
| endforeach() |
| endfunction() |
| |
| # Function to add header file(s) to the list to be passed to syscall generator |
| # if condition is true. |
| function(zephyr_syscall_header_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_syscall_header(${ARGN}) |
| endif() |
| endfunction() |
| |
| # Verify blobs fetched using west. If the sha256 checksum isn't valid, a warning/ |
| # fatal error message is printed (depends on REQUIRED flag). |
| # |
| # Usage: |
| # zephyr_blobs_verify(<MODULE module|FILES file [files...]> [REQUIRED]) |
| # |
| # Example: |
| # zephyr_blobs_verify(MODULE my_module REQUIRED) # verify all blobs in my_module and fail on error |
| # zephyr_blobs_verify(FILES img/file.bin) # verify a single file and print on error |
| function(zephyr_blobs_verify) |
| cmake_parse_arguments(BLOBS_VERIFY "REQUIRED" "MODULE" "FILES" ${ARGN}) |
| |
| if((DEFINED BLOBS_VERIFY_MODULE) EQUAL (DEFINED BLOBS_VERIFY_FILES)) |
| message(FATAL_ERROR "Either MODULE or FILES required when calling ${CMAKE_CURRENT_FUNCTION}") |
| endif() |
| |
| if(NOT WEST) |
| return() |
| endif() |
| |
| execute_process( |
| COMMAND ${WEST} blobs list ${BLOBS_VERIFY_MODULE} --format "{status} {abspath}" |
| OUTPUT_VARIABLE BLOBS_LIST_OUTPUT |
| OUTPUT_STRIP_TRAILING_WHITESPACE |
| COMMAND_ERROR_IS_FATAL ANY |
| ) |
| |
| if(${BLOBS_VERIFY_REQUIRED}) |
| set(msg_lvl FATAL_ERROR) |
| else() |
| set(msg_lvl WARNING) |
| endif() |
| |
| string(REPLACE "\n" ";" BLOBS_LIST ${BLOBS_LIST_OUTPUT}) |
| |
| if(DEFINED BLOBS_VERIFY_FILES) |
| foreach(file ${BLOBS_VERIFY_FILES}) |
| # Resolve path. |
| if(IS_ABSOLUTE ${file}) |
| file(REAL_PATH "${file}" real_path) |
| else() |
| file(REAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${file}" real_path) |
| endif() |
| file(TO_NATIVE_PATH ${real_path} path) |
| |
| message(VERBOSE "Verifying blob \"${path}\"") |
| |
| # Each path that has a correct sha256 is prefixed with an A |
| if(NOT "A ${path}" IN_LIST BLOBS_LIST) |
| message(${msg_lvl} "Blob for path \"${path}\" isn't valid.") |
| endif() |
| endforeach() |
| else() |
| foreach(blob ${BLOBS_LIST}) |
| separate_arguments(blob) |
| list(GET blob 0 status) |
| list(GET blob 1 path) |
| |
| message(VERBOSE "Verifying blob \"${path}\"") |
| |
| if(NOT "${status}" STREQUAL "A") |
| message(${msg_lvl} "Blob for path \"${path}\" isn't valid. Update with: west blobs fetch ${BLOBS_VERIFY_MODULE}") |
| endif() |
| endforeach() |
| endif() |
| endfunction() |
| |
| ######################################################## |
| # 2. Kconfig-aware extensions |
| ######################################################## |
| # |
| # Kconfig is a configuration language developed for the Linux |
| # kernel. The below functions integrate CMake with Kconfig. |
| # |
| |
| # 2.1 Misc |
| # |
| # import_kconfig(<prefix> <kconfig_fragment> [<keys>] [TARGET <target>]) |
| # |
| # Parse a KConfig fragment (typically with extension .config) and |
| # introduce all the symbols that are prefixed with 'prefix' into the |
| # CMake namespace. List all created variable names in the 'keys' |
| # output variable if present. |
| # |
| # <prefix> : symbol prefix of settings in the Kconfig fragment. |
| # <kconfig_fragment>: absolute path to the config fragment file. |
| # <keys> : output variable which will be populated with variable |
| # names loaded from the kconfig fragment. |
| # TARGET <target> : set all symbols on <target> instead of adding them to the |
| # CMake namespace. |
| function(import_kconfig prefix kconfig_fragment) |
| cmake_parse_arguments(IMPORT_KCONFIG "" "TARGET" "" ${ARGN}) |
| file( |
| STRINGS |
| ${kconfig_fragment} |
| DOT_CONFIG_LIST |
| ENCODING "UTF-8" |
| ) |
| |
| foreach (LINE ${DOT_CONFIG_LIST}) |
| if("${LINE}" MATCHES "^(${prefix}[^=]+)=([ymn]|.+$)") |
| # Matched a normal value assignment, like: CONFIG_NET_BUF=y |
| # Note: if the value starts with 'y', 'm', or 'n', then we assume it's a |
| # bool or tristate (we don't know the type from <kconfig_fragment> alone) |
| # and we only match the first character. This is to align with Kconfiglib. |
| set(CONF_VARIABLE_NAME "${CMAKE_MATCH_1}") |
| set(CONF_VARIABLE_VALUE "${CMAKE_MATCH_2}") |
| elseif("${LINE}" MATCHES "^# (${prefix}[^ ]+) is not set") |
| # Matched something like: # CONFIG_FOO is not set |
| # This is interpreted as: CONFIG_FOO=n |
| set(CONF_VARIABLE_NAME "${CMAKE_MATCH_1}") |
| set(CONF_VARIABLE_VALUE "n") |
| else() |
| # Ignore this line. |
| # Note: we also ignore assignments which don't have the desired <prefix>. |
| continue() |
| endif() |
| |
| # If the provided value is n, then the corresponding CMake variable or |
| # target property will be unset. |
| if("${CONF_VARIABLE_VALUE}" STREQUAL "n") |
| if(DEFINED IMPORT_KCONFIG_TARGET) |
| set_property(TARGET ${IMPORT_KCONFIG_TARGET} PROPERTY "${CONF_VARIABLE_NAME}") |
| else() |
| unset("${CONF_VARIABLE_NAME}" PARENT_SCOPE) |
| endif() |
| list(REMOVE_ITEM keys "${CONF_VARIABLE_NAME}") |
| continue() |
| endif() |
| |
| # Otherwise, the variable/property will be set to the provided value. |
| # For string values, we also remove the surrounding quotation marks. |
| if("${CONF_VARIABLE_VALUE}" MATCHES "^\"(.*)\"$") |
| set(CONF_VARIABLE_VALUE ${CMAKE_MATCH_1}) |
| endif() |
| |
| if(DEFINED IMPORT_KCONFIG_TARGET) |
| set_property(TARGET ${IMPORT_KCONFIG_TARGET} PROPERTY "${CONF_VARIABLE_NAME}" "${CONF_VARIABLE_VALUE}") |
| else() |
| set("${CONF_VARIABLE_NAME}" "${CONF_VARIABLE_VALUE}" PARENT_SCOPE) |
| endif() |
| list(APPEND keys "${CONF_VARIABLE_NAME}") |
| endforeach() |
| |
| if(DEFINED IMPORT_KCONFIG_TARGET) |
| set_property(TARGET ${IMPORT_KCONFIG_TARGET} PROPERTY "kconfigs" "${keys}") |
| endif() |
| |
| list(LENGTH IMPORT_KCONFIG_UNPARSED_ARGUMENTS unparsed_length) |
| if(unparsed_length GREATER 0) |
| if(unparsed_length GREATER 1) |
| # Two mandatory arguments and one optional, anything after that is an error. |
| list(GET IMPORT_KCONFIG_UNPARSED_ARGUMENTS 1 first_invalid) |
| message(FATAL_ERROR "Unexpected argument after '<keys>': import_kconfig(... ${first_invalid})") |
| endif() |
| set(${IMPORT_KCONFIG_UNPARSED_ARGUMENTS} "${keys}" PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| ######################################################## |
| # 3. CMake-generic extensions |
| ######################################################## |
| # |
| # These functions extend the CMake API in a way that is not particular |
| # to Zephyr. Primarily they work around limitations in the CMake |
| # language to allow cleaner build scripts. |
| |
| # 3.1. *_ifdef |
| # |
| # Functions for conditionally executing CMake functions with oneliners |
| # e.g. |
| # |
| # if(CONFIG_FFT) |
| # zephyr_library_source( |
| # fft_32.c |
| # fft_utils.c |
| # ) |
| # endif() |
| # |
| # Becomes |
| # |
| # zephyr_source_ifdef( |
| # CONFIG_FFT |
| # fft_32.c |
| # fft_utils.c |
| # ) |
| # |
| # More Generally |
| # "<function-name>_ifdef(CONDITION args)" |
| # Becomes |
| # """ |
| # if(CONDITION) |
| # <function-name>(args) |
| # endif() |
| # """ |
| # |
| # ifdef functions are added on an as-need basis. See |
| # https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html for |
| # a list of available functions. |
| function(add_subdirectory_ifdef feature_toggle source_dir) |
| if(${${feature_toggle}}) |
| add_subdirectory(${source_dir} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_sources_ifdef feature_toggle target scope item) |
| if(${${feature_toggle}}) |
| target_sources(${target} ${scope} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_compile_definitions_ifdef feature_toggle target scope item) |
| if(${${feature_toggle}}) |
| target_compile_definitions(${target} ${scope} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_include_directories_ifdef feature_toggle target scope item) |
| if(${${feature_toggle}}) |
| target_include_directories(${target} ${scope} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_link_libraries_ifdef feature_toggle target item) |
| if(${${feature_toggle}}) |
| target_link_libraries(${target} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(add_compile_option_ifdef feature_toggle option) |
| if(${${feature_toggle}}) |
| add_compile_options(${option}) |
| endif() |
| endfunction() |
| |
| function(target_compile_option_ifdef feature_toggle target scope option) |
| if(${feature_toggle}) |
| target_compile_options(${target} ${scope} ${option}) |
| endif() |
| endfunction() |
| |
| function(target_cc_option_ifdef feature_toggle target scope option) |
| if(${feature_toggle}) |
| target_cc_option(${target} ${scope} ${option}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_sources_ifdef feature_toggle source) |
| if(${${feature_toggle}}) |
| zephyr_library_sources(${source} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_sources_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_sources(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_cc_option_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_cc_option(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_ld_option_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_ld_options(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_link_libraries_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_link_libraries(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_compile_options_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_compile_options(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_compile_definitions_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_compile_definitions(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_include_directories_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_include_directories(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_compile_definitions_ifdef feature_toggle item) |
| if(${${feature_toggle}}) |
| zephyr_library_compile_definitions(${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_include_directories_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_library_include_directories(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_compile_options_ifdef feature_toggle item) |
| if(${${feature_toggle}}) |
| zephyr_library_compile_options(${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_link_interface_ifdef feature_toggle interface) |
| if(${${feature_toggle}}) |
| target_link_libraries(${interface} INTERFACE zephyr_interface) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_link_libraries_ifdef feature_toggle item) |
| if(${${feature_toggle}}) |
| zephyr_library_link_libraries(${item}) |
| endif() |
| endfunction() |
| |
| function(zephyr_linker_sources_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_linker_sources(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_add_dependencies_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_library_add_dependencies(${ARGN}) |
| endif() |
| endfunction() |
| |
| macro(list_append_ifdef feature_toggle list) |
| if(${${feature_toggle}}) |
| list(APPEND ${list} ${ARGN}) |
| endif() |
| endmacro() |
| |
| # 3.2. *_ifndef |
| # See 3.1 *_ifdef |
| function(set_ifndef variable value) |
| if(NOT ${variable}) |
| set(${variable} ${value} ${ARGN} PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| function(add_subdirectory_ifndef feature_toggle source_dir) |
| if(NOT ${feature_toggle}) |
| add_subdirectory(${source_dir} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_sources_ifndef feature_toggle target scope item) |
| if(NOT ${feature_toggle}) |
| target_sources(${target} ${scope} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_compile_definitions_ifndef feature_toggle target scope item) |
| if(NOT ${feature_toggle}) |
| target_compile_definitions(${target} ${scope} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_include_directories_ifndef feature_toggle target scope item) |
| if(NOT ${feature_toggle}) |
| target_include_directories(${target} ${scope} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(target_link_libraries_ifndef feature_toggle target item) |
| if(NOT ${feature_toggle}) |
| target_link_libraries(${target} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(add_compile_option_ifndef feature_toggle option) |
| if(NOT ${feature_toggle}) |
| add_compile_options(${option}) |
| endif() |
| endfunction() |
| |
| function(target_compile_option_ifndef feature_toggle target scope option) |
| if(NOT ${feature_toggle}) |
| target_compile_options(${target} ${scope} ${option}) |
| endif() |
| endfunction() |
| |
| function(target_cc_option_ifndef feature_toggle target scope option) |
| if(NOT ${feature_toggle}) |
| target_cc_option(${target} ${scope} ${option}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_sources_ifndef feature_toggle source) |
| if(NOT ${feature_toggle}) |
| zephyr_library_sources(${source} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_sources_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_sources(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_cc_option_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_cc_option(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_ld_option_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_ld_options(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_link_libraries_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_link_libraries(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_compile_options_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_compile_options(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_compile_definitions_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_compile_definitions(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_include_directories_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_include_directories(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_compile_definitions_ifndef feature_toggle item) |
| if(NOT ${feature_toggle}) |
| zephyr_library_compile_definitions(${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_include_directories_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_library_include_directories(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_compile_options_ifndef feature_toggle item) |
| if(NOT ${feature_toggle}) |
| zephyr_library_compile_options(${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_link_interface_ifndef feature_toggle interface) |
| if(NOT ${feature_toggle}) |
| target_link_libraries(${interface} INTERFACE zephyr_interface) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_link_libraries_ifndef feature_toggle item) |
| if(NOT ${feature_toggle}) |
| zephyr_library_link_libraries(${item}) |
| endif() |
| endfunction() |
| |
| function(zephyr_linker_sources_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_linker_sources(${ARGN}) |
| endif() |
| endfunction() |
| |
| function(zephyr_library_add_dependencies_ifndef feature_toggle) |
| if(NOT ${feature_toggle}) |
| zephyr_library_add_dependencies(${ARGN}) |
| endif() |
| endfunction() |
| |
| macro(list_append_ifndef feature_toggle list) |
| if(NOT ${feature_toggle}) |
| list(APPEND ${list} ${ARGN}) |
| endif() |
| endmacro() |
| |
| # 3.3. *_option Compiler-compatibility checks |
| # |
| # Utility functions for silently omitting compiler flags when the |
| # compiler lacks support. *_cc_option was ported from KBuild, see |
| # cc-option in |
| # https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt |
| |
| # Writes 1 to the output variable 'ok' for the language 'lang' if |
| # the flag is supported, otherwise writes 0. |
| # |
| # lang must be C or CXX |
| # |
| # TODO: Support ASM |
| # |
| # Usage: |
| # |
| # check_compiler_flag(C "-Wall" my_check) |
| # print(my_check) # my_check is now 1 |
| function(check_compiler_flag lang option ok) |
| if(NOT DEFINED CMAKE_REQUIRED_QUIET) |
| set(CMAKE_REQUIRED_QUIET 1) |
| endif() |
| |
| string(MAKE_C_IDENTIFIER |
| "check${option}_${lang}_${CMAKE_REQUIRED_FLAGS}" |
| ${ok} |
| ) |
| |
| if(${lang} STREQUAL C) |
| check_c_compiler_flag("${option}" ${${ok}}) |
| else() |
| check_cxx_compiler_flag("${option}" ${${ok}}) |
| endif() |
| |
| if(${${${ok}}}) |
| set(ret 1) |
| else() |
| set(ret 0) |
| endif() |
| |
| set(${ok} ${ret} PARENT_SCOPE) |
| endfunction() |
| |
| function(target_cc_option target scope option) |
| target_cc_option_fallback(${target} ${scope} ${option} "") |
| endfunction() |
| |
| # Support an optional second option for when the first option is not |
| # supported. |
| function(target_cc_option_fallback target scope option1 option2) |
| if(CONFIG_CPP) |
| foreach(lang C CXX) |
| # For now, we assume that all flags that apply to C/CXX also |
| # apply to ASM. |
| zephyr_check_compiler_flag(${lang} ${option1} check) |
| if(${check}) |
| target_compile_options(${target} ${scope} |
| $<$<COMPILE_LANGUAGE:${lang}>:${option1}> |
| $<$<COMPILE_LANGUAGE:ASM>:${option1}> |
| ) |
| elseif(option2) |
| target_compile_options(${target} ${scope} |
| $<$<COMPILE_LANGUAGE:${lang}>:${option2}> |
| $<$<COMPILE_LANGUAGE:ASM>:${option2}> |
| ) |
| endif() |
| endforeach() |
| else() |
| zephyr_check_compiler_flag(C ${option1} check) |
| if(${check}) |
| target_compile_options(${target} ${scope} ${option1}) |
| elseif(option2) |
| target_compile_options(${target} ${scope} ${option2}) |
| endif() |
| endif() |
| endfunction() |
| |
| function(target_ld_options target scope) |
| zephyr_get_parse_args(args ${ARGN}) |
| list(REMOVE_ITEM ARGN NO_SPLIT) |
| |
| foreach(option ${ARGN}) |
| if(args_NO_SPLIT) |
| set(option ${ARGN}) |
| endif() |
| string(JOIN "" check_identifier "check" ${option}) |
| string(MAKE_C_IDENTIFIER ${check_identifier} check) |
| |
| set(SAVED_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) |
| string(JOIN " " CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS} ${option}) |
| zephyr_check_compiler_flag(C "" ${check}) |
| set(CMAKE_REQUIRED_FLAGS ${SAVED_CMAKE_REQUIRED_FLAGS}) |
| |
| target_link_libraries_ifdef(${check} ${target} ${scope} ${option}) |
| |
| if(args_NO_SPLIT) |
| break() |
| endif() |
| endforeach() |
| endfunction() |
| |
| # 3.3.1 Toolchain integration |
| # |
| # 'toolchain_parse_make_rule' is a function that parses the output of |
| # 'gcc -M'. |
| # |
| # The argument 'input_file' is in input parameter with the path to the |
| # file with the dependency information. |
| # |
| # The argument 'include_files' is an output parameter with the result |
| # of parsing the include files. |
| function(toolchain_parse_make_rule input_file include_files) |
| file(STRINGS ${input_file} input) |
| |
| # The file is formatted like this: |
| # empty_file.o: misc/empty_file.c \ |
| # nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts \ |
| # nrf52840_qiaa.dtsi |
| |
| # The dep file will contain `\` for line continuation. |
| # This results in `\;` which is then treated a the char `;` instead of |
| # the element separator, so let's get the pure `;` back. |
| string(REPLACE "\;" ";" input_as_list ${input}) |
| |
| # Pop the first line and treat it specially |
| list(POP_FRONT input_as_list first_input_line) |
| string(FIND ${first_input_line} ": " index) |
| math(EXPR j "${index} + 2") |
| string(SUBSTRING ${first_input_line} ${j} -1 first_include_file) |
| |
| # Remove whitespace before and after filename and convert to CMake path. |
| string(STRIP "${first_include_file}" first_include_file) |
| file(TO_CMAKE_PATH "${first_include_file}" first_include_file) |
| set(result "${first_include_file}") |
| |
| # Remove whitespace before and after filename and convert to CMake path. |
| foreach(file ${input_as_list}) |
| string(STRIP "${file}" file) |
| file(TO_CMAKE_PATH "${file}" file) |
| list(APPEND result "${file}") |
| endforeach() |
| |
| set(${include_files} ${result} PARENT_SCOPE) |
| endfunction() |
| |
| # 'check_set_linker_property' is a function that check the provided linker |
| # flag and only set the linker property if the check succeeds |
| # |
| # This function is similar in nature to the CMake set_property function, but |
| # with the extension that it will check that the linker supports the flag before |
| # setting the property. |
| # |
| # APPEND: Flag indicated that the property should be appended to the existing |
| # value list for the property. |
| # TARGET: Name of target on which to add the property (commonly: linker) |
| # PROPERTY: Name of property with the value(s) following immediately after |
| # property name |
| function(check_set_linker_property) |
| set(options APPEND) |
| set(single_args TARGET) |
| set(multi_args PROPERTY) |
| cmake_parse_arguments(LINKER_PROPERTY "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| |
| if(LINKER_PROPERTY_APPEND) |
| set(APPEND "APPEND") |
| endif() |
| |
| list(GET LINKER_PROPERTY_PROPERTY 0 property) |
| list(REMOVE_AT LINKER_PROPERTY_PROPERTY 0) |
| set(option ${LINKER_PROPERTY_PROPERTY}) |
| |
| string(MAKE_C_IDENTIFIER check${option} check) |
| |
| set(SAVED_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) |
| set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${option}") |
| zephyr_check_compiler_flag(C "" ${check}) |
| set(CMAKE_REQUIRED_FLAGS ${SAVED_CMAKE_REQUIRED_FLAGS}) |
| |
| if(${${check}}) |
| set_property(TARGET ${LINKER_PROPERTY_TARGET} ${APPEND} PROPERTY ${property} ${option}) |
| endif() |
| endfunction() |
| |
| # 'set_compiler_property' is a function that sets the property for the C and |
| # C++ property targets used for toolchain abstraction. |
| # |
| # This function is similar in nature to the CMake set_property function, but |
| # with the extension that it will set the property on both the compile and |
| # compiler-cpp targets. |
| # |
| # APPEND: Flag indicated that the property should be appended to the existing |
| # value list for the property. |
| # PROPERTY: Name of property with the value(s) following immediately after |
| # property name |
| function(set_compiler_property) |
| set(options APPEND) |
| set(multi_args PROPERTY) |
| cmake_parse_arguments(COMPILER_PROPERTY "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| if(COMPILER_PROPERTY_APPEND) |
| set(APPEND "APPEND") |
| set(APPEND-CPP "APPEND") |
| endif() |
| |
| set_property(TARGET compiler ${APPEND} PROPERTY ${COMPILER_PROPERTY_PROPERTY}) |
| set_property(TARGET compiler-cpp ${APPEND} PROPERTY ${COMPILER_PROPERTY_PROPERTY}) |
| endfunction() |
| |
| # 'check_set_compiler_property' is a function that check the provided compiler |
| # flag and only set the compiler or compiler-cpp property if the check succeeds |
| # |
| # This function is similar in nature to the CMake set_property function, but |
| # with the extension that it will check that the compiler supports the flag |
| # before setting the property on compiler or compiler-cpp targets. |
| # |
| # To test flags together, such as '-Wformat -Wformat-security', an option group |
| # can be specified by using shell-like quoting along with a 'SHELL:' prefix. |
| # The 'SHELL:' prefix will be dropped before testing, so that |
| # '"SHELL:-Wformat -Wformat-security"' becomes '-Wformat -Wformat-security' for |
| # testing. |
| # |
| # APPEND: Flag indicated that the property should be appended to the existing |
| # value list for the property. |
| # PROPERTY: Name of property with the value(s) following immediately after |
| # property name |
| function(check_set_compiler_property) |
| set(options APPEND) |
| set(multi_args PROPERTY) |
| cmake_parse_arguments(COMPILER_PROPERTY "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| if(COMPILER_PROPERTY_APPEND) |
| set(APPEND "APPEND") |
| set(APPEND-CPP "APPEND") |
| endif() |
| |
| list(GET COMPILER_PROPERTY_PROPERTY 0 property) |
| list(REMOVE_AT COMPILER_PROPERTY_PROPERTY 0) |
| |
| foreach(option ${COMPILER_PROPERTY_PROPERTY}) |
| if(${option} MATCHES "^SHELL:") |
| string(REGEX REPLACE "^SHELL:" "" option ${option}) |
| separate_arguments(option UNIX_COMMAND ${option}) |
| endif() |
| |
| if(CONFIG_CPP) |
| zephyr_check_compiler_flag(CXX "${option}" check) |
| |
| if(${check}) |
| set_property(TARGET compiler-cpp ${APPEND-CPP} PROPERTY ${property} ${option}) |
| set(APPEND-CPP "APPEND") |
| endif() |
| endif() |
| |
| zephyr_check_compiler_flag(C "${option}" check) |
| |
| if(${check}) |
| set_property(TARGET compiler ${APPEND} PROPERTY ${property} ${option}) |
| set(APPEND "APPEND") |
| endif() |
| endforeach() |
| endfunction() |
| |
| |
| # 3.4. Debugging CMake |
| |
| # Usage: |
| # print(BOARD) |
| # |
| # will print: "BOARD: nrf52dk" |
| function(print arg) |
| message(STATUS "${arg}: ${${arg}}") |
| endfunction() |
| |
| # Usage: |
| # assert(ZEPHYR_TOOLCHAIN_VARIANT "ZEPHYR_TOOLCHAIN_VARIANT not set.") |
| # |
| # will cause a FATAL_ERROR and print an error message if the first |
| # expression is false |
| macro(assert test comment) |
| if(NOT ${test}) |
| message(FATAL_ERROR "Assertion failed: ${comment}") |
| endif() |
| endmacro() |
| |
| # Usage: |
| # assert_not(OBSOLETE_VAR "OBSOLETE_VAR has been removed; use NEW_VAR instead") |
| # |
| # will cause a FATAL_ERROR and print an error message if the first |
| # expression is true |
| macro(assert_not test comment) |
| if(${test}) |
| message(FATAL_ERROR "Assertion failed: ${comment}") |
| endif() |
| endmacro() |
| |
| # Usage: |
| # assert_exists(CMAKE_READELF) |
| # |
| # will cause a FATAL_ERROR if there is no file or directory behind the |
| # variable |
| macro(assert_exists var) |
| if(NOT EXISTS ${${var}}) |
| message(FATAL_ERROR "No such file or directory: ${var}: '${${var}}'") |
| endif() |
| endmacro() |
| |
| # 3.5. File system management |
| function(generate_unique_target_name_from_filename filename target_name) |
| get_filename_component(basename ${filename} NAME) |
| string(REPLACE "." "_" x ${basename}) |
| string(REPLACE "@" "_" x ${x}) |
| |
| string(MD5 unique_chars ${filename}) |
| |
| set(${target_name} gen_${x}_${unique_chars} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # zephyr_file(<mode> <arg> ...) |
| # |
| # Zephyr file function extension. |
| # This function currently supports the following <modes> |
| # |
| # APPLICATION_ROOT <path>: Check all paths in provided variable, and convert |
| # those paths that are defined with `-D<path>=<val>` |
| # to absolute path, relative from `APPLICATION_SOURCE_DIR` |
| # Issue an error for any relative path not specified |
| # by user with `-D<path>` |
| # |
| # returns an updated list of absolute paths |
| # |
| # Usage: |
| # zephyr_file(CONF_FILES <paths> [DTS <list>] [KCONF <list>] |
| # [BOARD <board> [BOARD_REVISION <revision>] | NAMES <name> ...] |
| # [BUILD <type>] [SUFFIX <suffix>] [REQUIRED] |
| # ) |
| # |
| # CONF_FILES <paths>: Find all configuration files in the list of paths and |
| # return them in a list. If paths is empty then no configuration |
| # files are returned. Configuration files will be: |
| # - DTS: Overlay files (.overlay) |
| # - Kconfig: Config fragments (.conf) |
| # - defconfig: defconfig files (_defconfig) |
| # The conf file search will return existing configuration |
| # files for the current board. |
| # CONF_FILES takes the following additional arguments: |
| # BOARD <board>: Find configuration files for specified board. |
| # BOARD_REVISION <revision>: Find configuration files for specified board |
| # revision. Requires BOARD to be specified. |
| # |
| # If no board is given the current BOARD and |
| # BOARD_REVISION will be used, unless NAMES are |
| # specified. |
| # |
| # NAMES <name1> [name2] ... List of file names to look for and instead of |
| # creating file names based on board settings. |
| # Only the first match found in <paths> will be |
| # returned in the <list> |
| # DTS <list>: List to append DTS overlay files in <path> to |
| # KCONF <list>: List to append Kconfig fragment files in <path> to |
| # DEFCONF <list>: List to append _defconfig files in <path> to |
| # BUILD <type>: Build type to include for search. |
| # For example: |
| # BUILD debug, will look for <board>_debug.conf |
| # and <board>_debug.overlay, instead of <board>.conf |
| # SUFFIX <name>: Suffix name to check for instead of the default name |
| # but with a fallback to the default name if not found. |
| # For example: |
| # SUFFIX fish, will look for <file>_fish.conf and use |
| # if found but will use <file>.conf if not found |
| # REQUIRED: Option to indicate that the <list> specified by DTS or KCONF |
| # must contain at least one element, else an error will be raised. |
| # |
| function(zephyr_file) |
| set(file_options APPLICATION_ROOT CONF_FILES) |
| if((ARGC EQUAL 0) OR (NOT (ARGV0 IN_LIST file_options))) |
| message(FATAL_ERROR "No <mode> given to `zephyr_file(<mode> <args>...)` function,\n \ |
| Please provide one of following: APPLICATION_ROOT, CONF_FILES") |
| endif() |
| |
| if(${ARGV0} STREQUAL APPLICATION_ROOT) |
| set(single_args APPLICATION_ROOT) |
| elseif(${ARGV0} STREQUAL CONF_FILES) |
| set(options QUALIFIERS REQUIRED) |
| set(single_args BOARD BOARD_REVISION BOARD_QUALIFIERS DTS KCONF DEFCONFIG BUILD SUFFIX) |
| set(multi_args CONF_FILES NAMES) |
| endif() |
| |
| cmake_parse_arguments(ZFILE "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| if(ZFILE_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_file(${ARGV0} <val> ...) given unknown arguments: ${ZFILE_UNPARSED_ARGUMENTS}") |
| endif() |
| |
| if(ZFILE_APPLICATION_ROOT) |
| # Note: user can do: `-D<var>=<relative-path>` and app can at same |
| # time specify `list(APPEND <var> <abs-path>)` |
| # Thus need to check and update only CACHED variables (-D<var>). |
| set(CACHED_PATH $CACHE{${ZFILE_APPLICATION_ROOT}}) |
| foreach(path ${CACHED_PATH}) |
| # The cached variable is relative path, i.e. provided by `-D<var>` or |
| # `set(<var> CACHE)`, so let's update current scope variable to absolute |
| # path from `APPLICATION_SOURCE_DIR`. |
| if(NOT IS_ABSOLUTE ${path}) |
| set(abs_path ${APPLICATION_SOURCE_DIR}/${path}) |
| list(FIND ${ZFILE_APPLICATION_ROOT} ${path} index) |
| if(NOT ${index} LESS 0) |
| list(REMOVE_AT ${ZFILE_APPLICATION_ROOT} ${index}) |
| list(INSERT ${ZFILE_APPLICATION_ROOT} ${index} ${abs_path}) |
| endif() |
| endif() |
| endforeach() |
| |
| # Now all cached relative paths has been updated. |
| # Let's check if anyone uses relative path as scoped variable, and fail |
| foreach(path ${${ZFILE_APPLICATION_ROOT}}) |
| if(NOT IS_ABSOLUTE ${path}) |
| message(FATAL_ERROR |
| "Relative path encountered in scoped variable: ${ZFILE_APPLICATION_ROOT}, value=${path}\n \ |
| Please adjust any `set(${ZFILE_APPLICATION_ROOT} ${path})` or `list(APPEND ${ZFILE_APPLICATION_ROOT} ${path})`\n \ |
| to absolute path using `\${CMAKE_CURRENT_SOURCE_DIR}/${path}` or similar. \n \ |
| Relative paths are only allowed with `-D${ARGV1}=<path>`") |
| endif() |
| endforeach() |
| |
| # This updates the provided argument in parent scope (callers scope) |
| set(${ZFILE_APPLICATION_ROOT} ${${ZFILE_APPLICATION_ROOT}} PARENT_SCOPE) |
| endif() |
| |
| if(ZFILE_CONF_FILES) |
| if(DEFINED ZFILE_BOARD_REVISION AND NOT ZFILE_BOARD) |
| message(FATAL_ERROR |
| "zephyr_file(${ARGV0} <path> BOARD_REVISION ${ZFILE_BOARD_REVISION} ...)" |
| " given without BOARD argument, please specify BOARD" |
| ) |
| endif() |
| |
| if(NOT DEFINED ZFILE_BOARD) |
| # Defaulting to system wide settings when BOARD is not given as argument |
| set(ZFILE_BOARD ${BOARD}) |
| if(DEFINED BOARD_REVISION) |
| set(ZFILE_BOARD_REVISION ${BOARD_REVISION}) |
| endif() |
| |
| if(DEFINED BOARD_QUALIFIERS) |
| set(ZFILE_BOARD_QUALIFIERS ${BOARD_QUALIFIERS}) |
| endif() |
| endif() |
| |
| if(ZFILE_NAMES) |
| set(dts_filename_list ${ZFILE_NAMES}) |
| set(kconf_filename_list ${ZFILE_NAMES}) |
| else() |
| if(NOT ZFILE_QUALIFIERS) |
| zephyr_build_string(filename_list |
| SHORT shortened_filename_list |
| BOARD ${ZFILE_BOARD} |
| BOARD_REVISION ${ZFILE_BOARD_REVISION} |
| BOARD_QUALIFIERS ${ZFILE_BOARD_QUALIFIERS} |
| BUILD ${ZFILE_BUILD} |
| MERGE REVERSE |
| ) |
| else() |
| zephyr_build_string(filename_list |
| BOARD_QUALIFIERS ${ZFILE_BOARD_QUALIFIERS} |
| BUILD ${ZFILE_BUILD} |
| MERGE REVERSE |
| ) |
| endif() |
| |
| set(dts_filename_list ${filename_list}) |
| set(dts_shortened_filename_list ${shortened_filename_list}) |
| list(TRANSFORM dts_filename_list APPEND ".overlay") |
| list(TRANSFORM dts_shortened_filename_list APPEND ".overlay") |
| |
| set(kconf_filename_list ${filename_list}) |
| set(kconf_shortened_filename_list ${shortened_filename_list}) |
| list(TRANSFORM kconf_filename_list APPEND ".conf") |
| list(TRANSFORM kconf_shortened_filename_list APPEND ".conf") |
| endif() |
| |
| if(ZFILE_DTS) |
| foreach(path ${ZFILE_CONF_FILES}) |
| foreach(filename IN ZIP_LISTS dts_filename_list dts_shortened_filename_list) |
| foreach(i RANGE 1) |
| if(NOT IS_ABSOLUTE filename_${i} AND DEFINED filename_${i}) |
| set(test_file_${i} ${path}/${filename_${i}}) |
| else() |
| set(test_file_${i} ${filename_${i}}) |
| endif() |
| zephyr_file_suffix(test_file_${i} SUFFIX ${ZFILE_SUFFIX}) |
| |
| if(NOT EXISTS ${test_file_${i}}) |
| set(test_file_${i}) |
| endif() |
| endforeach() |
| |
| if(test_file_0 OR test_file_1) |
| list(APPEND found_dts_files ${test_file_0}) |
| list(APPEND found_dts_files ${test_file_1}) |
| |
| if(DEFINED ZFILE_BUILD) |
| set(deprecated_file_found y) |
| endif() |
| |
| if(ZFILE_NAMES) |
| break() |
| endif() |
| endif() |
| |
| if(test_file_1 AND NOT BOARD_${ZFILE_BOARD}_SINGLE_SOC) |
| message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name " |
| "(${filename_1}) not allowed, use '<board>_<soc>.overlay' naming" |
| ) |
| endif() |
| |
| if(test_file_0 AND test_file_1) |
| message(FATAL_ERROR "Conflicting file names discovered. Cannot use both ${filename_0} " |
| "and ${filename_1}. Please choose one naming style, " |
| "${filename_0} is recommended." |
| ) |
| endif() |
| endforeach() |
| endforeach() |
| |
| list(APPEND ${ZFILE_DTS} ${found_dts_files}) |
| |
| # This updates the provided list in parent scope (callers scope) |
| set(${ZFILE_DTS} ${${ZFILE_DTS}} PARENT_SCOPE) |
| endif() |
| |
| if(ZFILE_KCONF) |
| set(found_conf_files) |
| foreach(path ${ZFILE_CONF_FILES}) |
| foreach(filename IN ZIP_LISTS kconf_filename_list kconf_shortened_filename_list) |
| foreach(i RANGE 1) |
| if(NOT IS_ABSOLUTE filename_${i} AND DEFINED filename_${i}) |
| set(test_file_${i} ${path}/${filename_${i}}) |
| else() |
| set(test_file_${i} ${filename_${i}}) |
| endif() |
| zephyr_file_suffix(test_file_${i} SUFFIX ${ZFILE_SUFFIX}) |
| |
| if(NOT EXISTS ${test_file_${i}}) |
| set(test_file_${i}) |
| endif() |
| endforeach() |
| |
| if(test_file_0 OR test_file_1) |
| list(APPEND found_conf_files ${test_file_0}) |
| list(APPEND found_conf_files ${test_file_1}) |
| |
| if(DEFINED ZFILE_BUILD) |
| set(deprecated_file_found y) |
| endif() |
| |
| if(ZFILE_NAMES) |
| break() |
| endif() |
| endif() |
| |
| if(test_file_1 AND NOT BOARD_${ZFILE_BOARD}_SINGLE_SOC) |
| message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name " |
| "(${filename_1}) not allowed, use '<board>_<soc>.conf' naming" |
| ) |
| endif() |
| |
| if(test_file_0 AND test_file_1) |
| message(FATAL_ERROR "Conflicting file names discovered. Cannot use both ${filename_0} " |
| "and ${filename_1}. Please choose one naming style, " |
| "${filename_0} is recommended." |
| ) |
| endif() |
| endforeach() |
| endforeach() |
| |
| list(APPEND ${ZFILE_KCONF} ${found_conf_files}) |
| |
| # This updates the provided list in parent scope (callers scope) |
| set(${ZFILE_KCONF} ${${ZFILE_KCONF}} PARENT_SCOPE) |
| |
| if(NOT ${ZFILE_KCONF}) |
| set(not_found ${kconf_filename_list}) |
| endif() |
| endif() |
| |
| if(ZFILE_REQUIRED AND DEFINED not_found) |
| message(FATAL_ERROR |
| "No ${not_found} file(s) was found in the ${ZFILE_CONF_FILES} folder(s), " |
| "please read the Zephyr documentation on application development." |
| ) |
| endif() |
| |
| if(deprecated_file_found) |
| message(DEPRECATION "prj_<build>.conf was deprecated after Zephyr 3.5," |
| " you should switch to using -DFILE_SUFFIX instead") |
| endif() |
| |
| if(ZFILE_DEFCONFIG) |
| set(found_defconf_files) |
| foreach(path ${ZFILE_CONF_FILES}) |
| foreach(filename IN ZIP_LISTS filename_list shortened_filename_list) |
| foreach(i RANGE 1) |
| set(test_file_${i} ${path}/${filename_${i}}_defconfig) |
| |
| if(EXISTS ${test_file_${i}}) |
| list(APPEND found_defconf_files ${test_file_${i}}) |
| else() |
| set(test_file_${i}) |
| endif() |
| endforeach() |
| |
| if(test_file_1 AND NOT BOARD_${ZFILE_BOARD}_SINGLE_SOC) |
| message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name " |
| "(${filename_1}_defconfig) not allowed, use '<board>_<soc>_defconfig' naming" |
| ) |
| endif() |
| |
| if(test_file_0 AND test_file_1) |
| message(FATAL_ERROR "Conflicting file names discovered. Cannot use both " |
| "${filename_0}_defconfig and ${filename_1}_defconfig. Please choose one " |
| "naming style, ${filename_0}_defconfig is recommended." |
| ) |
| endif() |
| endforeach() |
| endforeach() |
| list(APPEND ${ZFILE_DEFCONFIG} ${found_defconf_files}) |
| |
| # This updates the provided list in parent scope (callers scope) |
| set(${ZFILE_DEFCONFIG} ${${ZFILE_DEFCONFIG}} PARENT_SCOPE) |
| endif() |
| endif() |
| endfunction() |
| |
| # Usage: |
| # zephyr_file_copy(<oldname> <newname> [ONLY_IF_DIFFERENT]) |
| # |
| # Zephyr file copy extension. |
| # This function is similar to CMake function |
| # 'file(COPY_FILE <oldname> <newname> [ONLY_IF_DIFFERENT])' |
| # introduced with CMake 3.21. |
| # |
| # Because the minimal required CMake version with Zephyr is 3.20, this function |
| # is not guaranteed to be available. |
| # |
| # When using CMake version 3.21 or newer 'zephyr_file_copy()' simply calls |
| # 'file(COPY_FILE...)' directly. |
| # When using CMake version 3.20, the implementation will execute using CMake |
| # for running command line tool in a subprocess for identical functionality. |
| function(zephyr_file_copy oldname newname) |
| set(options ONLY_IF_DIFFERENT) |
| cmake_parse_arguments(ZEPHYR_FILE_COPY "${options}" "" "" ${ARGN}) |
| |
| if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.21.0) |
| if(ZEPHYR_FILE_COPY_ONLY_IF_DIFFERENT) |
| set(copy_file_options ONLY_IF_DIFFERENT) |
| endif() |
| file(COPY_FILE ${oldname} ${newname} ${copy_file_options}) |
| else() |
| if(ZEPHYR_FILE_COPY_ONLY_IF_DIFFERENT) |
| set(copy_file_command copy_if_different) |
| else() |
| set(copy_file_command copy) |
| endif() |
| execute_process( |
| COMMAND ${CMAKE_COMMAND} -E ${copy_file_command} ${oldname} ${newname} |
| ) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # zephyr_file_suffix(<filename> SUFFIX <suffix>) |
| # |
| # Zephyr file add suffix extension. |
| # This function will check the provied filename or list of filenames to see if they have a |
| # `_<suffix>` extension to them and if so, updates the supplied variable/list with the new |
| # path/paths. |
| # |
| # <filename>: Variable (singlular or list) of absolute path filename(s) which should be checked |
| # and updated if there is a filename which has the <suffix> present. |
| # <suffix>: The suffix to test for and append to the end of the provided filename. |
| # |
| # Returns an updated variable of absolute path(s) |
| # |
| function(zephyr_file_suffix filename) |
| set(single_args SUFFIX) |
| cmake_parse_arguments(SFILE "" "${single_args}" "" ${ARGN}) |
| |
| if(NOT DEFINED SFILE_SUFFIX OR NOT DEFINED ${filename}) |
| # If the file suffix variable is not known then there is nothing to do, return early |
| return() |
| endif() |
| |
| set(tmp_new_list) |
| |
| foreach(file ${${filename}}) |
| if("${file}" STREQUAL "") |
| # Skip checking empty variables |
| continue() |
| endif() |
| |
| # Search for the full stop so we know where to add the file suffix before the file extension |
| cmake_path(GET file EXTENSION file_ext) |
| cmake_path(REMOVE_EXTENSION file OUTPUT_VARIABLE new_filename) |
| cmake_path(APPEND_STRING new_filename "_${SFILE_SUFFIX}${file_ext}") |
| |
| # Use the filename with the suffix if it exists, if not then fall back to the default |
| if(EXISTS "${new_filename}") |
| list(APPEND tmp_new_list ${new_filename}) |
| else() |
| list(APPEND tmp_new_list ${file}) |
| endif() |
| endforeach() |
| |
| # Update supplied variable if it differs |
| if(NOT "${${filename}}" STREQUAL "${tmp_new_list}") |
| set(${filename} "${tmp_new_list}" PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # zephyr_string(<mode> <out-var> <input> ...) |
| # |
| # Zephyr string function extension. |
| # This function extends the CMake string function by providing additional |
| # manipulation arguments to CMake string. |
| # |
| # SANITIZE: Ensure that the output string does not contain any special |
| # characters. Special characters, such as -, +, =, $, etc. are |
| # converted to underscores '_'. |
| # |
| # SANITIZE TOUPPER: Ensure that the output string does not contain any special |
| # characters. Special characters, such as -, +, =, $, etc. are |
| # converted to underscores '_'. |
| # The sanitized string will be returned in UPPER case. |
| # |
| # returns the updated string |
| function(zephyr_string) |
| set(options SANITIZE TOUPPER) |
| cmake_parse_arguments(ZEPHYR_STRING "${options}" "" "" ${ARGN}) |
| |
| if (NOT ZEPHYR_STRING_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "Function zephyr_string() called without a return variable") |
| endif() |
| |
| list(GET ZEPHYR_STRING_UNPARSED_ARGUMENTS 0 return_arg) |
| list(REMOVE_AT ZEPHYR_STRING_UNPARSED_ARGUMENTS 0) |
| |
| list(JOIN ZEPHYR_STRING_UNPARSED_ARGUMENTS "" work_string) |
| |
| if(ZEPHYR_STRING_SANITIZE) |
| string(REGEX REPLACE "[^a-zA-Z0-9_]" "_" work_string ${work_string}) |
| endif() |
| |
| if(ZEPHYR_STRING_TOUPPER) |
| string(TOUPPER ${work_string} work_string) |
| endif() |
| |
| set(${return_arg} ${work_string} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # zephyr_list(TRANSFORM <list> <ACTION> |
| # [OUTPUT_VARIABLE <output variable]) |
| # |
| # Example: |
| # |
| # zephyr_list(TRANSFORM my_input_var NORMALIZE_PATHS |
| # OUTPUT_VARIABLE my_input_as_list) |
| # |
| # Like CMake's list(TRANSFORM ...). This is intended as a placeholder |
| # for storing current and future Zephyr-related extensions for list |
| # processing. |
| # |
| # <ACTION>: This currently must be NORMALIZE_PATHS. This action |
| # converts the argument list <list> to a ;-list with |
| # CMake path names, after passing its contents through |
| # a configure_file() transformation. The input list |
| # may be whitespace- or semicolon-separated. |
| # |
| # OUTPUT_VARIABLE: the result is normally stored in place, but |
| # an alternative variable to store the result |
| # can be provided with this. |
| function(zephyr_list transform list_var action) |
| # Parse arguments. |
| if(NOT "${transform}" STREQUAL "TRANSFORM") |
| message(FATAL_ERROR "the first argument must be TRANSFORM") |
| endif() |
| if(NOT "${action}" STREQUAL "NORMALIZE_PATHS") |
| message(FATAL_ERROR "the third argument must be NORMALIZE_PATHS") |
| endif() |
| set(single_args OUTPUT_VARIABLE) |
| cmake_parse_arguments(ZEPHYR_LIST "" "${single_args}" "" ${ARGN}) |
| if(DEFINED ZEPHYR_LIST_OUTPUT_VARIABLE) |
| set(out_var ${ZEPHYR_LIST_OUTPUT_VARIABLE}) |
| else() |
| set(out_var ${list_var}) |
| endif() |
| set(input ${${list_var}}) |
| |
| # Perform the transformation. |
| set(ret) |
| string(CONFIGURE "${input}" input_expanded) |
| string(REPLACE " " ";" input_raw_list "${input_expanded}") |
| foreach(file ${input_raw_list}) |
| file(TO_CMAKE_PATH "${file}" cmake_path_file) |
| list(APPEND ret ${cmake_path_file}) |
| endforeach() |
| |
| set(${out_var} ${ret} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # zephyr_var_name(<variable> <scope> <out>) |
| # |
| # Internal function for construction of scoped variable name expansion string. |
| # Examples: |
| # reading a current scope FOO variable is identical to expand ${FOO}. |
| # reading a cache scope FOO variable is identical to expand $CACHE{FOO}. |
| # |
| # this functions will return the var name in out var for the scope if it is |
| # defined, else it will set the outvar to undefined. |
| function(zephyr_var_name variable scope out) |
| if(scope STREQUAL "ENV" OR scope STREQUAL "CACHE") |
| if(DEFINED ${scope}{${variable}}) |
| set(${out} "$${scope}{${variable}}" PARENT_SCOPE) |
| else() |
| set(${out} PARENT_SCOPE) |
| endif() |
| else() |
| if(DEFINED ${scope}_${variable}) |
| set(${out} "${${scope}_${variable}}" PARENT_SCOPE) |
| else() |
| set(${out} PARENT_SCOPE) |
| endif() |
| endif() |
| endfunction() |
| |
| # Usage: |
| # zephyr_get(<variable> [MERGE [REVERSE]] [SYSBUILD [LOCAL|GLOBAL]] [VAR <var1> ...]) |
| # |
| # Return the value of <variable> as local scoped variable of same name. If MERGE |
| # is supplied, will return a list of found items. If REVERSE is supplied |
| # together with MERGE, the order of the list will be reversed before being |
| # returned. Reverse will happen before the list is returned and hence it will |
| # not change the order of precedence in which the list itself is constructed. |
| # |
| # VAR can be used either to store the result in a variable with a different |
| # name, or to look for values from multiple variables. |
| # zephyr_get(FOO VAR FOO_A FOO_B) |
| # zephyr_get(FOO MERGE VAR FOO_A FOO_B) |
| # |
| # zephyr_get() is a common function to provide a uniform way of supporting |
| # build settings that can be set from sysbuild, CMakeLists.txt, CMake cache, or |
| # in environment. |
| # |
| # The order of precedence for variables defined in multiple scopes: |
| # - 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. |
| # If using MERGE then SYSBUILD GLOBAL will get both the local and global |
| # sysbuild scope variables (in that order, if both exist). |
| # - CMake cache, set by `-D<var>=<value>` or `set(<var> <val> CACHE ...) |
| # - Environment |
| # - Locally in CMakeLists.txt before 'find_package(Zephyr)' |
| # |
| # For example, if ZEPHYR_TOOLCHAIN_VARIANT is set in environment but locally |
| # overridden by setting ZEPHYR_TOOLCHAIN_VARIANT directly in the CMake cache |
| # using `-DZEPHYR_TOOLCHAIN_VARIANT=<val>`, then the value from the cache is |
| # returned. |
| function(zephyr_get variable) |
| cmake_parse_arguments(GET_VAR "MERGE;REVERSE" "SYSBUILD" "VAR" ${ARGN}) |
| |
| 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(GET_VAR_REVERSE AND NOT GET_VAR_MERGE) |
| message(FATAL_ERROR "zephyr_get(... REVERSE) missing a required argument: MERGE") |
| endif() |
| |
| if(NOT DEFINED GET_VAR_VAR) |
| set(GET_VAR_VAR ${variable}) |
| endif() |
| |
| # Keep current scope variables in internal variables. |
| # This is needed to properly handle cases where we want to check value against |
| # environment value or when appending with the MERGE operation. |
| foreach(var ${GET_VAR_VAR}) |
| set(current_${var} ${${var}}) |
| set(${var}) |
| |
| 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_local_${var} TARGET sysbuild_cache PROPERTY ${sysbuild_name}_${var}) |
| get_property(sysbuild_global_${var} TARGET sysbuild_cache PROPERTY ${var}) |
| if(NOT DEFINED sysbuild_local_${var} AND sysbuild_main_app) |
| set(sysbuild_local_${var} ${sysbuild_global_${var}}) |
| endif() |
| if(NOT "${GET_VAR_SYSBUILD}" STREQUAL "GLOBAL") |
| set(sysbuild_global_${var}) |
| endif() |
| else() |
| set(sysbuild_local_${var}) |
| set(sysbuild_global_${var}) |
| endif() |
| |
| if(TARGET snippets_scope) |
| get_property(snippets_${var} TARGET snippets_scope PROPERTY ${var}) |
| endif() |
| endforeach() |
| |
| set(${variable} "") |
| set(scopes "sysbuild_local;sysbuild_global;CACHE;snippets;ENV;current") |
| if(GET_VAR_REVERSE) |
| list(REVERSE scopes) |
| endif() |
| foreach(scope IN LISTS scopes) |
| foreach(var ${GET_VAR_VAR}) |
| zephyr_var_name("${var}" "${scope}" expansion_var) |
| if(DEFINED expansion_var) |
| string(CONFIGURE "${expansion_var}" scope_value) |
| if(GET_VAR_MERGE) |
| list(APPEND ${variable} ${scope_value}) |
| else() |
| set(${variable} ${scope_value} PARENT_SCOPE) |
| |
| if("${scope}" STREQUAL "ENV") |
| # Set the environment variable in CMake cache, so that a build |
| # invocation triggering a CMake rerun doesn't rely on the |
| # environment variable still being available / have identical value. |
| set(${var} $ENV{${var}} CACHE INTERNAL "Cached environment variable ${var}") |
| endif() |
| |
| if("${scope}" STREQUAL "ENV" AND DEFINED current_${var} |
| AND NOT "${current_${var}}" STREQUAL "$ENV{${var}}" |
| ) |
| # Variable exists as current scoped variable, defined in a CMakeLists.txt |
| # file, however it is also set in environment. |
| # This might be a surprise to the user, so warn about it. |
| message(WARNING "environment variable '${var}' is hiding local " |
| "variable of same name.\n" |
| "Environment value (in use): $ENV{${var}}\n" |
| "Current scope value (hidden): ${current_${var}}\n" |
| ) |
| endif() |
| |
| return() |
| endif() |
| endif() |
| endforeach() |
| endforeach() |
| |
| if(GET_VAR_MERGE) |
| if(GET_VAR_REVERSE) |
| list(REVERSE ${variable}) |
| list(REMOVE_DUPLICATES ${variable}) |
| list(REVERSE ${variable}) |
| else() |
| list(REMOVE_DUPLICATES ${variable}) |
| endif() |
| set(${variable} ${${variable}} PARENT_SCOPE) |
| endif() |
| endfunction(zephyr_get variable) |
| |
| # Usage: |
| # zephyr_create_scope(<scope>) |
| # |
| # Create a new scope for creation of scoped variables. |
| # |
| # <scope>: Name of new scope. |
| # |
| function(zephyr_create_scope scope) |
| if(TARGET ${scope}_scope) |
| message(FATAL_ERROR "zephyr_create_scope(${scope}) already exists.") |
| endif() |
| |
| add_custom_target(${scope}_scope) |
| endfunction() |
| |
| # Usage: |
| # zephyr_set(<variable> <value> SCOPE <scope> [APPEND]) |
| # |
| # Zephyr extension of CMake set which allows a variable to be set in a specific |
| # scope. The scope is used on later zephyr_get() invocation for precedence |
| # handling when a variable it set in multiple scopes. |
| # |
| # <variable> : Name of variable |
| # <value> : Value of variable, multiple values will create a list. |
| # The SCOPE argument identifies the end of value list. |
| # SCOPE <scope>: Name of scope for the variable |
| # APPEND : Append values to the already existing variable in <scope> |
| # |
| function(zephyr_set variable) |
| cmake_parse_arguments(SET_VAR "APPEND" "SCOPE" "" ${ARGN}) |
| |
| zephyr_check_arguments_required_all(zephyr_set SET_VAR SCOPE) |
| |
| if(NOT TARGET ${SET_VAR_SCOPE}_scope) |
| message(FATAL_ERROR "zephyr_set(... SCOPE ${SET_VAR_SCOPE}) doesn't exists.") |
| endif() |
| |
| if(SET_VAR_APPEND) |
| set(property_args APPEND) |
| endif() |
| |
| set_property(TARGET ${SET_VAR_SCOPE}_scope ${property_args} |
| PROPERTY ${variable} ${SET_VAR_UNPARSED_ARGUMENTS} |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_check_cache(<variable> [REQUIRED]) |
| # |
| # Check the current CMake cache for <variable> and warn the user if the value |
| # is being modified. |
| # |
| # This can be used to ensure the user does not accidentally try to change |
| # Zephyr build variables, such as: |
| # - BOARD |
| # - SHIELD |
| # |
| # variable: Name of <variable> to check and set, for example BOARD. |
| # REQUIRED: Optional flag. If specified, then an unset <variable> will be |
| # treated as an error. |
| # WATCH: Optional flag. If specified, watch the variable and print a warning if |
| # the variable is later being changed. |
| # |
| # Details: |
| # <variable> can be set by 3 sources. |
| # - Using CMake argument, -D<variable> |
| # - Using an environment variable |
| # - In the project CMakeLists.txt before `find_package(Zephyr)`. |
| # |
| # CLI has the highest precedence, then comes environment variables, |
| # and then finally CMakeLists.txt. |
| # |
| # The value defined on the first CMake invocation will be stored in the CMake |
| # cache as CACHED_<variable>. This allows the Zephyr build system to detect |
| # when a user reconfigures a sticky variable. |
| # |
| # A user can ignore all the precedence rules if the same source is always used |
| # E.g. always specifies -D<variable>= on the command line, |
| # always has an environment <variable> set, or always has a set(<variable> foo) |
| # line in his CMakeLists.txt and avoids mixing sources. |
| # |
| # The selected <variable> can be accessed through the variable '<variable>' in |
| # following Zephyr CMake code. |
| # |
| # If the user tries to change <variable> to a new value, then a warning will |
| # be printed, and the previously cached value (CACHED_<variable>) will be |
| # used, as it has precedence. |
| # |
| # Together with the warning, user is informed that in order to change |
| # <variable> the build directory must be cleaned. |
| # |
| function(zephyr_check_cache variable) |
| cmake_parse_arguments(CACHE_VAR "REQUIRED;WATCH" "" "" ${ARGN}) |
| string(TOLOWER ${variable} variable_text) |
| string(REPLACE "_" " " variable_text ${variable_text}) |
| |
| get_property(cached_value CACHE ${variable} PROPERTY VALUE) |
| |
| # If the build has already been configured in an earlier CMake invocation, |
| # then CACHED_${variable} is set. The CACHED_${variable} setting takes |
| # precedence over any user or CMakeLists.txt input. |
| # If we detect that user tries to change the setting, then print a warning |
| # that a pristine build is needed. |
| |
| # If user uses -D<variable>=<new_value>, then cli_argument will hold the new |
| # value, otherwise cli_argument will hold the existing (old) value. |
| set(cli_argument ${cached_value}) |
| if(cli_argument STREQUAL CACHED_${variable}) |
| # The is no changes to the <variable> value. |
| unset(cli_argument) |
| endif() |
| |
| set(app_cmake_lists ${${variable}}) |
| if(cached_value STREQUAL ${variable}) |
| # The app build scripts did not set a default, The variable we are |
| # reading is the cached value from the CLI |
| unset(app_cmake_lists) |
| endif() |
| |
| if(DEFINED CACHED_${variable}) |
| # Warn the user if it looks like he is trying to change the variable |
| # without cleaning first |
| if(cli_argument) |
| if(NOT ((CACHED_${variable} STREQUAL cli_argument) OR (${variable}_DEPRECATED STREQUAL cli_argument))) |
| message(WARNING "The build directory must be cleaned pristinely when " |
| "changing ${variable_text},\n" |
| "Current value=\"${CACHED_${variable}}\", " |
| "Ignored value=\"${cli_argument}\"") |
| endif() |
| endif() |
| |
| if(CACHED_${variable}) |
| set(${variable} ${CACHED_${variable}} PARENT_SCOPE) |
| set(${variable} ${CACHED_${variable}}) |
| # This resets the user provided value with previous (working) value. |
| set(${variable} ${CACHED_${variable}} CACHE STRING "Selected ${variable_text}" FORCE) |
| else() |
| unset(${variable} PARENT_SCOPE) |
| unset(${variable} CACHE) |
| endif() |
| else() |
| zephyr_get(${variable}) |
| endif() |
| |
| if(${CACHE_VAR_REQUIRED} AND NOT DEFINED ${variable}) |
| message(FATAL_ERROR "${variable} is not being defined on the CMake command-line," |
| " in the environment or by the app." |
| ) |
| endif() |
| |
| if(DEFINED ${variable}) |
| # Store the specified variable in parent scope and the cache |
| set(${variable} ${${variable}} PARENT_SCOPE) |
| set(${variable} ${${variable}} CACHE STRING "Selected ${variable_text}") |
| endif() |
| set(CACHED_${variable} ${${variable}} CACHE STRING "Selected ${variable_text}") |
| |
| if(CACHE_VAR_WATCH) |
| # The variable is now set to its final value. |
| zephyr_boilerplate_watch(${variable}) |
| endif() |
| endfunction(zephyr_check_cache variable) |
| |
| |
| # Usage: |
| # zephyr_boilerplate_watch(SOME_BOILERPLATE_VAR) |
| # |
| # Inform the build system that SOME_BOILERPLATE_VAR, a variable |
| # handled in the Zephyr package's boilerplate code, is now fixed and |
| # should no longer be changed. |
| # |
| # This function uses variable_watch() to print a noisy warning |
| # if the variable is set after it returns. |
| function(zephyr_boilerplate_watch variable) |
| variable_watch(${variable} zephyr_variable_set_too_late) |
| endfunction() |
| |
| function(zephyr_variable_set_too_late variable access value current_list_file) |
| if (access STREQUAL "MODIFIED_ACCESS") |
| message(WARNING |
| " |
| ********************************************************************** |
| * |
| * WARNING |
| * |
| * CMake variable ${variable} set to \"${value}\" in: |
| * ${current_list_file} |
| * |
| * This is too late to make changes! The change was ignored. |
| * |
| * Hint: ${variable} must be set before calling find_package(Zephyr ...). |
| * |
| ********************************************************************** |
| ") |
| endif() |
| endfunction() |
| |
| # Usage: |
| # zephyr_get_targets(<directory> <types> <targets>) |
| # |
| # Get build targets for a given directory and sub-directories. |
| # |
| # This functions will traverse the build tree, starting from <directory>. |
| # It will read the `BUILDSYSTEM_TARGETS` for each directory in the build tree |
| # and return the build types matching the <types> list. |
| # Example of types: OBJECT_LIBRARY, STATIC_LIBRARY, INTERFACE_LIBRARY, UTILITY. |
| # |
| # returns a list of targets in <targets> matching the required <types>. |
| function(zephyr_get_targets directory types targets) |
| get_property(sub_directories DIRECTORY ${directory} PROPERTY SUBDIRECTORIES) |
| get_property(dir_targets DIRECTORY ${directory} PROPERTY BUILDSYSTEM_TARGETS) |
| foreach(dir_target ${dir_targets}) |
| get_property(target_type TARGET ${dir_target} PROPERTY TYPE) |
| if(${target_type} IN_LIST types) |
| list(APPEND ${targets} ${dir_target}) |
| endif() |
| endforeach() |
| |
| foreach(directory ${sub_directories}) |
| zephyr_get_targets(${directory} "${types}" ${targets}) |
| endforeach() |
| set(${targets} ${${targets}} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # test_sysbuild([REQUIRED]) |
| # |
| # Test that current sample is invoked through sysbuild. |
| # |
| # This function tests that current CMake configure was invoked through sysbuild. |
| # If CMake configure was not invoked through sysbuild, then a warning is printed |
| # to the user. The warning can be upgraded to an error by setting `REQUIRED` as |
| # argument the `test_sysbuild()`. |
| # |
| # This function allows samples that are multi-image samples by nature to ensure |
| # all samples are correctly built together. |
| function(test_sysbuild) |
| cmake_parse_arguments(TEST_SYSBUILD "REQUIRED" "" "" ${ARGN}) |
| |
| if(TEST_SYSBUILD_REQUIRED) |
| set(message_mode FATAL_ERROR) |
| else() |
| set(message_mode WARNING) |
| endif() |
| |
| if(NOT SYSBUILD) |
| message(${message_mode} |
| "Project '${PROJECT_NAME}' is designed for sysbuild.\n" |
| "For correct user-experiences, please build '${PROJECT_NAME}' " |
| "using sysbuild." |
| ) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # target_byproducts(TARGET <target> BYPRODUCTS <file> [<file>...]) |
| # |
| # Specify additional BYPRODUCTS that this target produces. |
| # |
| # This function allows the build system to specify additional byproducts to |
| # target created with `add_executable()`. When linking an executable the linker |
| # may produce additional files, like map files. Those files are not known to the |
| # build system. This function makes it possible to describe such additional |
| # byproducts in an easy manner. |
| function(target_byproducts) |
| cmake_parse_arguments(TB "" "TARGET" "BYPRODUCTS" ${ARGN}) |
| |
| if(NOT DEFINED TB_TARGET) |
| message(FATAL_ERROR "target_byproducts() missing parameter: TARGET <target>") |
| endif() |
| |
| add_custom_command(TARGET ${TB_TARGET} |
| POST_BUILD COMMAND ${CMAKE_COMMAND} -E true |
| BYPRODUCTS ${TB_BYPRODUCTS} |
| COMMENT "Logical command for additional byproducts on target: ${TB_TARGET}" |
| ) |
| endfunction() |
| |
| # Usage: |
| # topological_sort(TARGETS <target> [<target> ...] |
| # PROPERTY_NAME <property> |
| # RESULT <out-variable>) |
| # |
| # This function performs topological sorting of CMake targets using a specific |
| # <property>, which dictates target dependencies. A fatal error occurs if the |
| # provided dependencies cannot be met, e.g., if they contain cycles. |
| # |
| # TARGETS: List of target names. |
| # PROPERTY_NAME: Name of the target property to be used when sorting. For every |
| # target listed in TARGETS, this property must contain a list |
| # (possibly empty) of other targets, which this target depends on |
| # for a particular purpose. The property must not contain any |
| # target which is not also found in TARGETS. |
| # RESULT: Output variable, where the topologically sorted list of target |
| # names will be returned. |
| # |
| function(topological_sort) |
| cmake_parse_arguments(TS "" "RESULT;PROPERTY_NAME" "TARGETS" ${ARGN}) |
| |
| set(dep_targets) |
| set(start_targets) |
| set(sorted_targets) |
| |
| foreach(target ${TS_TARGETS}) |
| get_target_property(${target}_dependencies ${target} ${TS_PROPERTY_NAME}) |
| |
| if(${target}_dependencies) |
| list(APPEND dep_targets ${target}) |
| else() |
| list(APPEND start_targets ${target}) |
| endif() |
| endforeach() |
| |
| while(TRUE) |
| list(POP_FRONT start_targets node) |
| list(APPEND sorted_targets ${node}) |
| set(to_remove) |
| foreach(target ${dep_targets}) |
| if("${node}" IN_LIST ${target}_dependencies) |
| list(REMOVE_ITEM ${target}_dependencies ${node}) |
| if(NOT ${target}_dependencies) |
| list(APPEND start_targets ${target}) |
| list(APPEND to_remove ${target}) |
| endif() |
| endif() |
| endforeach() |
| |
| foreach(target ${to_remove}) |
| list(REMOVE_ITEM dep_targets ${target}) |
| endforeach() |
| if(NOT start_targets) |
| break() |
| endif() |
| endwhile() |
| |
| if(dep_targets) |
| foreach(target ${dep_targets}) |
| get_target_property(deps ${target} ${TS_PROPERTY_NAME}) |
| list(JOIN deps " " deps) |
| list(APPEND dep_string "${target} depends on: ${deps}") |
| endforeach() |
| list(JOIN dep_string "\n" dep_string) |
| message(FATAL_ERROR "Unmet or cyclic dependencies:\n${dep_string}") |
| endif() |
| |
| set(${TS_RESULT} "${sorted_targets}" PARENT_SCOPE) |
| endfunction() |
| |
| ######################################################## |
| # 4. Devicetree extensions |
| ######################################################## |
| # 4.1. dt_* |
| # |
| # The following methods are for retrieving devicetree information in CMake. |
| # |
| # Notes: |
| # |
| # - In CMake, we refer to the nodes using the node's path, therefore |
| # there is no dt_path(...) function for obtaining a node identifier |
| # like there is in the C devicetree.h API. |
| # |
| # - As another difference from the C API, you can generally use an |
| # alias at the beginning of a path interchangeably with the full |
| # path to the aliased node in these functions. The usage comments |
| # will make this clear in each case. |
| |
| # Usage: |
| # dt_nodelabel(<var> NODELABEL <label>) |
| # |
| # Function for retrieving the node path for the node having nodelabel |
| # <label>. |
| # |
| # Example devicetree fragment: |
| # |
| # / { |
| # soc { |
| # nvic: interrupt-controller@e000e100 { ... }; |
| # }; |
| # }; |
| # |
| # Example usage: |
| # |
| # # Sets 'nvic_path' to "/soc/interrupt-controller@e000e100" |
| # dt_nodelabel(nvic_path NODELABEL "nvic") |
| # |
| # The node's path will be returned in the <var> parameter. |
| # <var> will be undefined if node does not exist. |
| # |
| # <var> : Return variable where the node path will be stored |
| # NODELABEL <label> : Node label |
| function(dt_nodelabel var) |
| set(req_single_args "NODELABEL") |
| cmake_parse_arguments(DT_LABEL "" "${req_single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_nodelabel(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_LABEL_${arg}) |
| message(FATAL_ERROR "dt_nodelabel(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| get_target_property(${var} devicetree_target "DT_NODELABEL|${DT_LABEL_NODELABEL}") |
| if(${${var}} STREQUAL ${var}-NOTFOUND) |
| set(${var}) |
| endif() |
| |
| set(${var} ${${var}} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # dt_alias(<var> PROPERTY <prop>) |
| # |
| # Get a node path for an /aliases node property. |
| # |
| # Example usage: |
| # |
| # # The full path to the 'led0' alias is returned in 'path'. |
| # dt_alias(path PROPERTY "led0") |
| # |
| # # The variable 'path' will be left undefined for a nonexistent |
| # # alias "does-not-exist". |
| # dt_alias(path PROPERTY "does-not-exist") |
| # |
| # The node's path will be returned in the <var> parameter. The |
| # variable will be left undefined if the alias does not exist. |
| # |
| # <var> : Return variable where the node path will be stored |
| # PROPERTY <prop> : The alias to check |
| function(dt_alias var) |
| set(req_single_args "PROPERTY") |
| cmake_parse_arguments(DT_ALIAS "" "${req_single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_alias(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_ALIAS_${arg}) |
| message(FATAL_ERROR "dt_alias(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| get_target_property(${var} devicetree_target "DT_ALIAS|${DT_ALIAS_PROPERTY}") |
| if(${${var}} STREQUAL ${var}-NOTFOUND) |
| set(${var}) |
| endif() |
| |
| set(${var} ${${var}} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # dt_node_exists(<var> PATH <path>) |
| # |
| # Tests whether a node with path <path> exists in the devicetree. |
| # |
| # The <path> value may be any of these: |
| # |
| # - absolute path to a node, like '/foo/bar' |
| # - a node alias, like 'my-alias' |
| # - a node alias followed by a path to a child node, like 'my-alias/child-node' |
| # |
| # The result of the check, either TRUE or FALSE, will be returned in |
| # the <var> parameter. |
| # |
| # <var> : Return variable where the check result will be returned |
| # PATH <path> : Node path |
| function(dt_node_exists var) |
| set(req_single_args "PATH") |
| cmake_parse_arguments(DT_NODE "" "${req_single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_node_exists(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_NODE_${arg}) |
| message(FATAL_ERROR "dt_node_exists(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| dt_path_internal(canonical "${DT_NODE_PATH}") |
| if (DEFINED canonical) |
| set(${var} TRUE PARENT_SCOPE) |
| else() |
| set(${var} FALSE PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # dt_node_has_status(<var> PATH <path> STATUS <status>) |
| # |
| # Tests whether <path> refers to a node which: |
| # - exists in the devicetree, and |
| # - has a status property matching the <status> argument |
| # (a missing status or an “ok” status is treated as if it |
| # were “okay” instead) |
| # |
| # The <path> value may be any of these: |
| # |
| # - absolute path to a node, like '/foo/bar' |
| # - a node alias, like 'my-alias' |
| # - a node alias followed by a path to a child node, like 'my-alias/child-node' |
| # |
| # The result of the check, either TRUE or FALSE, will be returned in |
| # the <var> parameter. |
| # |
| # <var> : Return variable where the check result will be returned |
| # PATH <path> : Node path |
| # STATUS <status> : Status to check |
| function(dt_node_has_status var) |
| set(req_single_args "PATH;STATUS") |
| cmake_parse_arguments(DT_NODE "" "${req_single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_node_has_status(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_NODE_${arg}) |
| message(FATAL_ERROR "dt_node_has_status(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| dt_path_internal(canonical ${DT_NODE_PATH}) |
| if(NOT DEFINED canonical) |
| set(${var} FALSE PARENT_SCOPE) |
| return() |
| endif() |
| |
| dt_prop(status PATH ${canonical} PROPERTY status) |
| |
| if(NOT DEFINED status OR status STREQUAL "ok") |
| set(status "okay") |
| endif() |
| |
| if(status STREQUAL "${DT_NODE_STATUS}") |
| set(${var} TRUE PARENT_SCOPE) |
| else() |
| set(${var} FALSE PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # |
| # dt_prop(<var> PATH <path> PROPERTY <prop> [INDEX <idx>]) |
| # |
| # Get a devicetree property value. The value will be returned in the |
| # <var> parameter. |
| # |
| # The <path> value may be any of these: |
| # |
| # - absolute path to a node, like '/foo/bar' |
| # - a node alias, like 'my-alias' |
| # - a node alias followed by a path to a child node, like 'my-alias/child-node' |
| # |
| # This function currently only supports properties with the following |
| # devicetree binding types: string, int, boolean, array, uint8-array, |
| # string-array, path. |
| # |
| # For array valued properties (including uint8-array and |
| # string-array), the entire array is returned as a CMake list unless |
| # INDEX is given. If INDEX is given, just the array element at index |
| # <idx> is returned. |
| # |
| # The property value will be returned in the <var> parameter if the |
| # node exists and has a property <prop> with one of the above types. |
| # <var> will be undefined otherwise. |
| # |
| # To test if the property is defined before using it, use DEFINED on |
| # the return <var>, like this: |
| # |
| # dt_prop(reserved_ranges PATH "/soc/gpio@deadbeef" PROPERTY "gpio-reserved-ranges") |
| # if(DEFINED reserved_ranges) |
| # # Node exists and has the "gpio-reserved-ranges" property. |
| # endif() |
| # |
| # To distinguish a missing node from a missing property, combine |
| # dt_prop() and dt_node_exists(), like this: |
| # |
| # dt_node_exists(node_exists PATH "/soc/gpio@deadbeef") |
| # dt_prop(reserved_ranges PATH "/soc/gpio@deadbeef" PROPERTY "gpio-reserved-ranges") |
| # if(DEFINED reserved_ranges) |
| # # Node "/soc/gpio@deadbeef" exists and has the "gpio-reserved-ranges" property |
| # elseif(node_exists) |
| # # Node exists, but doesn't have the property, or the property has an unsupported type. |
| # endif() |
| # |
| # <var> : Return variable where the property value will be stored |
| # PATH <path> : Node path |
| # PROPERTY <prop>: Property for which a value should be returned, as it |
| # appears in the DTS source |
| # INDEX <idx> : Optional index when retrieving a value in an array property |
| function(dt_prop var) |
| set(req_single_args "PATH;PROPERTY") |
| set(single_args "INDEX") |
| cmake_parse_arguments(DT_PROP "" "${req_single_args};${single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_prop(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_PROP_${arg}) |
| message(FATAL_ERROR "dt_prop(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| dt_path_internal(canonical "${DT_PROP_PATH}") |
| get_property(exists TARGET devicetree_target |
| PROPERTY "DT_PROP|${canonical}|${DT_PROP_PROPERTY}" |
| SET |
| ) |
| |
| if(NOT exists) |
| set(${var} PARENT_SCOPE) |
| return() |
| endif() |
| |
| get_target_property(val devicetree_target |
| "DT_PROP|${canonical}|${DT_PROP_PROPERTY}" |
| ) |
| |
| if(DEFINED DT_PROP_INDEX) |
| list(GET val ${DT_PROP_INDEX} element) |
| set(${var} "${element}" PARENT_SCOPE) |
| else() |
| set(${var} "${val}" PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # |
| # dt_comp_path(<var> COMPATIBLE <compatible> [INDEX <idx>]) |
| # |
| # Get a list of paths for the nodes with the given compatible. The value will |
| # be returned in the <var> parameter. |
| # <var> will be undefined if no such compatible exists. |
| # |
| # For details and considerations about the format of <path> and the returned |
| # parameter refer to dt_prop(). |
| # |
| # <var> : Return variable where the property value will be stored |
| # COMPATIBLE <compatible>: Compatible for which the list of paths should be |
| # returned, as it appears in the DTS source |
| # INDEX <idx> : Optional index when retrieving a value in an array property |
| |
| function(dt_comp_path var) |
| set(req_single_args "COMPATIBLE") |
| set(single_args "INDEX") |
| cmake_parse_arguments(DT_COMP "" "${req_single_args};${single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_comp_path(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_COMP_${arg}) |
| message(FATAL_ERROR "dt_comp_path(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| get_property(exists TARGET devicetree_target |
| PROPERTY "DT_COMP|${DT_COMP_COMPATIBLE}" |
| SET |
| ) |
| |
| if(NOT exists) |
| set(${var} PARENT_SCOPE) |
| return() |
| endif() |
| |
| get_target_property(val devicetree_target |
| "DT_COMP|${DT_COMP_COMPATIBLE}" |
| ) |
| |
| if(DEFINED DT_COMP_INDEX) |
| list(GET val ${DT_COMP_INDEX} element) |
| set(${var} "${element}" PARENT_SCOPE) |
| else() |
| set(${var} "${val}" PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # dt_num_regs(<var> PATH <path>) |
| # |
| # Get the number of register blocks in the node's reg property; |
| # this may be zero. |
| # |
| # The value will be returned in the <var> parameter. |
| # |
| # The <path> value may be any of these: |
| # |
| # - absolute path to a node, like '/foo/bar' |
| # - a node alias, like 'my-alias' |
| # - a node alias followed by a path to a child node, like 'my-alias/child-node' |
| # |
| # <var> : Return variable where the property value will be stored |
| # PATH <path> : Node path |
| function(dt_num_regs var) |
| set(req_single_args "PATH") |
| cmake_parse_arguments(DT_REG "" "${req_single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_num_regs(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_REG_${arg}) |
| message(FATAL_ERROR "dt_num_regs(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| dt_path_internal(canonical "${DT_REG_PATH}") |
| get_target_property(${var} devicetree_target "DT_REG|${canonical}|NUM") |
| |
| set(${var} ${${var}} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # dt_reg_addr(<var> PATH <path> [INDEX <idx>] [NAME <name>]) |
| # |
| # Get the base address of the register block at index <idx>, or with |
| # name <name>. If <idx> and <name> are both omitted, the value at |
| # index 0 will be returned. Do not give both <idx> and <name>. |
| # |
| # The value will be returned in the <var> parameter. |
| # |
| # The <path> value may be any of these: |
| # |
| # - absolute path to a node, like '/foo/bar' |
| # - a node alias, like 'my-alias' |
| # - a node alias followed by a path to a child node, like 'my-alias/child-node' |
| # |
| # Results can be: |
| # - The base address of the register block |
| # - <var> will be undefined if node does not exists or does not have a register |
| # block at the requested index or with the requested name |
| # |
| # <var> : Return variable where the address value will be stored |
| # PATH <path> : Node path |
| # INDEX <idx> : Register block index number |
| # NAME <name> : Register block name |
| function(dt_reg_addr var) |
| set(req_single_args "PATH") |
| set(single_args "INDEX;NAME") |
| cmake_parse_arguments(DT_REG "" "${req_single_args};${single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_reg_addr(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_REG_${arg}) |
| message(FATAL_ERROR "dt_reg_addr(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| if(DEFINED DT_REG_INDEX AND DEFINED DT_REG_NAME) |
| message(FATAL_ERROR "dt_reg_addr(${ARGV0} ...) given both INDEX and NAME") |
| elseif(NOT DEFINED DT_REG_INDEX AND NOT DEFINED DT_REG_NAME) |
| set(DT_REG_INDEX 0) |
| elseif(DEFINED DT_REG_NAME) |
| dt_reg_index_private(DT_REG_INDEX "${DT_REG_PATH}" "${DT_REG_NAME}") |
| if(DT_REG_INDEX EQUAL "-1") |
| set(${var} PARENT_SCOPE) |
| return() |
| endif() |
| endif() |
| |
| dt_path_internal(canonical "${DT_REG_PATH}") |
| get_target_property(${var}_list devicetree_target "DT_REG|${canonical}|ADDR") |
| |
| list(GET ${var}_list ${DT_REG_INDEX} ${var}) |
| |
| if("${var}" STREQUAL NONE) |
| set(${var}) |
| endif() |
| |
| set(${var} ${${var}} PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # dt_reg_size(<var> PATH <path> [INDEX <idx>] [NAME <name>]) |
| # |
| # Get the size of the register block at index <idx>, or with |
| # name <name>. If <idx> and <name> are both omitted, the value at |
| # index 0 will be returned. Do not give both <idx> and <name>. |
| # |
| # The value will be returned in the <value> parameter. |
| # |
| # The <path> value may be any of these: |
| # |
| # - absolute path to a node, like '/foo/bar' |
| # - a node alias, like 'my-alias' |
| # - a node alias followed by a path to a child node, like 'my-alias/child-node' |
| # |
| # <var> : Return variable where the size value will be stored |
| # PATH <path> : Node path |
| # INDEX <idx> : Register block index number |
| # NAME <name> : Register block name |
| function(dt_reg_size var) |
| set(req_single_args "PATH") |
| set(single_args "INDEX;NAME") |
| cmake_parse_arguments(DT_REG "" "${req_single_args};${single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_reg_size(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_REG_${arg}) |
| message(FATAL_ERROR "dt_reg_size(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| if(DEFINED DT_REG_INDEX AND DEFINED DT_REG_NAME) |
| message(FATAL_ERROR "dt_reg_size(${ARGV0} ...) given both INDEX and NAME") |
| elseif(NOT DEFINED DT_REG_INDEX AND NOT DEFINED DT_REG_NAME) |
| set(DT_REG_INDEX 0) |
| elseif(DEFINED DT_REG_NAME) |
| dt_reg_index_private(DT_REG_INDEX "${DT_REG_PATH}" "${DT_REG_NAME}") |
| if(DT_REG_INDEX EQUAL "-1") |
| set(${var} PARENT_SCOPE) |
| return() |
| endif() |
| endif() |
| |
| dt_path_internal(canonical "${DT_REG_PATH}") |
| get_target_property(${var}_list devicetree_target "DT_REG|${canonical}|SIZE") |
| |
| list(GET ${var}_list ${DT_REG_INDEX} ${var}) |
| |
| if("${var}" STREQUAL NONE) |
| set(${var}) |
| endif() |
| |
| set(${var} ${${var}} PARENT_SCOPE) |
| endfunction() |
| |
| # Internal helper for dt_reg_addr/dt_reg_size; not meant to be used directly |
| function(dt_reg_index_private var path name) |
| dt_prop(reg_names PATH "${path}" PROPERTY "reg-names") |
| if(NOT DEFINED reg_names) |
| set(index "-1") |
| else() |
| list(FIND reg_names "${name}" index) |
| endif() |
| set(${var} "${index}" PARENT_SCOPE) |
| endfunction() |
| |
| # Usage: |
| # dt_has_chosen(<var> PROPERTY <prop>) |
| # |
| # Test if the devicetree's /chosen node has a given property |
| # <prop> which contains the path to a node. |
| # |
| # Example devicetree fragment: |
| # |
| # chosen { |
| # foo = &bar; |
| # }; |
| # |
| # Example usage: |
| # |
| # # Sets 'result' to TRUE |
| # dt_has_chosen(result PROPERTY "foo") |
| # |
| # # Sets 'result' to FALSE |
| # dt_has_chosen(result PROPERTY "baz") |
| # |
| # The result of the check, either TRUE or FALSE, will be stored in the |
| # <var> parameter. |
| # |
| # <var> : Return variable |
| # PROPERTY <prop> : Chosen property |
| function(dt_has_chosen var) |
| set(req_single_args "PROPERTY") |
| cmake_parse_arguments(DT_CHOSEN "" "${req_single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_has_chosen(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_CHOSEN_${arg}) |
| message(FATAL_ERROR "dt_has_chosen(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| get_target_property(exists devicetree_target "DT_CHOSEN|${DT_CHOSEN_PROPERTY}") |
| |
| if(${exists} STREQUAL exists-NOTFOUND) |
| set(${var} FALSE PARENT_SCOPE) |
| else() |
| set(${var} TRUE PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Usage: |
| # dt_chosen(<var> PROPERTY <prop>) |
| # |
| # Get a node path for a /chosen node property. |
| # |
| # The node's path will be returned in the <var> parameter. The |
| # variable will be left undefined if the chosen node does not exist. |
| # |
| # <var> : Return variable where the node path will be stored |
| # PROPERTY <prop> : Chosen property |
| function(dt_chosen var) |
| set(req_single_args "PROPERTY") |
| cmake_parse_arguments(DT_CHOSEN "" "${req_single_args}" "" ${ARGN}) |
| |
| if(${ARGV0} IN_LIST req_single_args) |
| message(FATAL_ERROR "dt_chosen(${ARGV0} ...) missing return parameter.") |
| endif() |
| |
| foreach(arg ${req_single_args}) |
| if(NOT DEFINED DT_CHOSEN_${arg}) |
| message(FATAL_ERROR "dt_chosen(${ARGV0} ...) " |
| "missing required argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| get_target_property(${var} devicetree_target "DT_CHOSEN|${DT_CHOSEN_PROPERTY}") |
| |
| if(${${var}} STREQUAL ${var}-NOTFOUND) |
| set(${var} PARENT_SCOPE) |
| else() |
| set(${var} ${${var}} PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # Internal helper. Canonicalizes a path 'path' into the output |
| # variable 'var'. This resolves aliases, if any. Child nodes may be |
| # accessed via alias as well. 'var' is left undefined if the path does |
| # not refer to an existing node. |
| # |
| # Example devicetree: |
| # |
| # / { |
| # foo { |
| # my-label: bar { |
| # baz {}; |
| # }; |
| # }; |
| # aliases { |
| # my-alias = &my-label; |
| # }; |
| # }; |
| # |
| # Example usage: |
| # |
| # dt_path_internal(ret "/foo/bar") # sets ret to "/foo/bar" |
| # dt_path_internal(ret "my-alias") # sets ret to "/foo/bar" |
| # dt_path_internal(ret "my-alias/baz") # sets ret to "/foo/bar/baz" |
| # dt_path_internal(ret "/blub") # ret is undefined |
| function(dt_path_internal var path) |
| string(FIND "${path}" "/" slash_index) |
| |
| if("${slash_index}" EQUAL 0) |
| # If the string starts with a slash, it should be an existing |
| # canonical path. |
| dt_path_internal_exists(check "${path}") |
| if (check) |
| set(${var} "${path}" PARENT_SCOPE) |
| return() |
| endif() |
| else() |
| # Otherwise, try to expand a leading alias. |
| string(SUBSTRING "${path}" 0 "${slash_index}" alias_name) |
| dt_alias(alias_path PROPERTY "${alias_name}") |
| |
| # If there is a leading alias, append the rest of the string |
| # onto it and see if that's an existing node. |
| if (DEFINED alias_path) |
| set(rest) |
| if (NOT "${slash_index}" EQUAL -1) |
| string(SUBSTRING "${path}" "${slash_index}" -1 rest) |
| endif() |
| dt_path_internal_exists(expanded_path_exists "${alias_path}${rest}") |
| if (expanded_path_exists) |
| set(${var} "${alias_path}${rest}" PARENT_SCOPE) |
| return() |
| endif() |
| endif() |
| endif() |
| |
| # Failed search; ensure return variable is undefined. |
| set(${var} PARENT_SCOPE) |
| endfunction() |
| |
| # Internal helper. Set 'var' to TRUE if a canonical path 'path' refers |
| # to an existing node. Set it to FALSE otherwise. See |
| # dt_path_internal for a definition and examples of 'canonical' paths. |
| function(dt_path_internal_exists var path) |
| get_target_property(path_prop devicetree_target "DT_NODE|${path}") |
| if (path_prop) |
| set(${var} TRUE PARENT_SCOPE) |
| else() |
| set(${var} FALSE PARENT_SCOPE) |
| endif() |
| endfunction() |
| |
| # 4.2. *_if_dt_node |
| # |
| # This section is similar to the extensions named *_ifdef, except |
| # actions are performed if the devicetree contains some node. |
| # *_if_dt_node functions may be added as needed, or if they are likely |
| # to be useful for user applications. |
| |
| # Add item(s) to a target's SOURCES list if a devicetree node exists. |
| # |
| # Example usage: |
| # |
| # # If the devicetree alias "led0" refers to a node, this |
| # # adds "blink_led.c" to the sources list for the "app" target. |
| # target_sources_if_dt_node("led0" app PRIVATE blink_led.c) |
| # |
| # # If the devicetree path "/soc/serial@4000" is a node, this |
| # # adds "uart.c" to the sources list for the "lib" target, |
| # target_sources_if_dt_node("/soc/serial@4000" lib PRIVATE uart.c) |
| # |
| # <path> : Path to devicetree node to check |
| # <target> : Build system target whose sources to add to |
| # <scope> : Scope to add items to |
| # <item> : Item (or items) to add to the target |
| function(target_sources_if_dt_node path target scope item) |
| dt_node_exists(check PATH "${path}") |
| if(${check}) |
| target_sources(${target} ${scope} ${item} ${ARGN}) |
| endif() |
| endfunction() |
| |
| ######################################################## |
| # 4.3 zephyr_dt_* |
| # |
| # The following methods are common code for dealing |
| # with devicetree related files in CMake. |
| # |
| # Note that functions related to accessing the |
| # *contents* of the devicetree belong in section 4.1. |
| # This section is just for DT file processing at |
| # configuration time. |
| ######################################################## |
| |
| # Usage: |
| # zephyr_dt_preprocess(CPP <path> [<argument...>] |
| # SOURCE_FILES <file...> |
| # OUT_FILE <file> |
| # [DEPS_FILE <file>] |
| # [EXTRA_CPPFLAGS <flag...>] |
| # [INCLUDE_DIRECTORIES <dir...>] |
| # [WORKING_DIRECTORY <dir>] |
| # |
| # Preprocess one or more devicetree source files. The preprocessor |
| # symbol __DTS__ will be defined. If the preprocessor command fails, a |
| # fatal error occurs. |
| # |
| # Mandatory arguments: |
| # |
| # CPP <path> [<argument...>]: path to C preprocessor, followed by any |
| # additional arguments |
| # |
| # SOURCE_FILES <file...>: The source files to run the preprocessor on. |
| # These will, in effect, be concatenated in order |
| # and used as the preprocessor input. |
| # |
| # OUT_FILE <file>: Where to store the preprocessor output. |
| # |
| # Optional arguments: |
| # |
| # DEPS_FILE <file>: If set, generate a dependency file here. |
| # |
| # EXTRA_CPPFLAGS <flag...>: Additional flags to pass the preprocessor. |
| # |
| # INCLUDE_DIRECTORIES <dir...>: Additional #include file directories. |
| # |
| # WORKING_DIRECTORY <dir>: where to run the preprocessor. |
| function(zephyr_dt_preprocess) |
| set(req_single_args "OUT_FILE") |
| set(single_args "DEPS_FILE;WORKING_DIRECTORY") |
| set(req_multi_args "CPP;SOURCE_FILES") |
| set(multi_args "EXTRA_CPPFLAGS;INCLUDE_DIRECTORIES") |
| cmake_parse_arguments(DT_PREPROCESS "" "${req_single_args};${single_args}" "${req_multi_args};${multi_args}" ${ARGN}) |
| |
| foreach(arg ${req_single_args} ${req_multi_args}) |
| if(NOT DEFINED DT_PREPROCESS_${arg}) |
| message(FATAL_ERROR "dt_preprocess() missing required argument: ${arg}") |
| endif() |
| endforeach() |
| |
| set(include_opts) |
| foreach(dir ${DT_PREPROCESS_INCLUDE_DIRECTORIES}) |
| list(APPEND include_opts -isystem ${dir}) |
| endforeach() |
| |
| set(source_opts) |
| foreach(file ${DT_PREPROCESS_SOURCE_FILES}) |
| list(APPEND source_opts -include ${file}) |
| endforeach() |
| |
| set(deps_opts) |
| if(DEFINED DT_PREPROCESS_DEPS_FILE) |
| list(APPEND deps_opts -MD -MF ${DT_PREPROCESS_DEPS_FILE}) |
| endif() |
| |
| set(workdir_opts) |
| if(DEFINED DT_PREPROCESS_WORKING_DIRECTORY) |
| list(APPEND workdir_opts WORKING_DIRECTORY ${DT_PREPROCESS_WORKING_DIRECTORY}) |
| endif() |
| |
| # We are leaving linemarker directives enabled on purpose. This tells |
| # dtlib where each line actually came from, which improves error |
| # reporting. |
| set(preprocess_cmd ${DT_PREPROCESS_CPP} |
| -x assembler-with-cpp |
| -nostdinc |
| ${include_opts} |
| ${source_opts} |
| ${NOSYSDEF_CFLAG} |
| -D__DTS__ |
| ${DT_PREPROCESS_EXTRA_CPPFLAGS} |
| -E # Stop after preprocessing |
| ${deps_opts} |
| -o ${DT_PREPROCESS_OUT_FILE} |
| ${ZEPHYR_BASE}/misc/empty_file.c |
| ${workdir_opts}) |
| |
| execute_process(COMMAND ${preprocess_cmd} RESULT_VARIABLE ret) |
| if(NOT "${ret}" STREQUAL "0") |
| message(FATAL_ERROR "failed to preprocess devicetree files (error code ${ret}): ${DT_PREPROCESS_SOURCE_FILES}") |
| endif() |
| endfunction() |
| |
| ######################################################## |
| # 5. Zephyr linker functions |
| ######################################################## |
| # 5.1. zephyr_linker* |
| # |
| # The following methods are for defining linker structure using CMake functions. |
| # |
| # This allows Zephyr developers to define linker sections and their content and |
| # have this configuration rendered into an appropriate linker script based on |
| # the toolchain in use. |
| # For example: |
| # ld linker scripts with GNU ld |
| # ARM scatter files with ARM linker. |
| # |
| # Example usage: |
| # zephyr_linker_section( |
| # NAME my_data |
| # VMA RAM |
| # LMA FLASH |
| # ) |
| # |
| # and to configure special input sections for the section |
| # zephyr_linker_section_configure( |
| # SECTION my_data |
| # INPUT "my_custom_data" |
| # KEEP |
| # ) |
| |
| |
| # Usage: |
| # zephyr_linker([FORMAT <format>] |
| # [ENTRY <entry symbol>] |
| # ) |
| # |
| # Zephyr linker general settings. |
| # This function specifies general settings for the linker script to be generated. |
| # |
| # FORMAT <format>: The output format of the linked executable. |
| # ENTRY <entry symbolformat>: The code entry symbol. |
| # |
| function(zephyr_linker) |
| set(single_args "ENTRY;FORMAT") |
| cmake_parse_arguments(LINKER "" "${single_args}" "" ${ARGN}) |
| |
| if(LINKER_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_linker(${ARGV0} ...) given unknown " |
| "arguments: ${LINKER_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| if(DEFINED LINKER_FORMAT) |
| get_property(format_defined TARGET linker PROPERTY FORMAT SET) |
| if(format_defined) |
| message(FATAL_ERROR "zephyr_linker(FORMAT ...) already configured.") |
| else() |
| set_property(TARGET linker PROPERTY FORMAT ${LINKER_FORMAT}) |
| endif() |
| endif() |
| |
| if(DEFINED LINKER_ENTRY) |
| get_property(entry_defined TARGET linker PROPERTY ENTRY SET) |
| if(entry_defined) |
| message(FATAL_ERROR "zephyr_linker(ENTRY ...) already configured.") |
| else() |
| set_property(TARGET linker PROPERTY ENTRY ${LINKER_ENTRY}) |
| endif() |
| endif() |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_memory(NAME <name> START <address> SIZE <size> FLAGS <flags>) |
| # |
| # Zephyr linker memory. |
| # This function specifies a memory region for the platform in use. |
| # |
| # Note: |
| # This function should generally be called with values obtained from |
| # devicetree or Kconfig. |
| # |
| # NAME <name> : Name of the memory region, for example FLASH. |
| # START <address>: Start address of the memory region. |
| # Start address can be given as decimal or hex value. |
| # SIZE <size> : Size of the memory region. |
| # Size can be given as decimal value, hex value, or decimal with postfix k or m. |
| # All the following are valid values: |
| # 1048576, 0x10000, 1024k, 1024K, 1m, and 1M. |
| # FLAGS <flags> : Flags describing properties of the memory region. |
| # Currently supported: |
| # r: Read-only region |
| # w: Read-write region |
| # x: Executable region |
| # The flags r and x, or w and x may be combined like: rx, wx. |
| function(zephyr_linker_memory) |
| set(single_args "FLAGS;NAME;SIZE;START") |
| cmake_parse_arguments(MEMORY "" "${single_args}" "" ${ARGN}) |
| |
| if(MEMORY_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_linker_memory(${ARGV0} ...) given unknown " |
| "arguments: ${MEMORY_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| foreach(arg ${single_args}) |
| if(NOT DEFINED MEMORY_${arg}) |
| message(FATAL_ERROR "zephyr_linker_memory(${ARGV0} ...) missing required " |
| "argument: ${arg}" |
| ) |
| endif() |
| endforeach() |
| |
| set(MEMORY) |
| zephyr_linker_arg_val_list(MEMORY "${single_args}") |
| |
| string(REPLACE ";" "\;" MEMORY "${MEMORY}") |
| set_property(TARGET linker |
| APPEND PROPERTY MEMORY_REGIONS "{${MEMORY}}" |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_memory_ifdef(<setting> NAME <name> START <address> SIZE <size> FLAGS <flags>) |
| # |
| # Will create memory region if <setting> is enabled. |
| # |
| # <setting>: Setting to check for True value before invoking |
| # zephyr_linker_memory() |
| # |
| # See zephyr_linker_memory() description for other supported arguments. |
| # |
| macro(zephyr_linker_memory_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_linker_memory(${ARGN}) |
| endif() |
| endmacro() |
| |
| # Usage: |
| # zephyr_linker_dts_section(PATH <path>) |
| # |
| # Zephyr linker devicetree memory section from path. |
| # |
| # This function specifies an output section for the platform in use based on its |
| # devicetree configuration. |
| # |
| # The section will only be defined if the devicetree exists and has status okay. |
| # |
| # PATH <path> : Devicetree node path. |
| # |
| function(zephyr_linker_dts_section) |
| set(single_args "PATH") |
| cmake_parse_arguments(DTS_SECTION "" "${single_args}" "" ${ARGN}) |
| |
| if(DTS_SECTION_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_linker_dts_section(${ARGV0} ...) given unknown " |
| "arguments: ${DTS_SECTION_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| if(NOT DEFINED DTS_SECTION_PATH) |
| message(FATAL_ERROR "zephyr_linker_dts_section(${ARGV0} ...) missing " |
| "required argument: PATH" |
| ) |
| endif() |
| |
| dt_node_has_status(okay PATH ${DTS_SECTION_PATH} STATUS okay) |
| if(NOT ${okay}) |
| return() |
| endif() |
| |
| dt_prop(name PATH ${DTS_SECTION_PATH} PROPERTY "zephyr,memory-region") |
| if(NOT DEFINED name) |
| message(FATAL_ERROR "zephyr_linker_dts_section(${ARGV0} ...) missing " |
| "\"zephyr,memory-region\" property" |
| ) |
| endif() |
| zephyr_string(SANITIZE name ${name}) |
| |
| dt_reg_addr(addr PATH ${DTS_SECTION_PATH}) |
| |
| zephyr_linker_section(NAME ${name} ADDRESS ${addr} VMA ${name} TYPE NOLOAD) |
| |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_dts_memory(PATH <path> FLAGS <flags>) |
| # zephyr_linker_dts_memory(NODELABEL <nodelabel> FLAGS <flags>) |
| # zephyr_linker_dts_memory(CHOSEN <prop> FLAGS <flags>) |
| # |
| # Zephyr linker devicetree memory. |
| # This function specifies a memory region for the platform in use based on its |
| # devicetree configuration. |
| # |
| # The memory will only be defined if the devicetree node or a devicetree node |
| # matching the nodelabel exists and has status okay. |
| # |
| # Only one of PATH, NODELABEL, and CHOSEN parameters may be given. |
| # |
| # PATH <path> : Devicetree node identifier. |
| # NODELABEL <label>: Node label |
| # CHOSEN <prop> : Chosen property, add memory section described by the |
| # /chosen property if it exists. |
| # FLAGS <flags> : Flags describing properties of the memory region. |
| # Currently supported: |
| # r: Read-only region |
| # w: Read-write region |
| # x: Executable region |
| # The flags r and x, or w and x may be combined like: rx, wx. |
| # |
| function(zephyr_linker_dts_memory) |
| set(single_args "CHOSEN;FLAGS;PATH;NODELABEL") |
| cmake_parse_arguments(DTS_MEMORY "" "${single_args}" "" ${ARGN}) |
| |
| if(DTS_MEMORY_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_linker_dts_memory(${ARGV0} ...) given unknown " |
| "arguments: ${DTS_MEMORY_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| if((DEFINED DTS_MEMORY_PATH AND (DEFINED DTS_MEMORY_NODELABEL OR DEFINED DTS_MEMORY_CHOSEN)) |
| OR (DEFINED DTS_MEMORY_NODELABEL AND DEFINED DTS_MEMORY_CHOSEN)) |
| message(FATAL_ERROR "zephyr_linker_dts_memory(${ARGV0} ...), only one of " |
| "PATH, NODELABEL, and CHOSEN is allowed." |
| ) |
| endif() |
| |
| if(DEFINED DTS_MEMORY_NODELABEL) |
| dt_nodelabel(DTS_MEMORY_PATH NODELABEL ${DTS_MEMORY_NODELABEL}) |
| endif() |
| |
| if(DEFINED DTS_MEMORY_CHOSEN) |
| dt_chosen(DTS_MEMORY_PATH PROPERTY ${DTS_MEMORY_CHOSEN}) |
| endif() |
| |
| if(NOT DEFINED DTS_MEMORY_PATH) |
| return() |
| endif() |
| |
| dt_node_has_status(okay PATH ${DTS_MEMORY_PATH} STATUS okay) |
| if(NOT ${okay}) |
| return() |
| endif() |
| |
| dt_reg_addr(addr PATH ${DTS_MEMORY_PATH}) |
| dt_reg_size(size PATH ${DTS_MEMORY_PATH}) |
| dt_prop(name PATH ${DTS_MEMORY_PATH} PROPERTY "zephyr,memory-region") |
| if(NOT DEFINED name) |
| message(FATAL_ERROR "zephyr_linker_dts_memory(${ARGV0} ...) missing " |
| "\"zephyr,memory-region\" property" |
| ) |
| endif() |
| zephyr_string(SANITIZE name ${name}) |
| |
| zephyr_linker_memory( |
| NAME ${name} |
| START ${addr} |
| SIZE ${size} |
| FLAGS ${DTS_MEMORY_FLAGS} |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_group(NAME <name> [VMA <region|group>] [LMA <region|group>] [SYMBOL <SECTION>]) |
| # zephyr_linker_group(NAME <name> GROUP <group> [SYMBOL <SECTION>]) |
| # |
| # Zephyr linker group. |
| # This function specifies a group inside a memory region or another group. |
| # |
| # The group ensures that all section inside the group are located together inside |
| # the specified group. |
| # |
| # This also allows for section placement inside a given group without the section |
| # itself needing the precise knowledge regarding the exact memory region this |
| # section will be placed in, as that will be determined by the group setup. |
| # |
| # Each group will define the following linker symbols: |
| # __<name>_start : Start address of the group |
| # __<name>_end : End address of the group |
| # __<name>_size : Size of the group |
| # |
| # Note: <name> will be converted to lower casing for linker symbols definitions. |
| # |
| # NAME <name> : Name of the group. |
| # VMA <region|group> : VMA Memory region or group to be used for this group. |
| # If a group is used then the VMA region of that group will be used. |
| # LMA <region|group> : Memory region or group to be used for this group. |
| # GROUP <group> : Place the new group inside the existing group <group> |
| # SYMBOL <SECTION> : Specify that start symbol of the region should be identical |
| # to the start address of the first section in the group. |
| # |
| # Note: VMA and LMA are mutual exclusive with GROUP |
| # |
| # Example: |
| # zephyr_linker_memory(NAME memA START ... SIZE ... FLAGS ...) |
| # zephyr_linker_group(NAME groupA LMA memA) |
| # zephyr_linker_group(NAME groupB LMA groupA) |
| # |
| # will create two groups in same memory region as groupB will inherit the LMA |
| # from groupA: |
| # |
| # +-----------------+ |
| # | memory region A | |
| # | | |
| # | +-------------+ | |
| # | | groupA | | |
| # | +-------------+ | |
| # | | |
| # | +-------------+ | |
| # | | groupB | | |
| # | +-------------+ | |
| # | | |
| # +-----------------+ |
| # |
| # whereas |
| # zephyr_linker_memory(NAME memA START ... SIZE ... FLAGS ...) |
| # zephyr_linker_group(NAME groupA LMA memA) |
| # zephyr_linker_group(NAME groupB GROUP groupA) |
| # |
| # will create groupB inside groupA: |
| # |
| # +---------------------+ |
| # | memory region A | |
| # | | |
| # | +-----------------+ | |
| # | | groupA | | |
| # | | | | |
| # | | +-------------+ | | |
| # | | | groupB | | | |
| # | | +-------------+ | | |
| # | | | | |
| # | +-----------------+ | |
| # | | |
| # +---------------------+ |
| function(zephyr_linker_group) |
| set(single_args "NAME;GROUP;LMA;SYMBOL;VMA") |
| set(symbol_values SECTION) |
| cmake_parse_arguments(GROUP "" "${single_args}" "" ${ARGN}) |
| |
| if(GROUP_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_linker_group(${ARGV0} ...) given unknown " |
| "arguments: ${GROUP_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| if(DEFINED GROUP_GROUP AND (DEFINED GROUP_VMA OR DEFINED GROUP_LMA)) |
| message(FATAL_ERROR "zephyr_linker_group(GROUP ...) cannot be used with " |
| "VMA or LMA" |
| ) |
| endif() |
| |
| if(DEFINED GROUP_SYMBOL) |
| if(NOT ${GROUP_SYMBOL} IN_LIST symbol_values) |
| message(FATAL_ERROR "zephyr_linker_group(SYMBOL ...) given unknown value") |
| endif() |
| endif() |
| |
| set(GROUP) |
| zephyr_linker_arg_val_list(GROUP "${single_args}") |
| |
| string(REPLACE ";" "\;" GROUP "${GROUP}") |
| set_property(TARGET linker |
| APPEND PROPERTY GROUPS "{${GROUP}}" |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_section(NAME <name> [GROUP <group>] |
| # [VMA <region|group>] [LMA <region|group>] |
| # [ADDRESS <address>] [ALIGN <alignment>] |
| # [SUBALIGN <alignment>] [FLAGS <flags>] |
| # [HIDDEN] [NOINPUT] [NOINIT] |
| # [PASS [NOT] <name>] |
| # ) |
| # |
| # Zephyr linker output section. |
| # This function specifies an output section for the linker. |
| # |
| # When using zephyr_linker_section(NAME <name>) an output section with <name> |
| # will be configured. This section will default include input sections of the |
| # same name, unless NOINPUT is specified. |
| # This means the section named `foo` will default include the sections matching |
| # `foo` and `foo.*` |
| # Each output section will define the following linker symbols: |
| # __<name>_start : Start address of the section |
| # __<name>_end : End address of the section |
| # __<name>_size : Size of the section |
| # __<name>_load_start : Load address of the section, if VMA = LMA then this |
| # value will be identical to `__<name>_start` |
| # |
| # The location of the output section can be controlled using LMA, VMA, and |
| # address parameters |
| # |
| # NAME <name> : Name of the output section. |
| # VMA <region|group> : VMA Memory region or group where code / data is located runtime (VMA) |
| # If <group> is used here it means the section will use the |
| # same VMA memory region as <group> but will not be placed |
| # inside the group itself, see also GROUP argument. |
| # KVMA <region|group> : Kernel VMA Memory region or group where code / data is located runtime (VMA) |
| # When MMU is active and Kernel VM base and offset is different |
| # from SRAM base and offset, then the region defined by KVMA will |
| # be used as VMA. |
| # If <group> is used here it means the section will use the |
| # same VMA memory region as <group> but will not be placed |
| # inside the group itself, see also GROUP argument. |
| # LMA <region|group> : Memory region or group where code / data is loaded (LMA) |
| # If VMA is different from LMA, the code / data will be loaded |
| # from LMA into VMA at bootup, this is usually the case for |
| # global or static variables that are loaded in rom and copied |
| # to ram at boot time. |
| # If <group> is used here it means the section will use the |
| # same LMA memory region as <group> but will not be placed |
| # inside the group itself, see also GROUP argument. |
| # GROUP <group> : Place this section inside the group <group> |
| # ADDRESS <address> : Specific address to use for this section. |
| # ALIGN_WITH_INPUT : The alignment difference between VMA and LMA is kept |
| # intact for this section. |
| # ALIGN <alignment> : Align the execution address with alignment. |
| # SUBALIGN <alignment>: Align input sections with alignment value. |
| # ENDALIGN <alignment>: Align the end so that next output section will start aligned. |
| # This only has effect on Scatter scripts. |
| # Note: Regarding all alignment attributes. Not all linkers may handle alignment |
| # in identical way. For example the Scatter file will align both load and |
| # execution address (LMA and VMA) to be aligned when given the ALIGN attribute. |
| # NOINPUT : No default input sections will be defined, to setup input |
| # sections for section <name>, the corresponding |
| # `zephyr_linker_section_configure()` must be used. |
| # PASS [NOT] <name> : Linker pass iteration where this section should be active. |
| # Default a section will be present during all linker passes |
| # but in cases a section shall only be present at a specific |
| # pass, this argument can be used. For example to only have |
| # this section present on the `TEST` linker pass, use `PASS TEST`. |
| # It is possible to negate <name>, such as `PASS NOT <name>`. |
| # For example, `PASS NOT TEST` means the call is effective |
| # on all but the `TEST` linker pass iteration. |
| # |
| # Note: VMA and LMA are mutual exclusive with GROUP |
| # |
| function(zephyr_linker_section) |
| set(options "ALIGN_WITH_INPUT;HIDDEN;NOINIT;NOINPUT") |
| set(single_args "ADDRESS;ALIGN;ENDALIGN;GROUP;KVMA;LMA;NAME;SUBALIGN;TYPE;VMA") |
| set(multi_args "PASS") |
| cmake_parse_arguments(SECTION "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| |
| if(SECTION_UNPARSED_ARGUMENTS) |
| message(WARNING "zephyr_linker_section(${ARGV0} ...) given unknown " |
| "arguments: ${SECTION_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| if(DEFINED SECTION_GROUP AND (DEFINED SECTION_VMA OR DEFINED SECTION_LMA)) |
| message(FATAL_ERROR "zephyr_linker_section(GROUP ...) cannot be used with " |
| "VMA or LMA" |
| ) |
| endif() |
| |
| if(DEFINED SECTION_KVMA) |
| # If KVMA is set and the Kernel virtual memory settings reqs are met, we |
| # substitute the VMA setting with the specified KVMA value. |
| if(CONFIG_MMU) |
| math(EXPR KERNEL_MEM_VM_OFFSET |
| "(${CONFIG_KERNEL_VM_BASE} + ${CONFIG_KERNEL_VM_OFFSET})\ |
| - (${CONFIG_SRAM_BASE_ADDRESS} + ${CONFIG_SRAM_OFFSET})" |
| ) |
| |
| if(NOT (${KERNEL_MEM_VM_OFFSET} EQUAL 0)) |
| set(SECTION_VMA ${SECTION_KVMA}) |
| set(SECTION_KVMA) |
| endif() |
| endif() |
| endif() |
| |
| if(DEFINED SECTION_PASS) |
| list(LENGTH SECTION_PASS pass_length) |
| if(${pass_length} GREATER 1) |
| list(GET SECTION_PASS 0 pass_elem_0) |
| if((NOT (${pass_elem_0} STREQUAL "NOT")) OR (${pass_length} GREATER 2)) |
| message(FATAL_ERROR "zephyr_linker_section(PASS takes maximum " |
| "a single argument of the form: '<pass name>' or 'NOT <pass_name>'.") |
| endif() |
| endif() |
| endif() |
| |
| set(SECTION) |
| zephyr_linker_arg_val_list(SECTION "${single_args}") |
| zephyr_linker_arg_val_list(SECTION "${options}") |
| zephyr_linker_arg_val_list(SECTION "${multi_args}") |
| |
| string(REPLACE ";" "\;" SECTION "${SECTION}") |
| set_property(TARGET linker |
| APPEND PROPERTY SECTIONS "{${SECTION}}" |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_section_ifdef(<setting> |
| # NAME <name> [VMA <region>] [LMA <region>] |
| # [ADDRESS <address>] [ALIGN <alignment>] |
| # [SUBALIGN <alignment>] [FLAGS <flags>] |
| # [HIDDEN] [NOINPUT] [NOINIT] |
| # [PASS <no> [<no>...] |
| # ) |
| # |
| # Will create an output section if <setting> is enabled. |
| # |
| # <setting>: Setting to check for True value before invoking |
| # zephyr_linker_section() |
| # |
| # See zephyr_linker_section() description for other supported arguments. |
| # |
| macro(zephyr_linker_section_ifdef feature_toggle) |
| if(${${feature_toggle}}) |
| zephyr_linker_section(${ARGN}) |
| endif() |
| endmacro() |
| |
| # Usage: |
| # zephyr_iterable_section(NAME <name> [GROUP <group>] |
| # [VMA <region|group>] [LMA <region|group>] |
| # [ALIGN_WITH_INPUT] [SUBALIGN <alignment>] |
| # ) |
| # |
| # |
| # Define an output section which will set up an iterable area |
| # of equally-sized data structures. For use with STRUCT_SECTION_ITERABLE. |
| # Input sections will be sorted by name in lexicographical order. |
| # |
| # Each list for an input section will define the following linker symbols: |
| # _<name>_list_start: Start of the iterable list |
| # _<name>_list_end : End of the iterable list |
| # |
| # The output section will be named `<name>_area` and define the following linker |
| # symbols: |
| # __<name>_area_start : Start address of the section |
| # __<name>_area_end : End address of the section |
| # __<name>_area_size : Size of the section |
| # __<name>_area_load_start : Load address of the section, if VMA = LMA then this |
| # value will be identical to `__<name>_area_start` |
| # |
| # NAME <name> : Name of the struct type, the output section be named |
| # accordingly as: <name>_area. |
| # VMA <region|group> : VMA Memory region where code / data is located runtime (VMA) |
| # LMA <region|group> : Memory region where code / data is loaded (LMA) |
| # If VMA is different from LMA, the code / data will be loaded |
| # from LMA into VMA at bootup, this is usually the case for |
| # global or static variables that are loaded in rom and copied |
| # to ram at boot time. |
| # GROUP <group> : Place this section inside the group <group> |
| # ADDRESS <address> : Specific address to use for this section. |
| # ALIGN_WITH_INPUT : The alignment difference between VMA and LMA is kept |
| # intact for this section. |
| # NUMERIC : Use numeric sorting. |
| # SUBALIGN <alignment>: Force input alignment with size <alignment> |
| # Note: Regarding all alignment attributes. Not all linkers may handle alignment |
| # in identical way. For example the Scatter file will align both load and |
| # execution address (LMA and VMA) to be aligned when given the ALIGN attribute. |
| #/ |
| function(zephyr_iterable_section) |
| # ToDo - Should we use ROM, RAM, etc as arguments ? |
| set(options "ALIGN_WITH_INPUT;NUMERIC") |
| set(single_args "GROUP;LMA;NAME;SUBALIGN;VMA") |
| set(multi_args "") |
| set(align_input) |
| cmake_parse_arguments(SECTION "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| |
| if(NOT DEFINED SECTION_NAME) |
| message(FATAL_ERROR "zephyr_iterable_section(${ARGV0} ...) missing " |
| "required argument: NAME" |
| ) |
| endif() |
| |
| if(NOT DEFINED SECTION_SUBALIGN) |
| message(FATAL_ERROR "zephyr_iterable_section(${ARGV0} ...) missing " |
| "required argument: SUBALIGN" |
| ) |
| endif() |
| |
| if(SECTION_ALIGN_WITH_INPUT) |
| set(align_input ALIGN_WITH_INPUT) |
| endif() |
| |
| if(SECTION_NUMERIC) |
| set(INPUT "._${SECTION_NAME}.static.*_?_*;._${SECTION_NAME}.static.*_??_*") |
| else() |
| set(INPUT "._${SECTION_NAME}.static.*") |
| endif() |
| |
| zephyr_linker_section( |
| NAME ${SECTION_NAME}_area |
| GROUP "${SECTION_GROUP}" |
| VMA "${SECTION_VMA}" LMA "${SECTION_LMA}" |
| NOINPUT ${align_input} SUBALIGN ${SECTION_SUBALIGN} |
| ) |
| zephyr_linker_section_configure( |
| SECTION ${SECTION_NAME}_area |
| INPUT "${INPUT}" |
| SYMBOLS _${SECTION_NAME}_list_start _${SECTION_NAME}_list_end |
| KEEP SORT NAME |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_section_obj_level(SECTION <section> LEVEL <level>) |
| # |
| # generate a symbol to mark the start of the objects array for |
| # the specified object and level, then link all of those objects |
| # (sorted by priority). Ensure the objects aren't discarded if there is |
| # no direct reference to them. |
| # |
| # This is useful content such as struct devices. |
| # |
| # For example: zephyr_linker_section_obj_level(SECTION init LEVEL PRE_KERNEL_1) |
| # will create an input section matching `.z_init_PRE_KERNEL_1?_` and |
| # `.z_init_PRE_KERNEL_1??_`. |
| # |
| # SECTION <section>: Section in which the objects shall be placed |
| # LEVEL <level> : Priority level, all input sections matching the level |
| # will be sorted. |
| # |
| function(zephyr_linker_section_obj_level) |
| set(single_args "SECTION;LEVEL") |
| cmake_parse_arguments(OBJ "" "${single_args}" "" ${ARGN}) |
| |
| if(NOT DEFINED OBJ_SECTION) |
| message(FATAL_ERROR "zephyr_linker_section_obj_level(${ARGV0} ...) " |
| "missing required argument: SECTION" |
| ) |
| endif() |
| |
| if(NOT DEFINED OBJ_LEVEL) |
| message(FATAL_ERROR "zephyr_linker_section_obj_level(${ARGV0} ...) " |
| "missing required argument: LEVEL" |
| ) |
| endif() |
| |
| zephyr_linker_section_configure( |
| SECTION ${OBJ_SECTION} |
| INPUT ".z_${OBJ_SECTION}_${OBJ_LEVEL}?_*" |
| SYMBOLS __${OBJ_SECTION}_${OBJ_LEVEL}_start |
| KEEP SORT NAME |
| ) |
| zephyr_linker_section_configure( |
| SECTION ${OBJ_SECTION} |
| INPUT ".z_${OBJ_SECTION}_${OBJ_LEVEL}??_*" |
| KEEP SORT NAME |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_section_configure(SECTION <section> [ALIGN <alignment>] |
| # [PASS [NOT] <name>] [PRIO <no>] [SORT <sort>] |
| # [ANY] [FIRST] [KEEP] |
| # ) |
| # |
| # Configure an output section with additional input sections. |
| # An output section can be configured with additional input sections besides its |
| # default section. |
| # For example, to add the input section `foo` to the output section bar, with KEEP |
| # attribute, call: |
| # zephyr_linker_section_configure(SECTION bar INPUT foo KEEP) |
| # |
| # ALIGN <alignment> : Will align the input section placement inside the load |
| # region with <alignment> |
| # FIRST : The first input section in the list should be marked as |
| # first section in output. |
| # SORT <NAME> : Sort the input sections according to <type>. |
| # Currently only `NAME` is supported. |
| # KEEP : Do not eliminate input section during linking |
| # PRIO : The priority of the input section. Per default, input |
| # sections order is not guaranteed by all linkers, but |
| # using priority Zephyr CMake linker will create sections |
| # such that order can be guaranteed. All unprioritized |
| # sections will internally be given a CMake process order |
| # priority counting from 100, so first unprioritized section |
| # is handled internal prio 100, next 101, and so on. |
| # To ensure a specific input section come before those, |
| # you may use `PRIO 50`, `PRIO 20` and so on. |
| # To ensure an input section is at the end, it is advised |
| # to use `PRIO 200` and above. |
| # PASS [NOT] <name> : The call should only be considered for linker pass where |
| # <name> is defined. It is possible to negate <name>, such |
| # as `PASS NOT <name>. |
| # For example, `PASS TEST` means the call is only effective |
| # on the `TEST` linker pass iteration. `PASS NOT TEST` on |
| # all iterations the are not `TEST`. |
| # FLAGS <flags> : Special section flags such as "+RO", +XO, "+ZI". |
| # ANY : ANY section flag in scatter file. |
| # The FLAGS and ANY arguments only has effect for scatter files. |
| # |
| function(zephyr_linker_section_configure) |
| set(options "ANY;FIRST;KEEP") |
| set(single_args "ALIGN;OFFSET;PRIO;SECTION;SORT") |
| set(multi_args "FLAGS;INPUT;PASS;SYMBOLS") |
| cmake_parse_arguments(SECTION "${options}" "${single_args}" "${multi_args}" ${ARGN}) |
| |
| if(SECTION_UNPARSED_ARGUMENTS) |
| message(FATAL_ERROR "zephyr_linker_section_configure(${ARGV0} ...) given unknown arguments: ${SECTION_UNPARSED_ARGUMENTS}") |
| endif() |
| |
| if(DEFINED SECTION_SYMBOLS) |
| list(LENGTH SECTION_SYMBOLS symbols_count) |
| if(${symbols_count} GREATER 2) |
| message(FATAL_ERROR "zephyr_linker_section_configure(SYMBOLS [start_sym [end_sym]]) takes maximum two symbol names (start and end).") |
| |
| endif() |
| endif() |
| |
| if(DEFINED SECTION_PASS) |
| list(LENGTH SECTION_PASS pass_length) |
| if(${pass_length} GREATER 1) |
| list(GET SECTION_PASS 0 pass_elem_0) |
| if((NOT (${pass_elem_0} STREQUAL "NOT")) OR (${pass_length} GREATER 2)) |
| message(FATAL_ERROR "zephyr_linker_section_configure(PASS takes maximum " |
| "a single argument of the form: '<pass name>' or 'NOT <pass_name>'.") |
| endif() |
| endif() |
| endif() |
| |
| set(SECTION) |
| zephyr_linker_arg_val_list(SECTION "${single_args}") |
| zephyr_linker_arg_val_list(SECTION "${options}") |
| zephyr_linker_arg_val_list(SECTION "${multi_args}") |
| |
| string(REPLACE ";" "\;" SECTION "${SECTION}") |
| set_property(TARGET linker |
| APPEND PROPERTY SECTION_SETTINGS "{${SECTION}}" |
| ) |
| endfunction() |
| |
| # Usage: |
| # zephyr_linker_symbol(SYMBOL <name> EXPR <expr>) |
| # |
| # Add additional user defined symbol to the generated linker script. |
| # |
| # SYMBOL <name>: Symbol name to be available. |
| # EXPR <expr> : Expression that defines the symbol. Due to linker limitations |
| # all expressions should only contain simple math, such as |
| # `+, -, *` and similar. The expression will go directly into the |
| # linker, and all `@<symbol>@` will be replaced with the referred |
| # symbol. |
| # |
| # Example: |
| # To create a new symbol `bar` pointing to the start VMA address of section |
| # `foo` + 1024, one can write: |
| # zephyr_linker_symbol(SYMBOL bar EXPR "(@foo@ + 1024)") |
| # |
| function(zephyr_linker_symbol) |
| set(single_args "EXPR;SYMBOL") |
| cmake_parse_arguments(SYMBOL "" "${single_args}" "" ${ARGN}) |
| |
| if(SECTION_UNPARSED_ARGUMENTS) |
| message(WARNING "zephyr_linker_symbol(${ARGV0} ...) given unknown " |
| "arguments: ${SECTION_UNPARSED_ARGUMENTS}" |
| ) |
| endif() |
| |
| set(SYMBOL) |
| zephyr_linker_arg_val_list(SYMBOL "${single_args}") |
| |
| string(REPLACE ";" "\;" SYMBOL "${SYMBOL}") |
| set_property(TARGET linker |
| APPEND PROPERTY SYMBOLS "{${SYMBOL}}" |
| ) |
| endfunction() |
| |
| # Internal helper macro for zephyr_linker*() functions. |
| # The macro will create a list of argument-value pairs for defined arguments |
| # that can be passed on to linker script generators and processed as a CMake |
| # function call using cmake_parse_arguments. |
| # |
| # For example, having the following argument and value: |
| # FOO: bar |
| # BAZ: <undefined> |
| # QUX: option set |
| # |
| # will create a list as: "FOO;bar;QUX;TRUE" which can then be parsed as argument |
| # list later. |
| macro(zephyr_linker_arg_val_list list arguments) |
| foreach(arg ${arguments}) |
| if(DEFINED ${list}_${arg}) |
| list(APPEND ${list} ${arg} "${${list}_${arg}}") |
| endif() |
| endforeach() |
| endmacro() |
| |
| ######################################################## |
| # 6. Function helper macros |
| ######################################################## |
| # |
| # Set of CMake macros to facilitate argument processing when defining functions. |
| # |
| |
| # |
| # Helper macro for verifying that at least one of the required arguments has |
| # been provided by the caller. |
| # |
| # A FATAL_ERROR will be raised if not one of the required arguments has been |
| # passed by the caller. |
| # |
| # Usage: |
| # zephyr_check_arguments_required(<function_name> <prefix> <arg1> [<arg2> ...]) |
| # |
| macro(zephyr_check_arguments_required function prefix) |
| set(check_defined DEFINED) |
| zephyr_check_flags_required(${function} ${prefix} ${ARGN}) |
| set(check_defined) |
| endmacro() |
| |
| # |
| # Helper macro for verifying that at least one of the required flags has |
| # been provided by the caller. |
| # |
| # A FATAL_ERROR will be raised if not one of the required arguments has been |
| # passed by the caller. |
| # |
| # Usage: |
| # zephyr_check_flags_required(<function_name> <prefix> <flag1> [<flag2> ...]) |
| # |
| macro(zephyr_check_flags_required function prefix) |
| set(required_found FALSE) |
| foreach(required ${ARGN}) |
| if(${check_defined} ${prefix}_${required}) |
| set(required_found TRUE) |
| endif() |
| endforeach() |
| |
| if(NOT required_found) |
| message(FATAL_ERROR "${function}(...) missing a required argument: ${ARGN}") |
| endif() |
| endmacro() |
| |
| # |
| # Helper macro for verifying that all the required arguments have been |
| # provided by the caller. |
| # |
| # A FATAL_ERROR will be raised if one of the required arguments is missing. |
| # |
| # Usage: |
| # zephyr_check_arguments_required_all(<function_name> <prefix> <arg1> [<arg2> ...]) |
| # |
| macro(zephyr_check_arguments_required_all function prefix) |
| foreach(required ${ARGN}) |
| if(NOT DEFINED ${prefix}_${required}) |
| message(FATAL_ERROR "${function}(...) missing a required argument: ${required}") |
| endif() |
| endforeach() |
| endmacro() |
| |
| # |
| # Helper macro for verifying that none of the mutual exclusive arguments are |
| # provided together. |
| # |
| # A FATAL_ERROR will be raised if any of the arguments are given together. |
| # |
| # Usage: |
| # zephyr_check_arguments_exclusive(<function_name> <prefix> <arg1> <arg2> [<arg3> ...]) |
| # |
| macro(zephyr_check_arguments_exclusive function prefix) |
| set(check_defined DEFINED) |
| zephyr_check_flags_exclusive(${function} ${prefix} ${ARGN}) |
| set(check_defined) |
| endmacro() |
| |
| # |
| # Helper macro for verifying that none of the mutual exclusive flags are |
| # provided together. |
| # |
| # A FATAL_ERROR will be raised if any of the flags are given together. |
| # |
| # Usage: |
| # zephyr_check_flags_exclusive(<function_name> <prefix> <flag1> <flag2> [<flag3> ...]) |
| # |
| macro(zephyr_check_flags_exclusive function prefix) |
| set(args_defined) |
| foreach(arg ${ARGN}) |
| if(${check_defined} ${prefix}_${arg}) |
| list(APPEND args_defined ${arg}) |
| endif() |
| endforeach() |
| list(LENGTH args_defined exclusive_length) |
| if(exclusive_length GREATER 1) |
| list(POP_FRONT args_defined argument) |
| message(FATAL_ERROR "${function}(${argument} ...) cannot be used with " |
| "argument: ${args_defined}" |
| ) |
| endif() |
| endmacro() |
| |
| ######################################################## |
| # 7. Linkable loadable extensions (llext) |
| ######################################################## |
| # |
| # These functions simplify the creation and management of linkable |
| # loadable extensions (llexts). |
| # |
| |
| # 7.1 Configuration functions |
| # |
| # The following functions simplify access to the compilation/link stage |
| # properties of an llext using the same API of the target_* functions. |
| # |
| |
| function(llext_compile_definitions target_name) |
| target_compile_definitions(${target_name}_llext_lib PRIVATE ${ARGN}) |
| endfunction() |
| |
| function(llext_compile_features target_name) |
| target_compile_features(${target_name}_llext_lib PRIVATE ${ARGN}) |
| endfunction() |
| |
| function(llext_compile_options target_name) |
| target_compile_options(${target_name}_llext_lib PRIVATE ${ARGN}) |
| endfunction() |
| |
| function(llext_include_directories target_name) |
| target_include_directories(${target_name}_llext_lib PRIVATE ${ARGN}) |
| endfunction() |
| |
| function(llext_link_options target_name) |
| target_link_options(${target_name}_llext_lib PRIVATE ${ARGN}) |
| endfunction() |
| |
| # 7.2 Build control functions |
| # |
| # The following functions add targets and subcommands to the build system |
| # to compile and link an llext. |
| # |
| |
| # Usage: |
| # add_llext_target(<target_name> |
| # OUTPUT <output_file> |
| # SOURCES <source_files> |
| # ) |
| # |
| # Add a custom target that compiles a set of source files to a .llext file. |
| # |
| # Output and source files must be specified using the OUTPUT and SOURCES |
| # arguments. Only one source file is supported when LLEXT_TYPE_ELF_OBJECT is |
| # selected, since there is no linking step in that case. |
| # |
| # The llext code will be compiled with mostly the same C compiler flags used |
| # in the Zephyr build, but with some important modifications. The list of |
| # flags to remove and flags to append is controlled respectively by the |
| # LLEXT_REMOVE_FLAGS and LLEXT_APPEND_FLAGS global variables. |
| # |
| # The following custom properties of <target_name> are defined and can be |
| # retrieved using the get_target_property() function: |
| # |
| # - lib_target Target name for the source compilation and/or link step. |
| # - lib_output The binary file resulting from compilation and/or |
| # linking steps. |
| # - pkg_input The file to be used as input for the packaging step. |
| # - pkg_output The final .llext file. |
| # |
| # Example usage: |
| # add_llext_target(hello_world |
| # OUTPUT ${PROJECT_BINARY_DIR}/hello_world.llext |
| # SOURCES ${PROJECT_SOURCE_DIR}/src/llext/hello_world.c |
| # ) |
| # will compile the source file src/llext/hello_world.c to a file |
| # named "${PROJECT_BINARY_DIR}/hello_world.llext". |
| # |
| function(add_llext_target target_name) |
| set(single_args OUTPUT) |
| set(multi_args SOURCES) |
| cmake_parse_arguments(PARSE_ARGV 1 LLEXT "${options}" "${single_args}" "${multi_args}") |
| |
| # Check that the llext subsystem is enabled for this build |
| if (NOT CONFIG_LLEXT) |
| message(FATAL_ERROR "add_llext_target: CONFIG_LLEXT must be enabled") |
| endif() |
| |
| # Source and output files must be provided |
| zephyr_check_arguments_required_all("add_llext_target" LLEXT OUTPUT SOURCES) |
| |
| list(LENGTH LLEXT_SOURCES source_count) |
| if(CONFIG_LLEXT_TYPE_ELF_OBJECT AND NOT (source_count EQUAL 1)) |
| message(FATAL_ERROR "add_llext_target: only one source file is supported " |
| "for ELF object file builds") |
| endif() |
| |
| set(llext_pkg_output ${LLEXT_OUTPUT}) |
| set(source_files ${LLEXT_SOURCES}) |
| |
| # Convert the LLEXT_REMOVE_FLAGS list to a regular expression, and use it to |
| # filter out these flags from the Zephyr target settings |
| list(TRANSFORM LLEXT_REMOVE_FLAGS |
| REPLACE "(.+)" "^\\1$" |
| OUTPUT_VARIABLE llext_remove_flags_regexp |
| ) |
| string(REPLACE ";" "|" llext_remove_flags_regexp "${llext_remove_flags_regexp}") |
| if ("${llext_remove_flags_regexp}" STREQUAL "") |
| # an empty regexp would match anything, we actually need the opposite |
| # so set it to match empty strings |
| set(llext_remove_flags_regexp "^$") |
| endif() |
| set(zephyr_flags |
| "$<TARGET_PROPERTY:zephyr_interface,INTERFACE_COMPILE_OPTIONS>" |
| ) |
| set(zephyr_filtered_flags |
| "$<FILTER:${zephyr_flags},EXCLUDE,${llext_remove_flags_regexp}>" |
| ) |
| |
| # Compile the source file using current Zephyr settings but a different |
| # set of flags to obtain the desired llext object type. |
| set(llext_lib_target ${target_name}_llext_lib) |
| if(CONFIG_LLEXT_TYPE_ELF_OBJECT) |
| |
| # Create an object library to compile the source file |
| add_library(${llext_lib_target} OBJECT ${source_files}) |
| set(llext_lib_output $<TARGET_OBJECTS:${llext_lib_target}>) |
| |
| elseif(CONFIG_LLEXT_TYPE_ELF_RELOCATABLE) |
| |
| # CMake does not directly support a "RELOCATABLE" library target. |
| # The "SHARED" target would be similar, but that unavoidably adds |
| # a "-shared" flag to the linker command line which does firmly |
| # conflict with "-r". |
| # A workaround is to use an executable target and make the linker |
| # output a relocatable file. The output file suffix is changed so |
| # the result looks like the object file it actually is. |
| add_executable(${llext_lib_target} EXCLUDE_FROM_ALL ${source_files}) |
| target_link_options(${llext_lib_target} PRIVATE |
| $<TARGET_PROPERTY:linker,partial_linking>) |
| set_target_properties(${llext_lib_target} PROPERTIES |
| SUFFIX ${CMAKE_C_OUTPUT_EXTENSION}) |
| set(llext_lib_output $<TARGET_FILE:${llext_lib_target}>) |
| |
| # Add the llext flags to the linking step as well |
| target_link_options(${llext_lib_target} PRIVATE |
| ${LLEXT_APPEND_FLAGS} |
| ) |
| |
| elseif(CONFIG_LLEXT_TYPE_ELF_SHAREDLIB) |
| |
| # Create a shared library |
| add_library(${llext_lib_target} SHARED ${source_files}) |
| set(llext_lib_output $<TARGET_FILE:${llext_lib_target}>) |
| |
| # Add the llext flags to the linking step as well |
| target_link_options(${llext_lib_target} PRIVATE |
| ${LLEXT_APPEND_FLAGS} |
| ) |
| |
| endif() |
| |
| target_compile_definitions(${llext_lib_target} PRIVATE |
| $<TARGET_PROPERTY:zephyr_interface,INTERFACE_COMPILE_DEFINITIONS> |
| ) |
| target_compile_options(${llext_lib_target} PRIVATE |
| ${zephyr_filtered_flags} |
| ${LLEXT_APPEND_FLAGS} |
| ) |
| target_include_directories(${llext_lib_target} PRIVATE |
| $<TARGET_PROPERTY:zephyr_interface,INTERFACE_INCLUDE_DIRECTORIES> |
| ) |
| target_include_directories(${llext_lib_target} SYSTEM PUBLIC |
| $<TARGET_PROPERTY:zephyr_interface,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES> |
| ) |
| add_dependencies(${llext_lib_target} |
| zephyr_interface |
| zephyr_generated_headers |
| ) |
| |
| # Set up an intermediate processing step between compilation and packaging |
| # to be used to support POST_BUILD commands on targets that do not use a |
| # dynamic library. |
| set(llext_proc_target ${target_name}_llext_proc) |
| set(llext_pkg_input ${PROJECT_BINARY_DIR}/${target_name}.llext.pkg_input) |
| add_custom_target(${llext_proc_target} DEPENDS ${llext_pkg_input}) |
| set_property(TARGET ${llext_proc_target} PROPERTY has_post_build_cmds 0) |
| |
| # By default this target must copy the `lib_output` binary file to the |
| # expected `pkg_input` location. If actual POST_BUILD commands are defined, |
| # they will take care of this and the default copy is replaced by a no-op. |
| set(has_post_build_cmds "$<TARGET_PROPERTY:${llext_proc_target},has_post_build_cmds>") |
| set(noop_cmd ${CMAKE_COMMAND} -E true) |
| set(copy_cmd ${CMAKE_COMMAND} -E copy ${llext_lib_output} ${llext_pkg_input}) |
| add_custom_command( |
| OUTPUT ${llext_pkg_input} |
| COMMAND "$<IF:${has_post_build_cmds},${noop_cmd},${copy_cmd}>" |
| DEPENDS ${llext_lib_target} ${llext_lib_output} |
| COMMAND_EXPAND_LISTS |
| ) |
| |
| # Type-specific packaging of the built binary file into an .llext file |
| if(CONFIG_LLEXT_TYPE_ELF_OBJECT) |
| |
| # No packaging required, simply copy the object file |
| add_custom_command( |
| OUTPUT ${llext_pkg_output} |
| COMMAND ${CMAKE_COMMAND} -E copy ${llext_pkg_input} ${llext_pkg_output} |
| DEPENDS ${llext_proc_target} ${llext_pkg_input} |
| ) |
| |
| elseif(CONFIG_LLEXT_TYPE_ELF_RELOCATABLE) |
| |
| # Need to remove just some sections from the relocatable object |
| # (using strip in this case would remove _all_ symbols) |
| add_custom_command( |
| OUTPUT ${llext_pkg_output} |
| COMMAND $<TARGET_PROPERTY:bintools,elfconvert_command> |
| $<TARGET_PROPERTY:bintools,elfconvert_flag> |
| $<TARGET_PROPERTY:bintools,elfconvert_flag_section_remove>.xt.* |
| $<TARGET_PROPERTY:bintools,elfconvert_flag_infile>${llext_pkg_input} |
| $<TARGET_PROPERTY:bintools,elfconvert_flag_outfile>${llext_pkg_output} |
| $<TARGET_PROPERTY:bintools,elfconvert_flag_final> |
| DEPENDS ${llext_proc_target} ${llext_pkg_input} |
| ) |
| |
| elseif(CONFIG_LLEXT_TYPE_ELF_SHAREDLIB) |
| |
| # Need to strip the shared library of some sections |
| add_custom_command( |
| OUTPUT ${llext_pkg_output} |
| COMMAND $<TARGET_PROPERTY:bintools,strip_command> |
| $<TARGET_PROPERTY:bintools,strip_flag> |
| $<TARGET_PROPERTY:bintools,strip_flag_remove_section>.xt.* |
| $<TARGET_PROPERTY:bintools,strip_flag_infile>${llext_pkg_input} |
| $<TARGET_PROPERTY:bintools,strip_flag_outfile>${llext_pkg_output} |
| $<TARGET_PROPERTY:bintools,strip_flag_final> |
| DEPENDS ${llext_proc_target} ${llext_pkg_input} |
| ) |
| |
| endif() |
| |
| # Add user-visible target and dependency, and fill in properties |
| get_filename_component(output_name ${llext_pkg_output} NAME) |
| add_custom_target(${target_name} |
| COMMENT "Generating ${output_name}" |
| DEPENDS ${llext_pkg_output} |
| ) |
| set_target_properties(${target_name} PROPERTIES |
| lib_target ${llext_lib_target} |
| lib_output ${llext_lib_output} |
| pkg_input ${llext_pkg_input} |
| pkg_output ${llext_pkg_output} |
| ) |
| endfunction() |
| |
| # Usage: |
| # add_llext_command( |
| # TARGET <target_name> |
| # PRE_BUILD | POST_BUILD | POST_PKG |
| # COMMAND <command> [...] |
| # ) |
| # |
| # Add a custom command to an llext target that will be executed during |
| # the build. The command will be executed at the specified build step and |
| # can refer to <target>'s properties for build-specific details. |
| # |
| # The different build steps are: |
| # - PRE_BUILD: Before the llext code is linked, if the architecture uses |
| # dynamic libraries. This step can access `lib_target` and |
| # its own properties. |
| # - POST_BUILD: After the llext code is built, but before packaging |
| # it in an .llext file. This step is expected to create a |
| # `pkg_input` file by reading the contents of `lib_output`. |
| # - POST_PKG: After the .llext file has been created. This can operate on |
| # the final llext file `pkg_output`. |
| # |
| # Anything else after COMMAND will be passed to add_custom_command() as-is |
| # (including multiple commands and other options). |
| function(add_llext_command) |
| set(options PRE_BUILD POST_BUILD POST_PKG) |
| set(single_args TARGET) |
| # COMMAND and other options are passed to add_custom_command() as-is |
| |
| cmake_parse_arguments(PARSE_ARGV 0 LLEXT "${options}" "${single_args}" "${multi_args}") |
| zephyr_check_arguments_required_all("add_llext_command" LLEXT TARGET) |
| |
| # Check the target exists and refers to an llext target |
| set(target_name ${LLEXT_TARGET}) |
| set(llext_lib_target ${target_name}_llext_lib) |
| set(llext_proc_target ${target_name}_llext_proc) |
| if(NOT TARGET ${llext_lib_target}) |
| message(FATAL_ERROR "add_llext_command: not an llext target: ${target_name}") |
| endif() |
| |
| # ARM uses an object file representation so there is no link step. |
| if(CONFIG_ARM AND LLEXT_PRE_BUILD) |
| message(FATAL_ERROR |
| "add_llext_command: PRE_BUILD not supported on this arch") |
| endif() |
| |
| # Determine the build step and the target to attach the command to |
| # based on the provided options |
| if(LLEXT_PRE_BUILD) |
| # > before the object files are linked: |
| # - execute user command(s) before the lib target's link step. |
| set(cmd_target ${llext_lib_target}) |
| set(build_step PRE_LINK) |
| elseif(LLEXT_POST_BUILD) |
| # > after linking, but before llext packaging: |
| # - stop default file copy to prevent user files from being clobbered; |
| # - execute user command(s) after the (now empty) `llext_proc_target`. |
| set_property(TARGET ${llext_proc_target} PROPERTY has_post_build_cmds 1) |
| set(cmd_target ${llext_proc_target}) |
| set(build_step POST_BUILD) |
| elseif(LLEXT_POST_PKG) |
| # > after the final llext binary is ready: |
| # - execute user command(s) after the main target is done. |
| set(cmd_target ${target_name}) |
| set(build_step POST_BUILD) |
| else() |
| message(FATAL_ERROR "add_llext_command: build step must be provided") |
| endif() |
| |
| # Check that the first unparsed argument is the word COMMAND |
| list(GET LLEXT_UNPARSED_ARGUMENTS 0 command_str) |
| if(NOT command_str STREQUAL "COMMAND") |
| message(FATAL_ERROR "add_llext_command: COMMAND argument must be provided") |
| endif() |
| |
| # Add the actual command(s) to the target |
| add_custom_command( |
| TARGET ${cmd_target} ${build_step} |
| ${LLEXT_UNPARSED_ARGUMENTS} |
| COMMAND_EXPAND_LISTS |
| ) |
| endfunction() |