# SPDX-License-Identifier: Apache-2.0 ######################################################## # 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 *_if_kconfig # 2.2 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 ######################################################## # 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) foreach(arg ${ARGV}) if(IS_ABSOLUTE ${arg}) set(path ${arg}) else() set(path ${CMAKE_CURRENT_SOURCE_DIR}/${arg}) endif() target_include_directories(zephyr_interface INTERFACE ${path}) endforeach() endfunction() # https://cmake.org/cmake/help/latest/command/target_include_directories.html function(zephyr_system_include_directories) foreach(arg ${ARGV}) if(IS_ABSOLUTE ${arg}) set(path ${arg}) else() set(path ${CMAKE_CURRENT_SOURCE_DIR}/${arg}) endif() target_include_directories(zephyr_interface SYSTEM INTERFACE ${path}) endforeach() 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() # 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 ${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 ${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 ${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) 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) get_property_and_add_prefix(flags zephyr_interface INTERFACE_INCLUDE_DIRECTORIES "-I" ${ARGN} ) process_flags(${lang} flags output_list) set(${i} ${output_list} PARENT_SCOPE) endfunction() function(zephyr_get_system_include_directories_for_lang lang i) get_property_and_add_prefix(flags zephyr_interface INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "-isystem" ${ARGN} ) process_flags(${lang} flags output_list) set(${i} ${output_list} PARENT_SCOPE) endfunction() function(zephyr_get_compile_definitions_for_lang lang i) get_property_and_add_prefix(flags zephyr_interface INTERFACE_COMPILE_DEFINITIONS "-D" ${ARGN} ) process_flags(${lang} flags output_list) set(${i} ${output_list} PARENT_SCOPE) endfunction() function(zephyr_get_compile_options_for_lang lang i) get_property(flags TARGET zephyr_interface PROPERTY INTERFACE_COMPILE_OPTIONS) process_flags(${lang} flags output_list) set(${i} ${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(x STREQUAL STRIP_PREFIX) set(${return_dict}_STRIP_PREFIX 1 PARENT_SCOPE) endif() endforeach() endfunction() function(process_flags lang input output) # The flags might contains compile language generator expressions that # look like this: # $<$:-fno-exceptions> # # 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 ":([^>]+)>") set(is_compile_lang_generator_expression 1) if(${l} STREQUAL ${lang}) list(APPEND tmp_list ${CMAKE_MATCH_1}) break() endif() endif() endforeach() if(NOT is_compile_lang_generator_expression) 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_ # # 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}) 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_libray_add_sources(...) # 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_ functions # 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 ${item}) set(lib_name options_interface_lib_${uniqueness}) if (TARGET ${lib_name}) # ${item} already added, ignoring duplicate just like CMake does return() endif() add_library( ${lib_name} INTERFACE) target_compile_options(${lib_name} INTERFACE ${item} ${ARGN}) 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() # Add the existing CMake library 'library' to the global list of # Zephyr CMake libraries. This is done automatically by the # constructor but must called explicitly on CMake libraries that do # not use a zephyr library constructor. function(zephyr_append_cmake_library library) 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 define 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" $ "${partition}") 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 it's header files and call it's # 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() to use this build # information. 'app' is treated as a special case for usability # reasons; a Kconfig option (CONFIG_APP_LINK_WITH_) # 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/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 which 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 including the boilerplate file: # 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 # last, so they take precedence. ${explicit} ) # Add the finalized runner to the global property list. set_property(GLOBAL APPEND PROPERTY ZEPHYR_RUNNERS ${runner}) 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- can not be tested by # check_compiler_flag, they will always pass, but -W can be # tested, so to test -Wno- flags we test -W # instead. if("${option}" MATCHES "-Wno-(.*)") set(possibly_translated_option -W${CMAKE_MATCH_1}) 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 sanitycheck 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 blacklist. 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( [SORT_KEY ] ) # # is one or more .ld formatted files whose contents will be # copied/included verbatim into the given in the global linker.ld. # Preprocessor directives work inside . Relative paths are resolved # relative to the calling file, like zephyr_sources(). # 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, and openisa_rv32m1. # RAM_SECTIONS Inside the RAMABLE_REGION GROUP. # SECTIONS Near the end of the file. Don't use this when linking into # RAMABLE_REGION, use RAM_SECTIONS instead. # 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, 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 or RAM_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(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") # 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 ${rom_start_path} "") file(WRITE ${noinit_path} "") file(WRITE ${rwdata_path} "") file(WRITE ${rodata_path} "") set_property(GLOBAL PROPERTY snippet_files_cleared true) endif() # Choose destination file, based on the argument. if ("${location}" STREQUAL "SECTIONS") set(snippet_path "${sections_path}") elseif("${location}" STREQUAL "RAM_SECTIONS") set(snippet_path "${ram_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}") 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}\"") # 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 function for CONFIG_CODE_DATA_RELOCATION # Call this function with 2 arguments file and then memory location function(zephyr_code_relocate file location) set_property(TARGET code_data_relocation_target APPEND PROPERTY COMPILE_DEFINITIONS "${location}:${CMAKE_CURRENT_SOURCE_DIR}/${file}") 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() ######################################################## # 2. Kconfig-aware extensions ######################################################## # # Kconfig is a configuration language developed for the Linux # kernel. The below functions integrate CMake with Kconfig. # # 2.1 *_if_kconfig # # Functions for conditionally including directories and source files # that have matching KConfig values. # # zephyr_library_sources_if_kconfig(fft.c) # is the same as # zephyr_library_sources_ifdef(CONFIG_FFT fft.c) # # add_subdirectory_if_kconfig(serial) # is the same as # add_subdirectory_ifdef(CONFIG_SERIAL serial) function(add_subdirectory_if_kconfig dir) string(TOUPPER config_${dir} UPPER_CASE_CONFIG) add_subdirectory_ifdef(${UPPER_CASE_CONFIG} ${dir}) endfunction() function(target_sources_if_kconfig target scope item) get_filename_component(item_basename ${item} NAME_WE) string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG) target_sources_ifdef(${UPPER_CASE_CONFIG} ${target} ${scope} ${item}) endfunction() function(zephyr_library_sources_if_kconfig item) get_filename_component(item_basename ${item} NAME_WE) string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG) zephyr_library_sources_ifdef(${UPPER_CASE_CONFIG} ${item}) endfunction() function(zephyr_sources_if_kconfig item) get_filename_component(item_basename ${item} NAME_WE) string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG) zephyr_sources_ifdef(${UPPER_CASE_CONFIG} ${item}) endfunction() # 2.2 Misc # # import_kconfig( []) # # 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. function(import_kconfig prefix kconfig_fragment) # Parse the lines prefixed with 'prefix' in ${kconfig_fragment} file( STRINGS ${kconfig_fragment} DOT_CONFIG_LIST REGEX "^${prefix}" ENCODING "UTF-8" ) foreach (CONFIG ${DOT_CONFIG_LIST}) # CONFIG could look like: CONFIG_NET_BUF=y # Match the first part, the variable name string(REGEX MATCH "[^=]+" CONF_VARIABLE_NAME ${CONFIG}) # Match the second part, variable value string(REGEX MATCH "=(.+$)" CONF_VARIABLE_VALUE ${CONFIG}) # The variable name match we just did included the '=' symbol. To just get the # part on the RHS we use match group 1 set(CONF_VARIABLE_VALUE ${CMAKE_MATCH_1}) if("${CONF_VARIABLE_VALUE}" MATCHES "^\"(.*)\"$") # Is surrounded by quotes set(CONF_VARIABLE_VALUE ${CMAKE_MATCH_1}) endif() set("${CONF_VARIABLE_NAME}" "${CONF_VARIABLE_VALUE}" PARENT_SCOPE) list(APPEND keys "${CONF_VARIABLE_NAME}") endforeach() foreach(outvar ${ARGN}) set(${outvar} "${keys}" PARENT_SCOPE) endforeach() 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 # "_ifdef(CONDITION args)" # Becomes # """ # if(CONDITION) # (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 dir) if(${${feature_toggle}}) add_subdirectory(${dir}) 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_library_sources_ifndef feature_toggle source) if(NOT ${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_sources_ifndef feature_toggle) if(NOT ${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_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() 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(target_cc_option_ifndef feature_toggle target scope option) if(NOT ${feature_toggle}) target_cc_option(${target} ${scope} ${option}) endif() endfunction() function(zephyr_cc_option_ifndef feature_toggle) if(NOT ${feature_toggle}) zephyr_cc_option(${ARGN}) endif() endfunction() function(zephyr_compile_options_ifndef feature_toggle) if(NOT ${feature_toggle}) zephyr_compile_options(${ARGN}) endif() endfunction() # 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_CPLUSPLUS) 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} $<$:${option1}> $<$:${option1}> ) elseif(option2) target_compile_options(${target} ${scope} $<$:${option2}> $<$:${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) foreach(option ${ARGN}) 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}) target_link_libraries_ifdef(${check} ${target} ${scope} ${option}) 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(READ ${input_file} input) # The file is formatted like this: # empty_file.o: misc/empty_file.c \ # nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts \ # nrf52840_qiaa.dtsi # Get rid of the backslashes string(REPLACE "\\" ";" input_as_list ${input}) # Pop the first line and treat it specially list(GET input_as_list 0 first_input_line) string(FIND ${first_input_line} ": " index) math(EXPR j "${index} + 2") string(SUBSTRING ${first_input_line} ${j} -1 first_include_file) list(REMOVE_AT input_as_list 0) list(APPEND result ${first_include_file}) # Add the other lines list(APPEND result ${input_as_list}) # Strip away the newlines and whitespaces list(TRANSFORM result STRIP) set(${include_files} ${result} PARENT_SCOPE) endfunction() # 3.4. Debugging CMake # Usage: # print(BOARD) # # will print: "BOARD: nrf52dk_nrf52832" 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() function(print_usage) if(NOT CMAKE_MAKE_PROGRAM) # Create dummy project, in order to obtain make program for correct usage printing. project(dummy_print_usage) endif() message("see usage:") string(REPLACE ";" " " BOARD_ROOT_SPACE_SEPARATED "${BOARD_ROOT}") string(REPLACE ";" " " SHIELD_LIST_SPACE_SEPARATED "${SHIELD_LIST}") execute_process( COMMAND ${CMAKE_COMMAND} -DZEPHYR_BASE=${ZEPHYR_BASE} -DBOARD_ROOT_SPACE_SEPARATED=${BOARD_ROOT_SPACE_SEPARATED} -DSHIELD_LIST_SPACE_SEPARATED=${SHIELD_LIST_SPACE_SEPARATED} -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM} -P ${ZEPHYR_BASE}/cmake/usage/usage.cmake ) endfunction() # 3.5. File system management function(check_if_directory_is_writeable dir ok) execute_process( COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/dir_is_writeable.py ${dir} RESULT_VARIABLE ret ) if("${ret}" STREQUAL "0") # The directory is write-able set(${ok} 1 PARENT_SCOPE) else() set(${ok} 0 PARENT_SCOPE) endif() endfunction() function(find_appropriate_cache_directory dir) set(env_suffix_LOCALAPPDATA .cache) if(CMAKE_HOST_APPLE) # On macOS, ~/Library/Caches is the preferred cache directory. set(env_suffix_HOME Library/Caches) else() set(env_suffix_HOME .cache) endif() # Determine which env vars should be checked if(CMAKE_HOST_APPLE) set(dirs HOME) elseif(CMAKE_HOST_WIN32) set(dirs LOCALAPPDATA) else() # Assume Linux when we did not detect 'mac' or 'win' # # On Linux, freedesktop.org recommends using $XDG_CACHE_HOME if # that is defined and defaulting to $HOME/.cache otherwise. set(dirs XDG_CACHE_HOME HOME ) endif() foreach(env_var ${dirs}) if(DEFINED ENV{${env_var}}) set(env_dir $ENV{${env_var}}) set(test_user_dir ${env_dir}/${env_suffix_${env_var}}) check_if_directory_is_writeable(${test_user_dir} ok) if(${ok}) # The directory is write-able set(user_dir ${test_user_dir}) break() else() # The directory was not writeable, keep looking for a suitable # directory endif() endif() endforeach() # Populate local_dir with a suitable directory for caching # files. Prefer a directory outside of the git repository because it # is good practice to have clean git repositories. if(DEFINED user_dir) # Zephyr's cache files go in the "zephyr" subdirectory of the # user's cache directory. set(local_dir ${user_dir}/zephyr) else() set(local_dir ${ZEPHYR_BASE}/.cache) endif() set(${dir} ${local_dir} PARENT_SCOPE) endfunction() 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()