You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3668 lines
125 KiB
3668 lines
125 KiB
# 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 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_* |
|
# 5. Zephyr linker functions |
|
# 5.1. zephyr_linker* |
|
|
|
######################################################## |
|
# 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 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 substitue `$<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-deplucation, 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}) |
|
|
|
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(...) |
|
# |
|
# 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 |
|
# |
|
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) |
|
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 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" $<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 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(<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/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 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 |
|
# 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) |
|
zephyr_check_cache(RIMAGE_TARGET) |
|
set(RIMAGE_TARGET ${target} CACHE STRING "rimage target") |
|
endfunction() |
|
|
|
# Zephyr board revision: |
|
# |
|
# This section provides a function for revision checking. |
|
|
|
# Usage: |
|
# board_check_revision(FORMAT <LETTER | 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 | MAJOR.MINOR.PATCH>: Specify the revision format. |
|
# LETTER: Revision format is a single letter from A - Z. |
|
# 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 |
|
# |
|
# 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 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(NOT DEFINED BOARD_REVISION) |
|
if(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 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 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 | 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}) |
|
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-(.*)") |
|
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 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(). |
|
# <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. |
|
# Note: On RISC-V the rom_start section will be after vector section. |
|
# RAM_SECTIONS Inside the RAMABLE_REGION GROUP, not initialized. |
|
# DATA_SECTIONS Inside the RAMABLE_REGION GROUP, initialized. |
|
# SECTIONS Near the end of the file. Don't use this when linking into |
|
# RAMABLE_REGION, use RAM_SECTIONS instead. |
|
# <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, 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") |
|
|
|
# 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} "") |
|
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}") |
|
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) |
|
if(NOT IS_ABSOLUTE ${file}) |
|
set(file ${CMAKE_CURRENT_SOURCE_DIR}/${file}) |
|
endif() |
|
set_property(TARGET code_data_relocation_target |
|
APPEND PROPERTY COMPILE_DEFINITIONS |
|
"${location}:${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() |
|
|
|
# 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. |
|
# |
|
# Usage: |
|
# zephyr_build_string(<out-variable> |
|
# BOARD <board> |
|
# [BOARD_REVISION <revision>] |
|
# [BUILD <type>] |
|
# ) |
|
# |
|
# <out-variable>: Output variable where the 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. |
|
# |
|
# 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. |
|
# |
|
function(zephyr_build_string outvar) |
|
set(single_args BOARD BOARD_REVISION BUILD) |
|
|
|
cmake_parse_arguments(BUILD_STR "" "${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() |
|
|
|
set(${outvar} ${BUILD_STR_BOARD}) |
|
|
|
if(DEFINED BUILD_STR_BOARD_REVISION) |
|
string(REPLACE "." "_" revision_string ${BUILD_STR_BOARD_REVISION}) |
|
set(${outvar} "${${outvar}}_${revision_string}") |
|
endif() |
|
|
|
if(BUILD_STR_BUILD) |
|
set(${outvar} "${${outvar}}_${BUILD_STR_BUILD}") |
|
endif() |
|
|
|
# This updates the provided outvar in parent scope (callers scope) |
|
set(${outvar} ${${outvar}} PARENT_SCOPE) |
|
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>]) |
|
# |
|
# 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 |
|
# "<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() |
|
|
|
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() |
|
|
|
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_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} |
|
$<$<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. |
|
# |
|
# 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(CONFIG_CPLUSPLUS) |
|
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_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() |
|
|
|
# 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() |
|
|
|
# Usage: |
|
# zephyr_file(<mode> <arg> ...) |
|
# |
|
# Zephyr file function extension. |
|
# This function currently support 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 |
|
# |
|
# CONF_FILES <path>: Find all configuration files in path and return them in a |
|
# list. Configuration files will be: |
|
# - DTS: Overlay files (.overlay) |
|
# - Kconfig: Config fragments (.conf) |
|
# 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. |
|
# |
|
# DTS <list>: List to populate with DTS overlay files |
|
# KCONF <list>: List to populate with Kconfig fragment files |
|
# 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 |
|
# |
|
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(single_args CONF_FILES BOARD BOARD_REVISION DTS KCONF BUILD) |
|
endif() |
|
|
|
cmake_parse_arguments(FILE "" "${single_args}" "" ${ARGN}) |
|
if(FILE_UNPARSED_ARGUMENTS) |
|
message(FATAL_ERROR "zephyr_file(${ARGV0} <val> ...) given unknown arguments: ${FILE_UNPARSED_ARGUMENTS}") |
|
endif() |
|
|
|
|
|
if(FILE_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{${FILE_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 ${FILE_APPLICATION_ROOT} ${path} index) |
|
if(NOT ${index} LESS 0) |
|
list(REMOVE_AT ${FILE_APPLICATION_ROOT} ${index}) |
|
list(INSERT ${FILE_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 ${${FILE_APPLICATION_ROOT}}) |
|
if(NOT IS_ABSOLUTE ${path}) |
|
message(FATAL_ERROR |
|
"Relative path encountered in scoped variable: ${FILE_APPLICATION_ROOT}, value=${path}\n \ |
|
Please adjust any `set(${FILE_APPLICATION_ROOT} ${path})` or `list(APPEND ${FILE_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(${FILE_APPLICATION_ROOT} ${${FILE_APPLICATION_ROOT}} PARENT_SCOPE) |
|
endif() |
|
|
|
if(FILE_CONF_FILES) |
|
if(DEFINED FILE_BOARD_REVISION AND NOT FILE_BOARD) |
|
message(FATAL_ERROR |
|
"zephyr_file(${ARGV0} <path> BOARD_REVISION ${FILE_BOARD_REVISION} ...)" |
|
" given without BOARD argument, please specify BOARD" |
|
) |
|
endif() |
|
|
|
if(NOT DEFINED FILE_BOARD) |
|
# Defaulting to system wide settings when BOARD is not given as argument |
|
set(FILE_BOARD ${BOARD}) |
|
if(DEFINED BOARD_REVISION) |
|
set(FILE_BOARD_REVISION ${BOARD_REVISION}) |
|
endif() |
|
endif() |
|
|
|
zephyr_build_string(filename |
|
BOARD ${FILE_BOARD} |
|
BUILD ${FILE_BUILD} |
|
) |
|
set(filename_list ${filename}) |
|
|
|
zephyr_build_string(filename |
|
BOARD ${FILE_BOARD} |
|
BOARD_REVISION ${FILE_BOARD_REVISION} |
|
BUILD ${FILE_BUILD} |
|
) |
|
list(APPEND filename_list ${filename}) |
|
list(REMOVE_DUPLICATES filename_list) |
|
|
|
if(FILE_DTS) |
|
foreach(filename ${filename_list}) |
|
if(EXISTS ${FILE_CONF_FILES}/${filename}.overlay) |
|
list(APPEND ${FILE_DTS} ${FILE_CONF_FILES}/${filename}.overlay) |
|
endif() |
|
endforeach() |
|
|
|
# This updates the provided list in parent scope (callers scope) |
|
set(${FILE_DTS} ${${FILE_DTS}} PARENT_SCOPE) |
|
endif() |
|
|
|
if(FILE_KCONF) |
|
foreach(filename ${filename_list}) |
|
if(EXISTS ${FILE_CONF_FILES}/${filename}.conf) |
|
list(APPEND ${FILE_KCONF} ${FILE_CONF_FILES}/${filename}.conf) |
|
endif() |
|
endforeach() |
|
|
|
# This updates the provided list in parent scope (callers scope) |
|
set(${FILE_KCONF} ${${FILE_KCONF}} PARENT_SCOPE) |
|
endif() |
|
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_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 BOARD 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 board |
|
# 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) |
|
# 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() |
|
elseif(cli_argument) |
|
set(${variable} ${cli_argument}) |
|
|
|
elseif(DEFINED ENV{${variable}}) |
|
set(${variable} $ENV{${variable}}) |
|
|
|
elseif(app_cmake_lists) |
|
set(${variable} ${app_cmake_lists}) |
|
|
|
elseif(${CACHE_VAR_REQUIRED}) |
|
message(FATAL_ERROR "${variable} is not being defined on the CMake command-line in the environment or by the app.") |
|
endif() |
|
|
|
# Store the specified variable in parent scope and the cache |
|
set(${variable} ${${variable}} PARENT_SCOPE) |
|
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 cmake/app/boilerplate.cmake, 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: |
|
# 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 echo "" |
|
BYPRODUCTS ${TB_BYPRODUCTS} |
|
COMMENT "Logical command for additional byproducts on target: ${TB_TARGET}" |
|
) |
|
endfunction() |
|
|
|
######################################################## |
|
# 4. Zephyr devicetree function |
|
######################################################## |
|
# 4.1. dt_* |
|
# |
|
# The following methods are for retrieving devicetree information in CMake. |
|
# |
|
# Note: In CMake we refer to the nodes using the node's path, therefore there |
|
# is no dt_path(...) function for obtaining a node identifier. |
|
|
|
# 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_node_exists(<var> PATH <path>) |
|
# |
|
# Tests whether a node with path <path> exists in the devicetree. |
|
# |
|
# 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_existsl(${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() |
|
|
|
get_target_property(${var} devicetree_target "DT_NODE|${DT_NODE_PATH}") |
|
|
|
if(${var}) |
|
set(${var} ${${var}} 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 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_node_exists(${var} PATH ${DT_NODE_PATH}) |
|
if(NOT ${${var}}) |
|
set(${var} FALSE PARENT_SCOPE) |
|
endif() |
|
|
|
dt_prop(${var} PATH ${DT_NODE_PATH} PROPERTY status) |
|
|
|
if(NOT DEFINED ${var} OR "${${var}}" STREQUAL ok) |
|
set(${var} okay) |
|
endif() |
|
|
|
if(${var} 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. |
|
# |
|
# 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() |
|
|
|
get_property(exists TARGET devicetree_target |
|
PROPERTY "DT_PROP|${DT_PROP_PATH}|${DT_PROP_PROPERTY}" |
|
SET |
|
) |
|
|
|
if(NOT exists) |
|
set(${var} PARENT_SCOPE) |
|
return() |
|
endif() |
|
|
|
get_target_property(val devicetree_target |
|
"DT_PROP|${DT_PROP_PATH}|${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_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. |
|
# |
|
# <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() |
|
|
|
get_target_property(${var} devicetree_target "DT_REG|${DT_REG_PATH}|NUM") |
|
|
|
set(${var} ${${var}} PARENT_SCOPE) |
|
endfunction() |
|
|
|
# Usage: |
|
# dt_reg_addr(<var> PATH <path> [INDEX <idx>]) |
|
# |
|
# Get the base address of the register block at index <idx>. |
|
# If <idx> is omitted, then the value at index 0 will be returned. |
|
# |
|
# The value will be returned in the <var> parameter. |
|
# |
|
# 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. |
|
# |
|
# <var> : Return variable where the address value will be stored |
|
# PATH <path> : Node path |
|
# INDEX <idx> : Index number |
|
function(dt_reg_addr var) |
|
set(req_single_args "PATH") |
|
set(single_args "INDEX") |
|
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(NOT DEFINED DT_REG_INDEX) |
|
set(DT_REG_INDEX 0) |
|
endif() |
|
|
|
get_target_property(${var}_list devicetree_target "DT_REG|${DT_REG_PATH}|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>]) |
|
# |
|
# Get the size of the register block at index <idx>. |
|
# If INDEX is omitted, then the value at index 0 will be returned. |
|
# |
|
# The value will be returned in the <value> parameter. |
|
# |
|
# <var> : Return variable where the size value will be stored |
|
# PATH <path> : Node path |
|
# INDEX <idx> : Index number |
|
function(dt_reg_size var) |
|
set(req_single_args "PATH") |
|
set(single_args "INDEX") |
|
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(NOT DEFINED DT_REG_INDEX) |
|
set(DT_REG_INDEX 0) |
|
endif() |
|
|
|
get_target_property(${var}_list devicetree_target "DT_REG|${DT_REG_PATH}|SIZE") |
|
|
|
list(GET ${var}_list ${DT_REG_INDEX} ${var}) |
|
|
|
if("${var}" STREQUAL NONE) |
|
set(${var}) |
|
endif() |
|
|
|
set(${var} ${${var}} 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 path will be returned in the <value> parameter. |
|
# |
|
# <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_has_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() |
|
|
|
######################################################## |
|
# 5. Zephyr linker function |
|
######################################################## |
|
# 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_memory(NAME <name> PATH <path> FLAGS <flags>) |
|
# zephyr_linker_dts_memory(NAME <name> NODELABEL <nodelabel> FLAGS <flags>) |
|
# zephyr_linker_dts_memory(NAME <name> 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. |
|
# |
|
# NAME <name> : Name of the memory region, for example FLASH. |
|
# 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;NAME;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_exists(exists PATH ${DTS_MEMORY_PATH}) |
|
if(NOT ${exists}) |
|
return() |
|
endif() |
|
|
|
dt_reg_addr(addr PATH ${DTS_MEMORY_PATH}) |
|
dt_reg_size(size PATH ${DTS_MEMORY_PATH}) |
|
|
|
zephyr_linker_memory( |
|
NAME ${DTS_MEMORY_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 Z_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. |
|
# 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") |
|
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() |
|
|
|
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 "._${SECTION_NAME}.static.*" |
|
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, adding the input section `foo` to the output section bar, with KEEP |
|
# attribute, then 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 no 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, then Zephyr CMake linker will create |
|
# sections so order can be guaranteed. All unprioritized |
|
# sections will internally be given a CMake process order |
|
# priority counting from 100, so first unprioritized section |
|
# is handles internal pri0 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()
|
|
|