957 lines
39 KiB
CMake
957 lines
39 KiB
CMake
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
include_guard(GLOBAL)
|
|
|
|
#[[
|
|
check_expected_tool_version()
|
|
|
|
Function to verify if the tool is using the expected version and issue a
|
|
warning if it is not.
|
|
#]]
|
|
function(check_expected_tool_version tool_name tool_path)
|
|
set(tool_version_warning "Check Getting Started documentation or proceed at own risk.\n")
|
|
set(tool_version_error "Check Getting Started documentation if the error continues.\n"
|
|
"You can override this error and proceed with build by defining the IDF_MAINTAINER environment variable.\n")
|
|
set(fixing_hint "Please try to run 'idf.py fullclean' to solve it.\n")
|
|
|
|
idf_build_get_property(python PYTHON)
|
|
idf_build_get_property(idf_path IDF_PATH)
|
|
|
|
set(ENV{IDF_TOOLS_VERSION_HELPER} "1")
|
|
# Use idf_tools.py to check if tool version is supported
|
|
execute_process(
|
|
COMMAND ${python} "${idf_path}/tools/idf_tools.py"
|
|
"check-tool-supported" "--tool-name" "${tool_name}"
|
|
"--exec-path" "${tool_path}"
|
|
OUTPUT_VARIABLE is_version_supported
|
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
ERROR_QUIET)
|
|
|
|
if(is_version_supported STREQUAL "False")
|
|
# Version is not supported. Need to get supported versions list to print them to user
|
|
execute_process(
|
|
COMMAND ${python} "${idf_path}/tools/idf_tools.py"
|
|
"get-tool-supported-versions" "--tool-name" "${tool_name}"
|
|
OUTPUT_VARIABLE tool_supported_versions
|
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
ERROR_QUIET)
|
|
# IDF maintainers can build projects with not supported versions with just a warning
|
|
if($ENV{IDF_MAINTAINER})
|
|
set(message_mode "WARNING")
|
|
else()
|
|
set(message_mode "FATAL_ERROR")
|
|
endif()
|
|
|
|
idf_msg(${message_mode} "\n"
|
|
"Tool doesn't match supported version from list "
|
|
"${tool_supported_versions}: ${tool_path}\n"
|
|
${fixing_hint})
|
|
elseif(NOT is_version_supported STREQUAL "True")
|
|
idf_warn("Can not get version for tool: ${tool_path}\n" ${tool_version_warning})
|
|
endif()
|
|
unset(ENV{IDF_TOOLS_VERSION_HELPER})
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_component_sources([SRCS <src>...]
|
|
[SRC_DIRS <dir>...]
|
|
[EXCLUDE_SRCS <exclude>...]
|
|
OUTPUT <var>)
|
|
|
|
*SRCS[in,opt]*
|
|
|
|
Optional list of sources.
|
|
|
|
*SRC_DIRS[in,opt]*
|
|
|
|
Option list of source directories.
|
|
|
|
*EXCLUDE_SRCS[in,opt]*
|
|
|
|
Optional list of sources to exclude.
|
|
|
|
*OUTPUT[out]*
|
|
|
|
Output variable to store the list of component sources.
|
|
|
|
This helper function gathers component sources from the arguments specified
|
|
in either ``SRCS`` or ``SRC_DIRS``. If both are provided, the ``SRC_DIRS``
|
|
option is disregarded. The sources are collected as absolute paths relative
|
|
to the component's directory. If ``EXCLUDE_SRCS`` is specified, the source
|
|
files listed in it are excluded. The final list of component sources is
|
|
returned in the ``OUTPUT`` variable.
|
|
|
|
The ``COMPONENT_NAME`` and ``COMPONENT_DIR`` variables are provided by the
|
|
build system when ``idf_component_register`` is called.
|
|
#]]
|
|
function(__get_component_sources)
|
|
set(options)
|
|
set(one_value OUTPUT)
|
|
set(multi_value SRCS SRC_DIRS EXCLUDE_SRCS)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_OUTPUT)
|
|
idf_die("OUTPUT option is required")
|
|
endif()
|
|
|
|
if(DEFINED ARG_SRCS AND DEFINED ARG_SRC_DIRS)
|
|
idf_warn("SRCS and SRC_DIRS are both specified for '${COMPONENT_NAME}' "
|
|
"in '${COMPONENT_DIR}'. Ignoring SRC_DIRS.")
|
|
unset(ARG_SRC_DIRS)
|
|
endif()
|
|
|
|
set(sources)
|
|
|
|
if(ARG_SRCS)
|
|
__get_absolute_paths(PATHS "${ARG_SRCS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT sources)
|
|
elseif(ARG_SRC_DIRS)
|
|
__get_absolute_paths(PATHS "${ARG_SRC_DIRS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT dirs)
|
|
foreach(dir IN LISTS dirs)
|
|
if(NOT IS_DIRECTORY "${dir}")
|
|
idf_die("SRC_DIRS entry '${dir}' does not exist for component "
|
|
"'${COMPONENT_NAME}' in '${COMPONENT_DIR}'.")
|
|
endif()
|
|
|
|
file(GLOB dir_sources "${dir}/*.c" "${dir}/*.cpp" "${dir}/*.S")
|
|
list(SORT dir_sources)
|
|
|
|
if(NOT dir_sources)
|
|
idf_warn("No source files found for SRC_DIRS entry '${dir}' for "
|
|
"'${COMPONENT_NAME}' in '${COMPONENT_DIR}'.")
|
|
continue()
|
|
endif()
|
|
|
|
list(APPEND sources "${dir_sources}")
|
|
endforeach()
|
|
endif()
|
|
|
|
if(ARG_EXCLUDE_SRCS)
|
|
__get_absolute_paths(PATHS "${ARG_EXCLUDE_SRCS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT exclude_srcs)
|
|
foreach(src IN LISTS exclude_srcs)
|
|
list(REMOVE_ITEM sources "${src}")
|
|
endforeach()
|
|
endif()
|
|
|
|
list(REMOVE_DUPLICATES sources)
|
|
set(${ARG_OUTPUT} "${sources}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: target_linker_script
|
|
|
|
.. code-block:: cmake
|
|
|
|
target_linker_script(<target> <deptype> <scriptfile>...
|
|
[PROCESS <output>])
|
|
|
|
*target[in]*
|
|
|
|
The component target to which linker script files should be added.
|
|
|
|
*deptype[in]*
|
|
|
|
This option is obsolete and maintained solely for backward
|
|
compatibility.
|
|
|
|
*scriptfile[in]*
|
|
|
|
Specifies the linker script file ``scriptfile`` to be added to the
|
|
link. Multiple files can be specified.
|
|
|
|
*PROCESS[in,opt]*
|
|
|
|
Specifies the ``output`` linker script, which is generated from the
|
|
``linkerscript`` template. The ``linkerscript`` is processed with ldgen
|
|
to produce the ``output``.
|
|
|
|
This function adds one or more linker scripts to the specified component
|
|
target, incorporating the linker script into the linking process.
|
|
|
|
If the ``PROCESS`` option is specified, the last ``scriptfile`` listed is
|
|
processed using the ldgen command, and the generated ``output`` file is
|
|
used as the linker script during the linking process. This implies that
|
|
with the ``PROCESS`` option, it is logical to provide only a single
|
|
``scriptfile`` as a template.
|
|
#]]
|
|
function(target_linker_script target deptype scriptfiles)
|
|
# The linker script files, templates, and their output filenames are stored
|
|
# only as component properties. The script files are generated and added to
|
|
# the library link interface in the idf_build_library function.
|
|
set(options)
|
|
set(one_value PROCESS)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
foreach(scriptfile ${scriptfiles})
|
|
get_filename_component(scriptfile "${scriptfile}" ABSOLUTE)
|
|
idf_msg("Adding linker script ${scriptfile}")
|
|
if(ARG_PROCESS)
|
|
get_filename_component(output "${ARG_PROCESS}" ABSOLUTE)
|
|
idf_component_set_property("${target}" LINKER_SCRIPTS_TEMPLATE "${scriptfile}" APPEND)
|
|
idf_component_set_property("${target}" LINKER_SCRIPTS_GENERATED "${output}" APPEND)
|
|
else()
|
|
idf_component_set_property("${target}" LINKER_SCRIPTS ${scriptfile} APPEND)
|
|
endif()
|
|
endforeach()
|
|
endfunction()
|
|
|
|
#[[
|
|
__idf_component_process_optional_requires()
|
|
|
|
Called by idf_build_library() before LIBRARY_COMPONENTS_LINKED is computed.
|
|
For each pending (caller, type, req) entry recorded by
|
|
idf_component_optional_requires, links req's interface target to the
|
|
caller's real target if both are present in this library.
|
|
#]]
|
|
function(__idf_component_process_optional_requires)
|
|
# Nothing to do if no component has called idf_component_optional_requires.
|
|
idf_build_get_property(callers __DEFERRED_OPTIONAL_CALLERS)
|
|
if(NOT callers)
|
|
return()
|
|
endif()
|
|
|
|
# DEFERRED mode + multiple libraries is disallowed
|
|
idf_build_get_property(libraries_list LIBRARY_INTERFACES)
|
|
list(LENGTH libraries_list lib_count)
|
|
if(lib_count GREATER 1)
|
|
idf_die("DEFERRED optional requires mode cannot be used when building "
|
|
"multiple libraries (detected ${lib_count} libraries). "
|
|
"Set IDF_COMPONENT_OPTIONAL_REQUIRES_MODE to IMMEDIATE for multi-library projects.")
|
|
endif()
|
|
|
|
# Fetch the components included in the project (single library only here,
|
|
# so this equals the library's component set).
|
|
idf_build_get_property(components_included COMPONENTS_INCLUDED)
|
|
set(library_component_interfaces "")
|
|
foreach(comp_name IN LISTS components_included)
|
|
__get_component_interface(COMPONENT "${comp_name}" OUTPUT dep_interface)
|
|
if(dep_interface AND NOT "${dep_interface}" IN_LIST library_component_interfaces)
|
|
list(APPEND library_component_interfaces "${dep_interface}")
|
|
endif()
|
|
endforeach()
|
|
|
|
# For every caller that recorded optional requirements, check whether it is
|
|
# part of this library and, if so, apply any pending pairs whose
|
|
# requirement is also in the library.
|
|
foreach(caller_target IN LISTS callers)
|
|
# Resolve the caller target to its component interface.
|
|
__get_component_interface(COMPONENT "${caller_target}" OUTPUT caller_interface)
|
|
if(NOT caller_interface)
|
|
continue()
|
|
endif()
|
|
|
|
# Skip if this caller is not linked into the current library.
|
|
if(NOT "${caller_interface}" IN_LIST library_component_interfaces)
|
|
continue()
|
|
endif()
|
|
|
|
# Fetch this caller's pending pairs and the pairs already processed in
|
|
# earlier idf_build_library() calls.
|
|
idf_build_get_property(pairs "__OPT_REQ_${caller_target}")
|
|
idf_build_get_property(done "__OPT_REQ_DONE_${caller_target}")
|
|
|
|
foreach(pair IN LISTS pairs)
|
|
# Skip pairs already processed for a previous library. The
|
|
# target_link_libraries call and property updates are permanent
|
|
# global mutations; repeating them would be redundant.
|
|
if("${pair}" IN_LIST done)
|
|
continue()
|
|
endif()
|
|
|
|
# Decode the "type::::req_interface" entry.
|
|
string(REPLACE "::::" ";" split "${pair}")
|
|
list(GET split 0 link_type)
|
|
list(GET split 1 req_interface)
|
|
|
|
# Skip if the optional requirement is not part of this library.
|
|
if(NOT "${req_interface}" IN_LIST library_component_interfaces)
|
|
continue()
|
|
endif()
|
|
|
|
# Link the caller's real target to the requirement's interface target.
|
|
target_link_libraries("${caller_target}" "${link_type}" "${req_interface}")
|
|
|
|
# Update the caller's REQUIRES or PRIV_REQUIRES property.
|
|
idf_component_get_property(req_name "${req_interface}" COMPONENT_NAME)
|
|
if("${link_type}" STREQUAL "PRIVATE")
|
|
idf_component_get_property(existing "${caller_interface}" PRIV_REQUIRES)
|
|
if(NOT "${req_name}" IN_LIST existing)
|
|
idf_component_set_property("${caller_interface}" PRIV_REQUIRES
|
|
"${req_name}" APPEND)
|
|
endif()
|
|
elseif("${link_type}" STREQUAL "PUBLIC" OR "${link_type}" STREQUAL "INTERFACE")
|
|
idf_component_get_property(existing "${caller_interface}" REQUIRES)
|
|
if(NOT "${req_name}" IN_LIST existing)
|
|
idf_component_set_property("${caller_interface}" REQUIRES
|
|
"${req_name}" APPEND)
|
|
endif()
|
|
endif()
|
|
|
|
# Mark this pair as done so it is not processed again for subsequent
|
|
# libraries.
|
|
idf_build_set_property("__OPT_REQ_DONE_${caller_target}" "${pair}" APPEND)
|
|
endforeach()
|
|
endforeach()
|
|
endfunction()
|
|
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_component_optional_requires
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_component_optional_requires(<type> <component>...)
|
|
|
|
*type[in]*
|
|
|
|
Type of dependency: PRIVATE, PUBLIC or INTERFACE
|
|
|
|
*component[in]*
|
|
|
|
The component name that should be added as a dependency to the
|
|
evaluated component. It may be provided multiple times.
|
|
|
|
Add a dependency on a specific component only if the component is
|
|
recognized by the build system. The behavior is controlled by the
|
|
``IDF_COMPONENT_OPTIONAL_REQUIRES_MODE`` build property:
|
|
|
|
* **IMMEDIATE** (default): Include the component and link it to the
|
|
caller if it is discovered. Safe for multi-library projects but may
|
|
pull in more components than strictly needed.
|
|
* **DEFERRED**: Do not include or link immediately; record the request
|
|
and resolve it in :cmakev2:ref:`idf_build_library` so that the
|
|
component is linked only when it is part of the library's dependency
|
|
graph. Matches v1 semantics and reduces unnecessary components, but
|
|
must not be used when building multiple libraries (see docs).
|
|
|
|
.. note::
|
|
This function should be avoided in cmakev2, where
|
|
dependencies should be added based on configuration options. This is purely
|
|
for backward compatibility with cmakev1.
|
|
|
|
.. warning::
|
|
In multi-library projects with **DEFERRED** mode, optional requires
|
|
resolved when processing a later library apply globally to shared
|
|
component targets. Earlier libraries then link that optional component
|
|
too, but their per-library metadata (e.g. linker fragments) was
|
|
already computed and is not updated. DEFERRED mode is therefore
|
|
disallowed when more than one library is built.
|
|
#]]
|
|
function(idf_component_optional_requires type)
|
|
set(optional_reqs ${ARGN})
|
|
|
|
idf_build_get_property(mode IDF_COMPONENT_OPTIONAL_REQUIRES_MODE)
|
|
if(NOT mode)
|
|
set(mode IMMEDIATE)
|
|
endif()
|
|
|
|
if("${mode}" STREQUAL "DEFERRED")
|
|
# DEFERRED mode: record for resolution in idf_build_library.
|
|
# The component is linked only if it is part of the library's dependency graph.
|
|
foreach(req ${optional_reqs})
|
|
__get_component_interface(COMPONENT "${req}" OUTPUT req_interface)
|
|
if("${req_interface}" STREQUAL "NOTFOUND")
|
|
continue()
|
|
endif()
|
|
|
|
idf_build_get_property(callers __DEFERRED_OPTIONAL_CALLERS)
|
|
if(NOT "${COMPONENT_TARGET}" IN_LIST callers)
|
|
idf_build_set_property(__DEFERRED_OPTIONAL_CALLERS "${COMPONENT_TARGET}" APPEND)
|
|
endif()
|
|
|
|
# Store the optional requirement in the __OPT_REQ_<component_target> property.
|
|
idf_build_set_property("__OPT_REQ_${COMPONENT_TARGET}"
|
|
"${type}::::${req_interface}" APPEND)
|
|
endforeach()
|
|
else()
|
|
# IMMEDIATE mode: include and link discovered components unconditionally.
|
|
foreach(req ${optional_reqs})
|
|
__get_component_interface(COMPONENT "${req}" OUTPUT req_interface)
|
|
if("${req_interface}" STREQUAL "NOTFOUND")
|
|
continue()
|
|
endif()
|
|
idf_component_include("${req}")
|
|
|
|
if("${type}" STREQUAL "PRIVATE")
|
|
set(req_type PRIV_REQUIRES)
|
|
elseif("${type}" STREQUAL "PUBLIC")
|
|
set(req_type REQUIRES)
|
|
else()
|
|
set(req_type "")
|
|
endif()
|
|
|
|
if(req_type)
|
|
idf_component_get_property(req_name "${req_interface}" COMPONENT_NAME)
|
|
idf_component_get_property(target_reqs "${COMPONENT_NAME}" ${req_type})
|
|
if(NOT "${req_name}" IN_LIST target_reqs)
|
|
idf_component_set_property("${COMPONENT_NAME}" ${req_type} "${req_name}" APPEND)
|
|
target_link_libraries(${COMPONENT_TARGET} ${type} ${req_interface})
|
|
endif()
|
|
else()
|
|
target_link_libraries(${COMPONENT_TARGET} ${type} ${req_interface})
|
|
endif()
|
|
endforeach()
|
|
endif()
|
|
endfunction()
|
|
|
|
#[[
|
|
__init_common_components()
|
|
|
|
Identify the commonly required components based on the target and
|
|
architecture, and store them in the __COMPONENT_REQUIRES_COMMON build
|
|
property. Their interfaces are stored in the __COMMON_COMPONENT_INTERFACES
|
|
build property. The commonly required component interfaces are
|
|
automatically linked to each cmakev1 component added through the
|
|
idf_component_register function.
|
|
|
|
This function is called from the idf_component_register function and is
|
|
evaluated only once per project, as ensured by the
|
|
__COMMON_COMPONENTS_INITIALIZED build property. The cmakev2 components are
|
|
expected to properly specify all their dependencies, rather than relying on
|
|
common components to be automatically linked to them. Therefore, the common
|
|
components are relevant only within the context of cmakev1 components.
|
|
#]]
|
|
function(__init_common_components)
|
|
idf_build_get_property(common_components_initialized __COMMON_COMPONENTS_INITIALIZED)
|
|
if(common_components_initialized)
|
|
return()
|
|
endif()
|
|
|
|
idf_build_get_property(idf_target IDF_TARGET)
|
|
idf_build_get_property(idf_target_arch IDF_TARGET_ARCH)
|
|
idf_build_get_property(explicit_requires_common __COMPONENT_REQUIRES_COMMON)
|
|
|
|
# Define common components that are included as dependencies for each
|
|
# component.
|
|
if(explicit_requires_common)
|
|
set(requires_common "${explicit_requires_common}")
|
|
# Auto-append the target's arch component when the app overrides
|
|
# common-requires without it. Lets G0-style apps (which set the property
|
|
# before project() to a restricted list) avoid hand-rolling a
|
|
# target -> arch map pre-project, since IDF_TARGET_ARCH isn't known
|
|
# until after sdkconfig is included. Empty on linux -> no append.
|
|
if(idf_target_arch AND NOT idf_target_arch IN_LIST requires_common)
|
|
list(APPEND requires_common "${idf_target_arch}")
|
|
endif()
|
|
|
|
elseif("${idf_target}" STREQUAL "linux")
|
|
set(requires_common freertos esp_hw_support heap log soc hal esp_rom esp_common esp_system linux
|
|
esp_stdio)
|
|
else()
|
|
set(requires_common cxx esp_libc freertos esp_hw_support heap log soc hal esp_rom esp_common
|
|
esp_system esp_stdio ${idf_target_arch})
|
|
endif()
|
|
idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}")
|
|
|
|
# Set the common component interfaces first, before including them, so the
|
|
# idf_component_include function can see the complete list of common
|
|
# component interfaces.
|
|
set(common_component_interfaces "")
|
|
foreach(component_name IN LISTS requires_common)
|
|
idf_component_get_property(component_interface "${component_name}" COMPONENT_INTERFACE)
|
|
list(APPEND common_component_interfaces "${component_interface}")
|
|
endforeach()
|
|
idf_build_set_property(__COMMON_COMPONENT_INTERFACES "${common_component_interfaces}")
|
|
|
|
foreach(component_name IN LISTS requires_common)
|
|
idf_component_include("${component_name}")
|
|
endforeach()
|
|
|
|
idf_build_set_property(__COMMON_COMPONENTS_INITIALIZED YES)
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_component_mock
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_component_mock([INCLUDE_DIRS <dir>...]
|
|
[MOCK_HEADER_FILES <file>...]
|
|
[REQUIRES <component>...]
|
|
[MOCK_SUBDIR <subdir>])
|
|
|
|
*INCLUDE_DIRS[in,opt]*
|
|
|
|
Include directories for the header files provided in MOCK_HEADER_FILES.
|
|
|
|
*MOCK_HEADER_FILES[in,opt]*
|
|
|
|
Header files from which CMock generates mock implementations.
|
|
|
|
*REQUIRES[in,opt]*
|
|
|
|
Additional components required by the mock component.
|
|
|
|
*MOCK_SUBDIR[in,opt]*
|
|
|
|
Subdirectory under the mocks output where generated files are placed.
|
|
|
|
Create a mock component using CMock and register it with the build system.
|
|
This is a compatibility shim matching the v1 ``idf_component_mock``
|
|
function in ``tools/cmake/component.cmake``. It generates Mock*.c and
|
|
Mock*.h files from the given headers using Ruby + CMock, then delegates to
|
|
``idf_component_register`` so the mock participates in normal dependency
|
|
resolution.
|
|
#]]
|
|
function(idf_component_mock)
|
|
set(options)
|
|
set(single_value MOCK_SUBDIR)
|
|
set(multi_value MOCK_HEADER_FILES INCLUDE_DIRS REQUIRES)
|
|
cmake_parse_arguments(_ "${options}" "${single_value}" "${multi_value}" ${ARGN})
|
|
|
|
list(APPEND __REQUIRES "cmock")
|
|
|
|
set(MOCK_GENERATED_HEADERS "")
|
|
set(MOCK_GENERATED_SRCS "")
|
|
set(IDF_PATH $ENV{IDF_PATH})
|
|
set(CMOCK_DIR "${IDF_PATH}/components/cmock/CMock")
|
|
set(MOCK_GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
|
list(APPEND __INCLUDE_DIRS "${MOCK_GEN_DIR}/mocks")
|
|
|
|
foreach(header_file ${__MOCK_HEADER_FILES})
|
|
get_filename_component(file_without_dir ${header_file} NAME_WE)
|
|
if("${__MOCK_SUBDIR}" STREQUAL "")
|
|
list(APPEND MOCK_GENERATED_HEADERS "${MOCK_GEN_DIR}/mocks/Mock${file_without_dir}.h")
|
|
list(APPEND MOCK_GENERATED_SRCS "${MOCK_GEN_DIR}/mocks/Mock${file_without_dir}.c")
|
|
else()
|
|
list(APPEND MOCK_GENERATED_HEADERS "${MOCK_GEN_DIR}/mocks/${__MOCK_SUBDIR}/Mock${file_without_dir}.h")
|
|
list(APPEND MOCK_GENERATED_SRCS "${MOCK_GEN_DIR}/mocks/${__MOCK_SUBDIR}/Mock${file_without_dir}.c")
|
|
endif()
|
|
endforeach()
|
|
|
|
file(MAKE_DIRECTORY "${MOCK_GEN_DIR}/mocks")
|
|
|
|
idf_component_register(SRCS "${MOCK_GENERATED_SRCS}"
|
|
INCLUDE_DIRS ${__INCLUDE_DIRS}
|
|
REQUIRES ${__REQUIRES})
|
|
|
|
set(COMPONENT_LIB ${COMPONENT_LIB} PARENT_SCOPE)
|
|
add_custom_command(
|
|
OUTPUT ruby_found SYMBOLIC
|
|
COMMAND "ruby" "-v"
|
|
COMMENT "Try to find ruby. If this fails, you need to install ruby"
|
|
)
|
|
|
|
add_custom_command(
|
|
OUTPUT ${MOCK_GENERATED_SRCS} ${MOCK_GENERATED_HEADERS}
|
|
DEPENDS ruby_found
|
|
COMMAND ${CMAKE_COMMAND} -E env "UNITY_DIR=${IDF_PATH}/components/unity/unity"
|
|
ruby
|
|
${CMOCK_DIR}/lib/cmock.rb
|
|
-o${CMAKE_CURRENT_SOURCE_DIR}/mock/mock_config.yaml
|
|
${__MOCK_HEADER_FILES}
|
|
)
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_component_register
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_component_register([SRCS <file>..]
|
|
[SRC_DIRS <dir>...]
|
|
[EXCLUDE_SRCS <file>...]
|
|
[INCLUDE_DIRS <dir>...]
|
|
[PRIV_INCLUDE_DIRS <dir>...]
|
|
[LDFRAGMENTS <file>...]
|
|
[REQUIRES <component>...]
|
|
[PRIV_REQUIRES <component>...]
|
|
[REQUIRED_IDF_TARGETS <target>...]
|
|
[EMBED_FILES <file>...]
|
|
[EMBED_TXTFILES <file>...]
|
|
[WHOLE_ARCHIVE])
|
|
|
|
*SRCS[in,opt]*
|
|
|
|
List of source files for the component.
|
|
|
|
*SRC_DIRS[in,opt]*
|
|
|
|
List of source directories to search for source files (.c, .cpp, .S);
|
|
ignored when SRCS is specified.
|
|
|
|
*EXCLUDE_SRCS[in,opt]*
|
|
|
|
List of source files to exclude from the designated source directories.
|
|
|
|
*INCLUDE_DIRS[in,opt]*
|
|
|
|
List of public include directories for the created component library.
|
|
|
|
*PRIV_INCLUDE_DIRS[in,opt]*
|
|
|
|
List of private include directories for the newly created component
|
|
library.
|
|
|
|
*LDFRAGMENTS[in,opt]*
|
|
|
|
List of linker script fragments for the component.
|
|
|
|
*REQUIRES[in,opt]*
|
|
|
|
List of publicly required components based on usage requirements.
|
|
|
|
*PRIV_REQUIRES[in,opt]*
|
|
|
|
List of privately required components based on usage requirements.
|
|
|
|
*REQUIRED_IDF_TARGETS[in,opt]*
|
|
|
|
List of IDF build targets supported exclusively by the component.
|
|
|
|
*EMBED_FILES[in,opt]*
|
|
|
|
List of binary files to embed with the component.
|
|
|
|
*EMBED_TXTFILES[in,opt]*
|
|
|
|
List of text files to embed with the component.
|
|
|
|
*WHOLE_ARCHIVE[in,opt]*
|
|
|
|
Link the component as --whole-archive.
|
|
|
|
Register a new component with the build system using the provided options.
|
|
This function also automatically links all commonly required and managed
|
|
components to the component's target.
|
|
#]]
|
|
function(idf_component_register)
|
|
set(options WHOLE_ARCHIVE)
|
|
set(one_value KCONFIG KCONFIG_PROJBUILD)
|
|
set(multi_value SRCS SRC_DIRS EXCLUDE_SRCS
|
|
INCLUDE_DIRS PRIV_INCLUDE_DIRS LDFRAGMENTS REQUIRES
|
|
PRIV_REQUIRES REQUIRED_IDF_TARGETS EMBED_FILES EMBED_TXTFILES)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
# Some managed components pass their REQUIRES / PRIV_REQUIRES as a single
|
|
# quoted space-separated string (e.g. PRIV_REQUIRES "log esp_eth").
|
|
# cmake_parse_arguments stores that as one list element. Normalise the
|
|
# lists so that each component name is a separate element.
|
|
separate_arguments(ARG_REQUIRES)
|
|
separate_arguments(ARG_PRIV_REQUIRES)
|
|
|
|
# Initialize and include commonly required components.
|
|
__init_common_components()
|
|
|
|
if(ARG_REQUIRED_IDF_TARGETS)
|
|
idf_build_get_property(idf_target IDF_TARGET)
|
|
if(NOT idf_target IN_LIST ARG_REQUIRED_IDF_TARGETS)
|
|
idf_die("Component ${COMPONENT_NAME} only supports targets: ${ARG_REQUIRED_IDF_TARGETS}")
|
|
endif()
|
|
endif()
|
|
|
|
if(ARG_KCONFIG)
|
|
get_filename_component(filename "${ARG_KCONFIG}" NAME)
|
|
string(TOLOWER "${filename}" filename)
|
|
if(NOT "${filename}" STREQUAL "kconfig")
|
|
idf_die("The component '${COMPONENT_NAME}' in the '${COMPONENT_DIR}' directory "
|
|
"is specifying '${ARG_KCONFIG}' as the 'KCONFIG' option, which is not "
|
|
"the default 'Kconfig' filename.")
|
|
endif()
|
|
endif()
|
|
|
|
if(ARG_KCONFIG_PROJBUILD)
|
|
get_filename_component(filename "${ARG_KCONFIG_PROJBUILD}" NAME)
|
|
string(TOLOWER "${filename}" filename)
|
|
if(NOT "${filename}" STREQUAL "kconfig.projbuild")
|
|
idf_die("The component '${COMPONENT_NAME}' in the '${COMPONENT_DIR}' directory "
|
|
"is specifying '${ARG_KCONFIG_PROJBUILD}' as the 'KCONFIG_PROJBUILD' option, "
|
|
"which is not the default 'Kconfig.projbuild' filename.")
|
|
endif()
|
|
endif()
|
|
|
|
__get_component_sources(SRCS "${ARG_SRCS}"
|
|
SRC_DIRS "${ARG_SRC_DIRS}"
|
|
EXCLUDE_SRCS "${ARG_EXCLUDE_SRCS}"
|
|
OUTPUT sources)
|
|
|
|
idf_build_get_property(include_directories INCLUDE_DIRECTORIES GENERATOR_EXPRESSION)
|
|
include_directories("${include_directories}")
|
|
|
|
idf_build_get_property(compile_definitions COMPILE_DEFINITIONS GENERATOR_EXPRESSION)
|
|
add_compile_definitions("${compile_definitions}")
|
|
|
|
idf_build_get_compile_options(compile_options)
|
|
add_compile_options("${compile_options}")
|
|
|
|
idf_build_get_property(common_component_interfaces __COMMON_COMPONENT_INTERFACES)
|
|
idf_component_get_property(component_interface "${COMPONENT_NAME}" COMPONENT_INTERFACE)
|
|
list(REMOVE_ITEM common_component_interfaces ${component_interface})
|
|
link_libraries(${common_component_interfaces})
|
|
|
|
__get_absolute_paths(PATHS "${ARG_INCLUDE_DIRS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT include_dirs)
|
|
__get_absolute_paths(PATHS "${ARG_PRIV_INCLUDE_DIRS}" BASE_DIR "${COMPONENT_DIR}" OUTPUT priv_include_dirs)
|
|
foreach(dir IN LISTS include_dirs priv_include_dirs)
|
|
if(NOT IS_DIRECTORY ${dir})
|
|
idf_die("The component '${COMPONENT_NAME}' in the '${COMPONENT_DIR}' directory "
|
|
"is specifying '${dir}' as its include directory, but the directory does "
|
|
"not exists.")
|
|
endif()
|
|
endforeach()
|
|
|
|
if(sources OR ARG_EMBED_FILES OR ARG_EMBED_TXTFILES)
|
|
add_library("${COMPONENT_TARGET}" STATIC ${sources})
|
|
|
|
foreach(include_dir IN LISTS include_dirs)
|
|
target_include_directories("${COMPONENT_TARGET}" PUBLIC "${include_dir}")
|
|
endforeach()
|
|
|
|
foreach(include_dir IN LISTS priv_include_dirs)
|
|
target_include_directories("${COMPONENT_TARGET}" PRIVATE "${include_dir}")
|
|
endforeach()
|
|
|
|
set_target_properties(${COMPONENT_TARGET} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME} LINKER_LANGUAGE C)
|
|
|
|
set(component_type LIBRARY)
|
|
|
|
else()
|
|
add_library("${COMPONENT_TARGET}" INTERFACE)
|
|
|
|
foreach(include_dir IN LISTS include_dirs)
|
|
target_include_directories("${COMPONENT_TARGET}" INTERFACE "${include_dir}")
|
|
endforeach()
|
|
|
|
set(component_type CONFIG_ONLY)
|
|
endif()
|
|
|
|
foreach(req IN LISTS ARG_REQUIRES)
|
|
idf_component_include("${req}")
|
|
idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE)
|
|
if(${component_type} STREQUAL LIBRARY)
|
|
target_link_libraries("${COMPONENT_TARGET}" PUBLIC "${req_interface}")
|
|
else()
|
|
target_link_libraries("${COMPONENT_TARGET}" INTERFACE "${req_interface}")
|
|
endif()
|
|
endforeach()
|
|
|
|
foreach(req IN LISTS ARG_PRIV_REQUIRES)
|
|
idf_component_include("${req}")
|
|
idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE)
|
|
if(${component_type} STREQUAL CONFIG_ONLY)
|
|
continue()
|
|
endif()
|
|
target_link_libraries("${COMPONENT_TARGET}" PRIVATE "${req_interface}")
|
|
endforeach()
|
|
|
|
# Special treatment for 'main' component is only applied when the project
|
|
# was entered through the Build system v1 compatibility shim. Native
|
|
# Build system v2 projects with no REQUIRES on main should remain the
|
|
# author-declared dependency set rooted at main; forcing every discovered
|
|
# component into main's link graph in that case contradicts the Build
|
|
# system v2 contract that the dependency graph is what the author writes.
|
|
idf_build_get_property(v1_compat_shim __V1_COMPAT_SHIM)
|
|
if(v1_compat_shim AND COMPONENT_NAME STREQUAL "main"
|
|
AND NOT ARG_REQUIRES AND NOT ARG_PRIV_REQUIRES)
|
|
idf_component_get_property(component_source "${COMPONENT_NAME}" COMPONENT_SOURCE)
|
|
if(component_source STREQUAL "project_components")
|
|
idf_build_get_property(shim_components __SHIM_COMPONENTS)
|
|
if(shim_components)
|
|
# The project explicitly restricted COMPONENTS=<list>. Honor
|
|
# the restriction literally — do not pull every discovered
|
|
# component into main's link graph, even when the user's list
|
|
# collapses to just "main". Apps that need additional
|
|
# components must enumerate them in COMPONENTS or main's
|
|
# REQUIRES.
|
|
set(all_components ${shim_components})
|
|
else()
|
|
idf_build_get_property(all_components COMPONENTS_DISCOVERED)
|
|
endif()
|
|
list(REMOVE_ITEM all_components "main")
|
|
# Common components are already linked via link_libraries()
|
|
idf_build_get_property(common_components __COMPONENT_REQUIRES_COMMON)
|
|
if(common_components)
|
|
list(REMOVE_ITEM all_components ${common_components})
|
|
endif()
|
|
foreach(req IN LISTS all_components)
|
|
idf_component_include("${req}")
|
|
idf_component_get_property(req_interface "${req}" COMPONENT_INTERFACE)
|
|
if(${component_type} STREQUAL LIBRARY)
|
|
target_link_libraries("${COMPONENT_TARGET}" PUBLIC "${req_interface}")
|
|
else()
|
|
target_link_libraries("${COMPONENT_TARGET}" INTERFACE "${req_interface}")
|
|
endif()
|
|
endforeach()
|
|
endif()
|
|
endif()
|
|
|
|
# Signal to idf_component_include that this component was included via the
|
|
# backward compatible idf_component_register function.
|
|
idf_component_set_property("${COMPONENT_NAME}" COMPONENT_FORMAT CMAKEV1)
|
|
|
|
# The handling of WHOLE_ARCHIVE linkage is managed within the
|
|
# idf_component_include function.
|
|
idf_component_set_property("${COMPONENT_NAME}" WHOLE_ARCHIVE "${ARG_WHOLE_ARCHIVE}")
|
|
|
|
idf_component_set_property("${COMPONENT_NAME}" SRCS "${sources}")
|
|
idf_component_set_property("${COMPONENT_NAME}" INCLUDE_DIRS "${ARG_INCLUDE_DIRS}")
|
|
idf_component_set_property("${COMPONENT_NAME}" PRIV_INCLUDE_DIRS "${ARG_PRIV_INCLUDE_DIRS}")
|
|
|
|
# The addition of ldgen fragment files is managed by the
|
|
# idf_component_include function.
|
|
idf_component_set_property("${COMPONENT_NAME}" LDFRAGMENTS "${ARG_LDFRAGMENTS}")
|
|
|
|
# Embedded files are managed in the idf_component_include function.
|
|
idf_component_set_property("${COMPONENT_NAME}" EMBED_FILES "${ARG_EMBED_FILES}")
|
|
idf_component_set_property("${COMPONENT_NAME}" EMBED_TXTFILES "${ARG_EMBED_TXTFILES}")
|
|
# The component manager's dependency injection runs before this component's
|
|
# CMakeLists.txt is evaluated (see idf_component_include() in
|
|
# component.cmake), and writes the manifest-derived names into both the
|
|
# MANAGED_* properties and the regular REQUIRES / PRIV_REQUIRES properties.
|
|
# The latter must not be overwritten by the component's own register call,
|
|
# else the manifest-derived transitive deps disappear from the build. Union
|
|
# the two so the persisted property is what the component author wrote
|
|
# PLUS what the manifest contributed.
|
|
idf_component_get_property(__existing_requires "${COMPONENT_NAME}" REQUIRES)
|
|
idf_component_get_property(__existing_priv_requires "${COMPONENT_NAME}" PRIV_REQUIRES)
|
|
set(__merged_requires "${ARG_REQUIRES}")
|
|
set(__merged_priv_requires "${ARG_PRIV_REQUIRES}")
|
|
foreach(__r IN LISTS __existing_requires)
|
|
if(__r AND NOT __r IN_LIST __merged_requires)
|
|
list(APPEND __merged_requires "${__r}")
|
|
endif()
|
|
endforeach()
|
|
foreach(__r IN LISTS __existing_priv_requires)
|
|
if(__r AND NOT __r IN_LIST __merged_priv_requires)
|
|
list(APPEND __merged_priv_requires "${__r}")
|
|
endif()
|
|
endforeach()
|
|
idf_component_set_property("${COMPONENT_NAME}" REQUIRES "${__merged_requires}")
|
|
idf_component_set_property("${COMPONENT_NAME}" PRIV_REQUIRES "${__merged_priv_requires}")
|
|
idf_component_set_property("${COMPONENT_NAME}" REQUIRED_IDF_TARGETS "${ARG_REQUIRED_IDF_TARGETS}")
|
|
idf_component_set_property("${COMPONENT_NAME}" COMPONENT_TYPE "${component_type}")
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_component_add_link_dependency
|
|
|
|
Backward-compatible shim for ``idf_component_add_link_dependency()`` used
|
|
by some managed components (e.g. espressif__esp_flash_nor).
|
|
#]]
|
|
function(idf_component_add_link_dependency)
|
|
set(single_value FROM TO)
|
|
cmake_parse_arguments(_ "" "${single_value}" "" ${ARGN})
|
|
|
|
idf_component_get_property(from_lib ${__FROM} COMPONENT_LIB)
|
|
if(__TO)
|
|
idf_component_get_property(to_lib ${__TO} COMPONENT_LIB)
|
|
else()
|
|
set(to_lib ${COMPONENT_LIB})
|
|
endif()
|
|
set_property(TARGET ${from_lib} APPEND PROPERTY INTERFACE_LINK_LIBRARIES $<LINK_ONLY:${to_lib}>)
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:macro:: register_component
|
|
|
|
Backward-compatible shim for the legacy ``register_component()`` macro used
|
|
by some older ESP-IDF examples (ULP apps, BLE mesh demos, etc.). It converts
|
|
the old-style ``COMPONENT_*`` variables to ``idf_component_register()`` calls.
|
|
#]]
|
|
macro(register_component)
|
|
# Convert space-separated variables to CMake lists.
|
|
foreach(_var COMPONENT_SRCS COMPONENT_SRCDIRS COMPONENT_ADD_INCLUDEDIRS
|
|
COMPONENT_PRIV_INCLUDEDIRS COMPONENT_REQUIRES COMPONENT_PRIV_REQUIRES
|
|
COMPONENT_ADD_LDFRAGMENTS COMPONENT_EMBED_FILES COMPONENT_EMBED_TXTFILES
|
|
COMPONENT_SRCEXCLUDE)
|
|
if(DEFINED ${_var})
|
|
separate_arguments(${_var})
|
|
endif()
|
|
endforeach()
|
|
|
|
idf_component_register(SRCS "${COMPONENT_SRCS}"
|
|
SRC_DIRS "${COMPONENT_SRCDIRS}"
|
|
INCLUDE_DIRS "${COMPONENT_ADD_INCLUDEDIRS}"
|
|
PRIV_INCLUDE_DIRS "${COMPONENT_PRIV_INCLUDEDIRS}"
|
|
REQUIRES "${COMPONENT_REQUIRES}"
|
|
PRIV_REQUIRES "${COMPONENT_PRIV_REQUIRES}"
|
|
LDFRAGMENTS "${COMPONENT_ADD_LDFRAGMENTS}"
|
|
EMBED_FILES "${COMPONENT_EMBED_FILES}"
|
|
EMBED_TXTFILES "${COMPONENT_EMBED_TXTFILES}"
|
|
EXCLUDE_SRCS "${COMPONENT_SRCEXCLUDE}")
|
|
endmacro()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_build_component
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_component(<component_dir> [<component_source>])
|
|
|
|
*component_dir[in]*
|
|
|
|
Directory path of the component.
|
|
|
|
*component_source[in,opt]*
|
|
|
|
Source of the component. One of:
|
|
|
|
* ``idf_components``
|
|
* ``project_managed_components``
|
|
* ``project_extra_components``
|
|
* ``project_components``
|
|
|
|
Defaults to ``project_components``.
|
|
|
|
Compatibility shim used by generated files (e.g. managed_components_list)
|
|
that initializes the component in build system v2 by delegating to
|
|
``__init_component``.
|
|
#]]
|
|
function(idf_build_component component_dir)
|
|
if(${ARGC} EQUAL 1)
|
|
set(component_source "project_components")
|
|
else()
|
|
set(component_source ${ARGV1})
|
|
endif()
|
|
|
|
# Get the component name and check if it's already initialized
|
|
get_filename_component(component_name "${component_dir}" NAME)
|
|
__get_component_interface(COMPONENT "${component_name}" OUTPUT existing_interface)
|
|
|
|
if(existing_interface)
|
|
# Component already exists, check if it's from the same directory and source
|
|
idf_component_get_property(existing_dir "${existing_interface}" COMPONENT_DIR)
|
|
idf_component_get_property(existing_source "${existing_interface}" COMPONENT_SOURCE)
|
|
|
|
# If same directory and same source, skip re-initialization
|
|
# This handles the case where component manager may output the same component
|
|
# multiple times due to path separator issues on Windows.
|
|
# TODO: Remove this workaround once idf-component-manager is fixed (IDF-14260)
|
|
if("${component_dir}" STREQUAL "${existing_dir}" AND
|
|
"${component_source}" STREQUAL "${existing_source}")
|
|
# Component already initialized from the same location, skip duplicate
|
|
return()
|
|
endif()
|
|
|
|
# Different directory or source, let __init_component handle the priority logic
|
|
endif()
|
|
|
|
idf_build_get_property(component_prefix PREFIX)
|
|
__init_component(DIRECTORY "${component_dir}"
|
|
PREFIX "${component_prefix}"
|
|
SOURCE "${component_source}")
|
|
endfunction()
|
|
|
|
#[[
|
|
__import_sdkconfig_as_build_properties()
|
|
|
|
Mirror every CONFIG_* symbol from the generated sdkconfig.cmake onto the
|
|
build-properties target so that the
|
|
`idf_build_get_property(var CONFIG_FOO)` idiom keeps working under the
|
|
compatibility shim. Called from kconfig.cmake::__generate_sdkconfig after
|
|
each kconfgen run.
|
|
#]]
|
|
function(__import_sdkconfig_as_build_properties)
|
|
idf_build_get_property(sdkconfig_cmake __SDKCONFIG_CMAKE)
|
|
if(NOT sdkconfig_cmake OR NOT EXISTS "${sdkconfig_cmake}")
|
|
return()
|
|
endif()
|
|
include("${sdkconfig_cmake}")
|
|
if(NOT DEFINED CONFIGS_LIST)
|
|
return()
|
|
endif()
|
|
idf_build_set_property(__CONFIG_VARIABLES "${CONFIGS_LIST}")
|
|
foreach(config IN LISTS CONFIGS_LIST)
|
|
idf_build_set_property("${config}" "${${config}}")
|
|
endforeach()
|
|
endfunction()
|