mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Legacy ulp_embed_binary() call-site sources were attached directly to the CMake v2 child executable. However, the ULP runtime is linked with WHOLE_ARCHIVE through target_link_options(), which CMake emits before executable objects. This reversed the CMake v1 order, changed ULP code placement, and allowed runtime weak definitions to take precedence over legacy strong handlers. Build the call-site sources as a dedicated static archive and place its whole-archive option before the runtime archive. Preserve the parent sdkconfig and include view, including FSM-specific include behavior, without generating a synthetic component tree. Factor linker-specific whole-archive handling into a shared CMake v2 helper for GNU, Darwin, and ULP FSM linkers. Link the archive target normally as well so CMake tracks build and relink dependencies when a legacy ULP source changes.
1518 lines
58 KiB
CMake
1518 lines
58 KiB
CMake
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
include_guard(GLOBAL)
|
|
|
|
include(utilities)
|
|
include(CheckCCompilerFlag)
|
|
include(CheckCXXCompilerFlag)
|
|
include(component_validation)
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_build_set_property
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_set_property(<property> <value> [APPEND])
|
|
|
|
*property[in]*
|
|
|
|
Property name.
|
|
|
|
*value[in]*
|
|
|
|
Property value.
|
|
|
|
*APPEND*
|
|
|
|
Append the value to the property's current value instead of replacing
|
|
it.
|
|
|
|
Set the value of the specified property related to the ESP-IDF build. The
|
|
property is also added to the internal list of build properties if it isn't
|
|
already there.
|
|
#]]
|
|
function(idf_build_set_property property value)
|
|
set(options APPEND)
|
|
set(one_value)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if("${property}" STREQUAL MINIMAL_BUILD)
|
|
# TODO: Remove this MINIMAL_BUILD compatibility block once CMake v1 is fully deprecated.
|
|
# V1 compatibility shims may still set MINIMAL_BUILD; warn only for native CMake v2 projects.
|
|
idf_build_get_property(_v1_compat __V1_COMPAT_SHIM)
|
|
if(NOT _v1_compat)
|
|
idf_warn("Build property 'MINIMAL_BUILD' is obsolete and will be ignored")
|
|
endif()
|
|
endif()
|
|
|
|
set(append)
|
|
if(ARG_APPEND)
|
|
set(append APPEND)
|
|
endif()
|
|
|
|
__set_property(TARGET idf_build_properties
|
|
PROPERTY "${property}"
|
|
PROPERTIES BUILD_PROPERTIES
|
|
VALUE "${value}"
|
|
${append})
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_build_get_property
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_get_property(<var> <property> [GENERATOR_EXPRESSION])
|
|
|
|
*variable[out]*
|
|
|
|
Variable to store the value in.
|
|
|
|
*property[in]*
|
|
|
|
Property name to get the value of.
|
|
|
|
*GENERATOR_EXPRESSION[in]*
|
|
|
|
Obtain the generator expression for the property rather than the actual
|
|
value.
|
|
|
|
Get the value of the specified property related to the ESP-IDF build.
|
|
#]]
|
|
function(idf_build_get_property variable property)
|
|
set(options GENERATOR_EXPRESSION)
|
|
set(one_value)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if("${property}" STREQUAL BUILD_COMPONENTS)
|
|
# BUILD_COMPONENTS is populated by the Build system v1 compatibility
|
|
# shim; reject only when running as a native Build system v2 project.
|
|
idf_build_get_property(_v1_compat __V1_COMPAT_SHIM)
|
|
if(NOT _v1_compat)
|
|
idf_die("Build property 'BUILD_COMPONENTS' is not supported")
|
|
endif()
|
|
endif()
|
|
|
|
set(genexpr)
|
|
if(ARG_GENERATOR_EXPRESSION)
|
|
set(genexpr GENERATOR_EXPRESSION)
|
|
endif()
|
|
|
|
__get_property(TARGET idf_build_properties
|
|
PROPERTY "${property}"
|
|
OUTPUT value
|
|
${genexpr})
|
|
set(${variable} ${value} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__dump_build_properties()
|
|
|
|
Dump all build properties.
|
|
#]]
|
|
function(__dump_build_properties)
|
|
idf_build_get_property(properties BUILD_PROPERTIES)
|
|
idf_msg("build properties: ${properties}")
|
|
foreach(property IN LISTS properties)
|
|
idf_build_get_property(value ${property})
|
|
idf_msg(" ${property}: ${value}")
|
|
endforeach()
|
|
endfunction()
|
|
|
|
#[[
|
|
__idf_build_dispatch_build_event(<event> <target>)
|
|
|
|
*event[in]*
|
|
|
|
Build event name. Currently only ``POST_ELF`` is supported. Other build
|
|
events may be extended when required.
|
|
|
|
*target[in]*
|
|
|
|
Name of the primary CMake target at this event point. Passed as the
|
|
sole argument to every registered callback so that callbacks can
|
|
operate on the correct target without querying build properties.
|
|
For ``POST_ELF`` this is the executable target name.
|
|
|
|
Internal dispatcher called by the build system at well-defined lifecycle
|
|
points. Invokes every CMake function registered for
|
|
``event`` via ``idf_component_register_build_event_callback``.
|
|
#]]
|
|
function(__idf_build_dispatch_build_event event target)
|
|
idf_build_get_property(callbacks "__BUILD_EVENT_CALLBACKS_${event}")
|
|
foreach(cb IN LISTS callbacks)
|
|
cmake_language(CALL "${cb}" "${target}")
|
|
endforeach()
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_library_interface_or_die(LIBRARY <library>
|
|
OUTPUT <variable>)
|
|
|
|
*LIBRARY[in]*
|
|
|
|
Library interface or alias.
|
|
|
|
*OUTPUT[out]*
|
|
|
|
Output variable to store the library interface.
|
|
|
|
Verify that "LIBRARY" is a known interface created by ``idf_build_library``
|
|
or its alias. If it is, return the library interface; otherwise, terminate
|
|
the build process.
|
|
#]]
|
|
function(__get_library_interface_or_die)
|
|
set(options)
|
|
set(one_value LIBRARY OUTPUT)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_LIBRARY)
|
|
idf_die("LIBRARY option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT)
|
|
idf_die("OUTPUT option is required")
|
|
endif()
|
|
|
|
__get_real_target(TARGET ${ARG_LIBRARY} OUTPUT library_interface)
|
|
idf_build_get_property(library_interfaces LIBRARY_INTERFACES)
|
|
|
|
if(NOT "${library_interface}" IN_LIST library_interfaces)
|
|
idf_die("Library interface '${ARG_LIBRARY}' does not exist")
|
|
endif()
|
|
set(${ARG_OUTPUT} ${library_interface} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_library_set_property
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_library_set_property(<library> <property> <value> [APPEND])
|
|
|
|
*library[in]*
|
|
|
|
Library interface target or alias.
|
|
|
|
*property[in]*
|
|
|
|
Property name.
|
|
|
|
*value[in]*
|
|
|
|
Property value.
|
|
|
|
*APPEND*
|
|
|
|
Append the value to the property's current value instead of replacing
|
|
it.
|
|
|
|
Set the value of the specified library property. The property is also added
|
|
to the internal list of library properties if it isn't already there.
|
|
#]]
|
|
function(idf_library_set_property library property value)
|
|
set(options APPEND)
|
|
set(one_value)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
set(append)
|
|
if(ARG_APPEND)
|
|
set(append APPEND)
|
|
endif()
|
|
|
|
__get_library_interface_or_die(LIBRARY "${library}" OUTPUT library_interface)
|
|
__set_property(TARGET "${library_interface}"
|
|
PROPERTY "${property}"
|
|
PROPERTIES LIBRARY_PROPERTIES
|
|
VALUE "${value}"
|
|
${append})
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_library_get_property
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_library_get_property(<variable> <library> <property> [GENERATOR_EXPRESSION])
|
|
|
|
*variable[out]*
|
|
|
|
Variable to store the value in.
|
|
|
|
*library[in]*
|
|
|
|
Library interface target or alias.
|
|
|
|
*property[in]*
|
|
|
|
Property name to get the value of.
|
|
|
|
*GENERATOR_EXPRESSION*
|
|
|
|
Obtain the generator expression for the property rather than the actual
|
|
value.
|
|
|
|
Retrieve the value of the specified library property.
|
|
#]]
|
|
function(idf_library_get_property variable library property)
|
|
set(options GENERATOR_EXPRESSION)
|
|
set(one_value)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
set(genexpr)
|
|
if(ARG_GENERATOR_EXPRESSION)
|
|
set(genexpr GENERATOR_EXPRESSION)
|
|
endif()
|
|
|
|
__get_library_interface_or_die(LIBRARY "${library}" OUTPUT library_interface)
|
|
__get_property(TARGET "${library_interface}"
|
|
PROPERTY "${property}"
|
|
OUTPUT value
|
|
${genexpr})
|
|
set(${variable} ${value} PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[
|
|
__dump_library_properties(<libraries>)
|
|
|
|
*libraries*
|
|
|
|
List of library interfaces whose properties should be displayed.
|
|
|
|
Dump all properties for the libraries listed in ``<libraries>``.
|
|
#]]
|
|
function(__dump_library_properties libraries)
|
|
foreach(library IN LISTS libraries)
|
|
idf_library_get_property(properties "${library}" LIBRARY_PROPERTIES)
|
|
idf_msg("library '${library}' properties: ${properties}")
|
|
foreach(property IN LISTS properties)
|
|
idf_library_get_property(value "${library}" "${property}")
|
|
idf_msg(" ${property}: ${value}")
|
|
endforeach()
|
|
endforeach()
|
|
endfunction()
|
|
|
|
function(__idf_build_link_whole_archive target scope library)
|
|
idf_build_get_property(linker_type LINKER_TYPE)
|
|
if(linker_type STREQUAL "GNU")
|
|
set(link_option "SHELL:-Wl,--whole-archive $<TARGET_FILE:${library}> -Wl,--no-whole-archive")
|
|
elseif(linker_type STREQUAL "Darwin")
|
|
set(link_option "SHELL:-Wl,-force_load $<TARGET_FILE:${library}>")
|
|
elseif(linker_type STREQUAL "ULP_FSM")
|
|
set(link_option "SHELL:--whole-archive $<TARGET_FILE:${library}> --no-whole-archive")
|
|
else()
|
|
target_link_libraries(${target} ${scope} ${library})
|
|
return()
|
|
endif()
|
|
|
|
# ARGN may contain BEFORE to place the archive ahead of existing link options.
|
|
# Also link the target normally so CMake tracks its build and relink dependencies.
|
|
target_link_options(${target} ${ARGN} ${scope} "${link_option}")
|
|
target_link_libraries(${target} ${scope} ${library})
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_build_library
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_library(<library>
|
|
[COMPONENTS <component>...])
|
|
|
|
*library[in]*
|
|
|
|
Name of the library interface to be created.
|
|
|
|
*COMPONENTS[in,opt]*
|
|
|
|
List of component names to add to the library.
|
|
|
|
Create a new library interface target with the name specified in the
|
|
``library`` option and link component targets to it based on the component
|
|
names provided in the ``COMPONENTS`` option. If ``COMPONENTS`` option is
|
|
not set, link component targets of all discovered components.
|
|
|
|
List of library properties
|
|
|
|
LIBRARY_COMPONENTS
|
|
List of component as specified by the ``COMPONENTS`` option.
|
|
|
|
LIBRARY_COMPONENTS_LINKED
|
|
List of component names linked to the library based on recursive evaluations
|
|
of the INTERFACE_LINK_LIBRARIES and LINK_LIBRARIES target properties.
|
|
|
|
LIBRARY_COMPONENT_INTERFACES_LINKED
|
|
List of component interfaces linked to the library based on recursive
|
|
evaluations of the INTERFACE_LINK_LIBRARIES and LINK_LIBRARIES target
|
|
properties.
|
|
#]]
|
|
function(idf_build_library library)
|
|
set(options)
|
|
set(one_value)
|
|
set(multi_value COMPONENTS)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
idf_build_get_property(project_initialized __PROJECT_INITIALIZED)
|
|
if(NOT project_initialized)
|
|
idf_die("The IDF project is not initialized. The 'idf_project_init()' must be called first.")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_COMPONENTS)
|
|
# The library should include all discovered components.
|
|
idf_build_get_property(component_names COMPONENTS_DISCOVERED)
|
|
set(ARG_COMPONENTS "${component_names}")
|
|
endif()
|
|
|
|
# Create library interface.
|
|
add_library("${library}" INTERFACE)
|
|
idf_build_set_property(LIBRARY_INTERFACES "${library}" APPEND)
|
|
idf_library_set_property("${library}" LIBRARY_COMPONENTS "${ARG_COMPONENTS}")
|
|
|
|
# Add global include directories, such as the directory containing
|
|
# sdkconfig, to the library's include directories.
|
|
idf_build_get_property(include_directories INCLUDE_DIRECTORIES GENERATOR_EXPRESSION)
|
|
target_include_directories("${library}" INTERFACE "${include_directories}")
|
|
|
|
# Add link options.
|
|
idf_build_get_property(link_options LINK_OPTIONS)
|
|
target_link_options(${library} INTERFACE "${link_options}")
|
|
|
|
# Include the requested components and link their interface targets to the
|
|
# library.
|
|
#
|
|
# On the GNU linker, bracket the component interface list with
|
|
# --start-group/--end-group so the linker re-scans archives until all
|
|
# symbols resolve. Required when the archive defining a __wrap_*
|
|
# implementation is encountered after the archive consuming the wrapped
|
|
# symbol on the link line; without group iteration the wrap symbol is
|
|
# unresolved.
|
|
idf_build_get_property(linker_type LINKER_TYPE)
|
|
if("${linker_type}" STREQUAL "GNU")
|
|
target_link_libraries("${library}" INTERFACE "-Wl,--start-group")
|
|
endif()
|
|
foreach(component_name IN LISTS ARG_COMPONENTS)
|
|
idf_component_include("${component_name}")
|
|
idf_component_get_property(component_interface "${component_name}" COMPONENT_INTERFACE)
|
|
target_link_libraries("${library}" INTERFACE "${component_interface}")
|
|
endforeach()
|
|
if("${linker_type}" STREQUAL "GNU")
|
|
target_link_libraries("${library}" INTERFACE "-Wl,--end-group")
|
|
endif()
|
|
|
|
# Process optional requirements in DEFERRED mode only (no-op in IMMEDIATE or when unset).
|
|
idf_build_get_property(opt_req_mode IDF_COMPONENT_OPTIONAL_REQUIRES_MODE)
|
|
if("${opt_req_mode}" STREQUAL "DEFERRED")
|
|
__idf_component_process_optional_requires()
|
|
endif()
|
|
|
|
# Get all targets transitively linked to the library interface target.
|
|
__get_target_dependencies(TARGET "${library}" OUTPUT dependencies)
|
|
|
|
# Identify the components linked to the library by looking at all targets
|
|
# that are transitively linked to the library and mapping these targets to
|
|
# components. Store the linked component name in LIBRARY_COMPONENTS_LINKED
|
|
# and component interface in LIBRARY_COMPONENT_INTERFACES_LINKED property.
|
|
foreach(dep IN LISTS dependencies)
|
|
__get_component_interface(COMPONENT "${dep}" OUTPUT component_interface)
|
|
if(NOT component_interface)
|
|
continue()
|
|
endif()
|
|
idf_library_get_property(component_interfaces_linked "${library}" LIBRARY_COMPONENT_INTERFACES_LINKED)
|
|
if("${component_interface}" IN_LIST component_interfaces_linked)
|
|
continue()
|
|
endif()
|
|
|
|
idf_component_get_property(component_name "${component_interface}" COMPONENT_NAME)
|
|
idf_library_set_property("${library}" LIBRARY_COMPONENTS_LINKED "${component_name}" APPEND)
|
|
idf_library_set_property("${library}" LIBRARY_COMPONENT_INTERFACES_LINKED "${component_interface}" APPEND)
|
|
endforeach()
|
|
|
|
# Collect linker fragment files from all components linked to the library
|
|
# interface and store them in the __LDGEN_FRAGMENT_FILES files. This
|
|
# property is used by ldgen to generate template-based linker scripts.
|
|
idf_library_get_property(component_interfaces_linked "${library}" LIBRARY_COMPONENT_INTERFACES_LINKED)
|
|
foreach(component_interface IN LISTS component_interfaces_linked)
|
|
idf_component_get_property(component_ldfragments "${component_interface}" LDFRAGMENTS)
|
|
idf_component_get_property(component_directory "${component_interface}" COMPONENT_DIR)
|
|
__get_absolute_paths(PATHS "${component_ldfragments}"
|
|
BASE_DIR "${component_directory}"
|
|
OUTPUT ldfragments)
|
|
idf_library_set_property("${library}" __LDGEN_FRAGMENT_FILES "${ldfragments}" APPEND)
|
|
endforeach()
|
|
|
|
# Collect the filenames of project component archives, which are considered
|
|
# mutable, in the __LDGEN_MUTABLE_LIBS library property. These libraries
|
|
# are placed by ldgen into a separate location in the linker script,
|
|
# enabling the fast reflashing feature.
|
|
idf_library_get_property(component_interfaces_linked "${library}" LIBRARY_COMPONENT_INTERFACES_LINKED)
|
|
foreach(component_interface IN LISTS component_interfaces_linked)
|
|
idf_component_get_property(component_source "${component_interface}" COMPONENT_SOURCE)
|
|
idf_component_get_property(component_target "${component_interface}" COMPONENT_TARGET)
|
|
idf_component_get_property(component_type "${component_interface}" COMPONENT_TYPE)
|
|
|
|
if("${component_type}" STREQUAL "CONFIG_ONLY")
|
|
# Configuration only component interface target without a library.
|
|
continue()
|
|
endif()
|
|
|
|
if(NOT "${component_source}" STREQUAL "project_components")
|
|
# Add only project components as mutable.
|
|
continue()
|
|
endif()
|
|
|
|
idf_library_set_property("${library}" __LDGEN_MUTABLE_LIBS
|
|
"$<TARGET_LINKER_FILE_NAME:${component_target}>" APPEND)
|
|
endforeach()
|
|
|
|
# Collect archive files from all targets linked to the library interface
|
|
# and store them in the __LDGEN_DEPENDS and __LDGEN_LIBRARIES library
|
|
# properties. These properties are used by ldgen to generate linker scripts
|
|
# from templates. The __LDGEN_LIBRARIES property contains a list of
|
|
# TARGET_FILE generator expressions for archive files.
|
|
foreach(dep IN LISTS dependencies)
|
|
if(NOT TARGET "${dep}")
|
|
continue()
|
|
endif()
|
|
|
|
get_target_property(type "${dep}" TYPE)
|
|
if("${type}" STREQUAL "INTERFACE_LIBRARY")
|
|
continue()
|
|
endif()
|
|
|
|
idf_library_get_property(ldgen_depends "${library}" __LDGEN_DEPENDS)
|
|
if(NOT "${dep}" IN_LIST ldgen_depends)
|
|
idf_library_set_property("${library}" __LDGEN_LIBRARIES "$<TARGET_FILE:${dep}>" APPEND)
|
|
idf_library_set_property("${library}" __LDGEN_DEPENDS ${dep} APPEND)
|
|
endif()
|
|
endforeach()
|
|
|
|
# Create a sanitized library interface name that can be used as a suffix
|
|
# for files and targets specific to the library.
|
|
string(MAKE_C_IDENTIFIER "_${library}" suffix)
|
|
|
|
idf_library_get_property(component_interfaces_linked "${library}" LIBRARY_COMPONENT_INTERFACES_LINKED)
|
|
|
|
# Build -I arguments for the public include dirs of every component in the
|
|
# linked closure. These are appended to every linker-script preprocess so a
|
|
# template can #include any component header (e.g. the ULP memory layout
|
|
# including soc/soc.h) without the component having to know about it.
|
|
# INCLUDE_DIRS is stored relative to each component's COMPONENT_DIR.
|
|
set(component_include_flags "")
|
|
foreach(ci IN LISTS component_interfaces_linked)
|
|
idf_component_get_property(ci_dir "${ci}" COMPONENT_DIR)
|
|
idf_component_get_property(ci_includes "${ci}" INCLUDE_DIRS)
|
|
foreach(ci_include IN LISTS ci_includes)
|
|
get_filename_component(ci_include_abs "${ci_include}" ABSOLUTE BASE_DIR "${ci_dir}")
|
|
# Quote the path: the preprocessor invocation splits CFLAGS with
|
|
# separate_arguments(UNIX_COMMAND), so unquoted paths containing
|
|
# spaces would fall apart.
|
|
string(APPEND component_include_flags " -I\"${ci_include_abs}\"")
|
|
endforeach()
|
|
endforeach()
|
|
|
|
# Linker scripts marked MEMORY (the memory-layout base for the link) are
|
|
# emitted as -T before the rest, so section-placement scripts from any
|
|
# component can reference their MEMORY regions and REGION_ALIASes. Collect
|
|
# across all components into two ordered lists and emit memory ones first.
|
|
set(memory_scripts "")
|
|
set(other_scripts "")
|
|
|
|
foreach(component_interface IN LISTS component_interfaces_linked)
|
|
idf_component_get_property(component_dir "${component_interface}" COMPONENT_DIR)
|
|
idf_component_get_property(component_build_dir "${component_interface}" COMPONENT_BUILD_DIR)
|
|
idf_component_get_property(preprocessed "${component_interface}" __LINKER_SCRIPTS_PREPROCESSED)
|
|
|
|
# Add linker scripts that do not require ldgen processing.
|
|
idf_component_get_property(scripts_static "${component_interface}" LINKER_SCRIPTS)
|
|
__get_absolute_paths(PATHS "${scripts_static}" BASE_DIR "${component_dir}" OUTPUT scripts_static_abs)
|
|
foreach(script IN LISTS scripts_static_abs)
|
|
__linker_script_key("${script}" script_key)
|
|
idf_component_get_property(script_is_memory "${component_interface}"
|
|
"LINKER_SCRIPT_MEMORY_${script_key}")
|
|
if(script MATCHES "\\.in$")
|
|
# If the linker script file name ends with the ".in" extension,
|
|
# preprocess it with the C preprocessor.
|
|
get_filename_component(script_name "${script}" NAME_WLE)
|
|
set(script_out "${component_build_dir}/ld/${script_name}")
|
|
string(MAKE_C_IDENTIFIER "gen_${script_name}" script_target)
|
|
|
|
if(NOT preprocessed)
|
|
file(MAKE_DIRECTORY "${component_build_dir}/ld")
|
|
idf_component_get_property(script_flags "${component_interface}"
|
|
"LINKER_SCRIPT_FLAGS_${script_key}")
|
|
__preprocess_linker_script("${script}" "${script_out}"
|
|
"${script_flags}" "${component_include_flags}")
|
|
# Add a custom target for the preprocessed script.
|
|
add_custom_target(${script_target} DEPENDS "${script_out}")
|
|
endif()
|
|
# Add dependency for the library interface to ensure the script is
|
|
# generated before linking.
|
|
add_dependencies("${library}" ${script_target})
|
|
set(emit_script "${script_out}")
|
|
else()
|
|
set(emit_script "${script}")
|
|
endif()
|
|
if(script_is_memory)
|
|
list(APPEND memory_scripts "${emit_script}")
|
|
else()
|
|
list(APPEND other_scripts "${emit_script}")
|
|
endif()
|
|
endforeach()
|
|
|
|
# Generate linker scripts from templates. The generated output path and
|
|
# the optional preprocessor flags for each template are stored as
|
|
# component properties keyed by the template, so no second list has to
|
|
# stay index-aligned with LINKER_SCRIPTS_TEMPLATE.
|
|
idf_component_get_property(template_scripts "${component_interface}" LINKER_SCRIPTS_TEMPLATE)
|
|
|
|
foreach(template IN LISTS template_scripts)
|
|
__linker_script_key("${template}" template_key)
|
|
idf_component_get_property(script "${component_interface}"
|
|
"LINKER_SCRIPT_GENERATED_${template_key}")
|
|
if(template MATCHES "\\.in$")
|
|
# If the linker script file name ends with the ".in" extension,
|
|
# preprocess it with the C preprocessor.
|
|
get_filename_component(template_name "${template}" NAME)
|
|
set(template_out "${component_build_dir}/ld/${template_name}")
|
|
if(NOT preprocessed)
|
|
file(MAKE_DIRECTORY "${component_build_dir}/ld")
|
|
idf_component_get_property(template_flags "${component_interface}"
|
|
"LINKER_SCRIPT_FLAGS_${template_key}")
|
|
__preprocess_linker_script("${template}" "${template_out}"
|
|
"${template_flags}" "${component_include_flags}")
|
|
endif()
|
|
set(template "${template_out}")
|
|
endif()
|
|
set(script "${script}${suffix}")
|
|
__ldgen_process_template(LIBRARY "${library}"
|
|
TEMPLATE "${template}"
|
|
SUFFIX "${suffix}"
|
|
OUTPUT "${script}")
|
|
idf_component_get_property(template_is_memory "${component_interface}"
|
|
"LINKER_SCRIPT_MEMORY_${template_key}")
|
|
if(template_is_memory)
|
|
list(APPEND memory_scripts "${script}")
|
|
else()
|
|
list(APPEND other_scripts "${script}")
|
|
endif()
|
|
# Add a custom target for the generated script and include it as a
|
|
# dependency for the library interface to ensure the script is
|
|
# generated before linking.
|
|
get_filename_component(basename "${script}" NAME)
|
|
string(MAKE_C_IDENTIFIER "${basename}" basename)
|
|
add_custom_target(gen_${basename} DEPENDS "${script}")
|
|
add_dependencies("${library}" gen_${basename})
|
|
endforeach()
|
|
|
|
# The static scripts for the given component have already been
|
|
# preprocessed and are identical across all libraries. Therefore, there
|
|
# is no need to preprocess them multiple times. Set the component
|
|
# property to indicate that the linker scripts have already been
|
|
# preprocessed.
|
|
idf_component_set_property("${component_interface}" __LINKER_SCRIPTS_PREPROCESSED YES)
|
|
endforeach()
|
|
|
|
# Emit the collected linker scripts as -T on the library interface, with
|
|
# memory-layout scripts first so their MEMORY regions and REGION_ALIASes are
|
|
# seen before any section-placement script references them.
|
|
foreach(script IN LISTS memory_scripts other_scripts)
|
|
get_filename_component(script_dir "${script}" DIRECTORY)
|
|
# Add the linker script directory to the linker search path so INCLUDE
|
|
# directives inside a script can resolve sibling scripts by name.
|
|
target_link_directories("${library}" INTERFACE "${script_dir}")
|
|
# Add the linker script to the link by absolute path. Passing the full
|
|
# path (rather than a bare name resolved via -L) keeps this working for
|
|
# direct linker drivers such as the ULP FSM esp32ulp-elf-ld link, where
|
|
# the -L search directories follow the -T options on the command line
|
|
# and GNU ld therefore does not use them to locate the -T script.
|
|
# Regarding the usage of SHELL, see
|
|
# https://cmake.org/cmake/help/latest/command/target_link_options.html#option-de-duplication
|
|
# Quote the path: SHELL: strings are re-split on spaces, so an unquoted
|
|
# path containing spaces would fall apart.
|
|
target_link_options("${library}" INTERFACE "SHELL:-T \"${script}\"")
|
|
# Add the linker script as a dependency to ensure the executable is
|
|
# re-linked if the script changes.
|
|
set_property(TARGET "${library}" APPEND PROPERTY INTERFACE_LINK_DEPENDS "${script}")
|
|
endforeach()
|
|
|
|
# Validate components linked to this library
|
|
__component_validation_run_checks(LIBRARY "${library}")
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_build_executable
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_executable(<executable>
|
|
[COMPONENTS <component>...]
|
|
[NAME <name>]
|
|
[SUFFIX <suffix>])
|
|
|
|
*executable[in]*
|
|
|
|
Name of the executable target to be created.
|
|
|
|
*COMPONENTS[in,opt]*
|
|
|
|
List of component names to add to the library.
|
|
|
|
*NAME[in,opt]*
|
|
|
|
Optional preferred generated binary name. If not provided, the
|
|
``executable`` name is used.
|
|
|
|
*SUFFIX[in,opt]*
|
|
|
|
Optional ``executable`` suffix.
|
|
|
|
*MAPFILE_TARGET[in,opt]*
|
|
|
|
Name of the target for the map file. If provided, the link map file is
|
|
generated for the specified executable, and the ``MAPFILE_TARGET``
|
|
target name is created for it. The ``MAPFILE_PATH`` property with the
|
|
link map file path is added to the ``MAPFILE_TARGET`` target. This can
|
|
be used for other targets that depend on the link map file. The link map file
|
|
is not generated on Darwin host, so the target ``MAPFILE_TARGET`` may not
|
|
be created if link map file is not generated.
|
|
|
|
Create a new executable target using the name specified in the
|
|
``executable`` argument, and link it to the library created with the
|
|
component names provided in the ``COMPONENTS`` option. If the
|
|
``COMPONENTS`` option is not set, all discovered components are added to
|
|
the library. Optionally set the executable name and suffix. The executable
|
|
library target name is added to the ``LIBRARY_INTERFACE`` executable
|
|
property.
|
|
#]]
|
|
function(idf_build_executable executable)
|
|
set(options)
|
|
set(one_value NAME SUFFIX MAPFILE_TARGET)
|
|
set(multi_value COMPONENTS)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_NAME)
|
|
set(ARG_NAME "${executable}")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_SUFFIX)
|
|
set(ARG_SUFFIX ".elf")
|
|
endif()
|
|
|
|
get_property(enabled_languages GLOBAL PROPERTY ENABLED_LANGUAGES)
|
|
if(C IN_LIST enabled_languages)
|
|
set(stub_language C)
|
|
set(stub_extension c)
|
|
elseif(CXX IN_LIST enabled_languages)
|
|
set(stub_language CXX)
|
|
set(stub_extension c)
|
|
elseif(ASM IN_LIST enabled_languages)
|
|
set(stub_language ASM)
|
|
set(stub_extension S)
|
|
else()
|
|
message(FATAL_ERROR "idf_build_executable() requires C, CXX, or ASM to be enabled.")
|
|
endif()
|
|
|
|
idf_build_get_property(build_dir BUILD_DIR)
|
|
|
|
set(library "library_${executable}")
|
|
idf_build_library(${library} COMPONENTS "${ARG_COMPONENTS}")
|
|
|
|
set(executable_src ${CMAKE_BINARY_DIR}/executable_${executable}.${stub_extension})
|
|
if(NOT EXISTS "${executable_src}")
|
|
file(TOUCH "${executable_src}")
|
|
endif()
|
|
if(stub_language STREQUAL "CXX")
|
|
set_source_files_properties("${executable_src}" PROPERTIES LANGUAGE CXX)
|
|
endif()
|
|
|
|
add_executable(${executable} "${executable_src}")
|
|
|
|
set_target_properties(${executable} PROPERTIES OUTPUT_NAME ${ARG_NAME})
|
|
|
|
if(ARG_SUFFIX)
|
|
set_target_properties(${executable} PROPERTIES SUFFIX ${ARG_SUFFIX})
|
|
endif()
|
|
|
|
if(NOT stub_language STREQUAL "C")
|
|
set_target_properties(${executable} PROPERTIES LINKER_LANGUAGE ${stub_language})
|
|
endif()
|
|
|
|
target_link_libraries(${executable} PRIVATE ${library})
|
|
|
|
idf_build_get_property(linker_type LINKER_TYPE)
|
|
if(ARG_MAPFILE_TARGET AND "${linker_type}" STREQUAL "GNU")
|
|
set(mapfile "${CMAKE_BINARY_DIR}/${ARG_NAME}.map")
|
|
target_link_options(${executable} PRIVATE "LINKER:--Map=${mapfile}")
|
|
# Add cross-reference table to the map file
|
|
target_link_options(${executable} PRIVATE "LINKER:--cref")
|
|
add_custom_command(
|
|
OUTPUT "${mapfile}"
|
|
DEPENDS ${executable}
|
|
)
|
|
add_custom_target(${ARG_MAPFILE_TARGET}
|
|
DEPENDS "${mapfile}"
|
|
)
|
|
set_target_properties(${ARG_MAPFILE_TARGET} PROPERTIES MAPFILE_PATH ${mapfile})
|
|
endif()
|
|
|
|
set_target_properties(${executable} PROPERTIES LIBRARY_INTERFACE ${library})
|
|
|
|
# Dispatch POST_ELF event once the executable target exists
|
|
__idf_build_dispatch_build_event(POST_ELF "${executable}")
|
|
endfunction()
|
|
|
|
#[[
|
|
__get_components_metadata(COMPONENTS <component_interface>...
|
|
OUTPUT <variable>)
|
|
|
|
*COMPONENTS[in]*
|
|
|
|
List of component interfaces for which to generate metadata.
|
|
|
|
*OUTPUT[out]*
|
|
|
|
Output variable to store JSON metadata.
|
|
|
|
Generate metadata in JSON format from ``COMPONENTS`` and store it in the
|
|
``OUTPUT`` variable.
|
|
#]]
|
|
function(__get_components_metadata)
|
|
set(options)
|
|
set(one_value OUTPUT)
|
|
set(multi_value COMPONENTS)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_COMPONENTS)
|
|
idf_die("COMPONENTS option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT)
|
|
idf_die("OUTPUT option is required")
|
|
endif()
|
|
|
|
set(components_json "")
|
|
|
|
foreach(interface IN LISTS ARG_COMPONENTS)
|
|
__idf_component_get_property_unchecked(name ${interface} COMPONENT_NAME)
|
|
__idf_component_get_property_unchecked(target ${interface} COMPONENT_REAL_TARGET)
|
|
__idf_component_get_property_unchecked(alias ${interface} COMPONENT_ALIAS)
|
|
__idf_component_get_property_unchecked(prefix ${interface} __PREFIX)
|
|
__idf_component_get_property_unchecked(dir ${interface} COMPONENT_DIR)
|
|
__idf_component_get_property_unchecked(type ${interface} COMPONENT_TYPE)
|
|
__idf_component_get_property_unchecked(lib ${interface} COMPONENT_LIB)
|
|
__idf_component_get_property_unchecked(reqs ${interface} REQUIRES)
|
|
__idf_component_get_property_unchecked(include_dirs ${interface} INCLUDE_DIRS)
|
|
__idf_component_get_property_unchecked(priv_reqs ${interface} PRIV_REQUIRES)
|
|
__idf_component_get_property_unchecked(managed_reqs ${interface} MANAGED_REQUIRES)
|
|
__idf_component_get_property_unchecked(managed_priv_reqs ${interface} MANAGED_PRIV_REQUIRES)
|
|
__idf_component_get_property_unchecked(component_source ${interface} COMPONENT_SOURCE)
|
|
if("${type}" STREQUAL "LIBRARY")
|
|
set(file "$<TARGET_LINKER_FILE:${lib}>")
|
|
|
|
# The idf_component_register function is converting each source file path defined
|
|
# in SRCS into absolute one. But source files can be also added with cmake's
|
|
# target_sources and have relative paths. This is used for example in log
|
|
# component. Let's make sure all source files have absolute path.
|
|
set(sources "")
|
|
get_target_property(srcs ${lib} SOURCES)
|
|
foreach(src IN LISTS srcs)
|
|
get_filename_component(src "${src}" ABSOLUTE BASE_DIR "${dir}")
|
|
list(APPEND sources "${src}")
|
|
endforeach()
|
|
|
|
else()
|
|
set(file "")
|
|
set(sources "")
|
|
endif()
|
|
|
|
__make_json_list("${reqs}" OUTPUT reqs)
|
|
__make_json_list("${priv_reqs}" OUTPUT priv_reqs)
|
|
__make_json_list("${managed_reqs}" OUTPUT managed_reqs)
|
|
__make_json_list("${managed_priv_reqs}" OUTPUT managed_priv_reqs)
|
|
__make_json_list("${include_dirs}" OUTPUT include_dirs)
|
|
__make_json_list("${sources}" OUTPUT sources)
|
|
|
|
string(JOIN "\n" component_json
|
|
" \"${name}\": {"
|
|
" \"alias\": \"${alias}\","
|
|
" \"target\": \"${target}\","
|
|
" \"prefix\": \"${prefix}\","
|
|
" \"dir\": \"${dir}\","
|
|
" \"source\": \"${component_source}\","
|
|
" \"type\": \"${type}\","
|
|
" \"lib\": \"${lib}\","
|
|
" \"reqs\": ${reqs},"
|
|
" \"priv_reqs\": ${priv_reqs},"
|
|
" \"managed_reqs\": ${managed_reqs},"
|
|
" \"managed_priv_reqs\": ${managed_priv_reqs},"
|
|
" \"file\": \"${file}\","
|
|
" \"sources\": ${sources},"
|
|
" \"include_dirs\": ${include_dirs}"
|
|
" }"
|
|
)
|
|
string(CONFIGURE "${component_json}" component_json)
|
|
if(NOT "${components_json}" STREQUAL "")
|
|
string(APPEND components_json ",\n")
|
|
endif()
|
|
string(APPEND components_json "${component_json}")
|
|
endforeach()
|
|
string(PREPEND components_json "{\n")
|
|
string(APPEND components_json "\n }")
|
|
set(${ARG_OUTPUT} "${components_json}" PARENT_SCOPE)
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_build_generate_metadata
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_generate_metadata([BINARY <binary>]
|
|
[EXECUTABLE <executable>]
|
|
[OUTPUT_FILE <file>]
|
|
[HINTS_OUTPUT_FILE <file>])
|
|
|
|
*BINARY[in,opt]*
|
|
|
|
Binary target for which to generate a metadata file.
|
|
|
|
*EXECUTABLE[in,opt]*
|
|
|
|
Executable target for which to generate a metadata file.
|
|
|
|
*OUTPUT_FILE[in,opt]*
|
|
|
|
Optional output file path for storing the metadata. If not provided,
|
|
the default path ``<build>/project_description.json`` is used.
|
|
|
|
*HINTS_OUTPUT_FILE[in,opt]*
|
|
|
|
Optional output file path for storing the merged hints YAML file. If
|
|
not provided, hints generation is skipped entirely. This opt-in
|
|
behaviour prevents hint files from different binaries overwriting each
|
|
other in multi-binary projects.
|
|
|
|
Generate metadata for the specified ``binary`` or ``executable`` target and
|
|
store it in the specified ``OUTPUT_FILE``. If no ``OUTPUT_FILE`` is
|
|
provided, the default location ``<build>/project_description.json`` will be
|
|
used.
|
|
#]]
|
|
function(idf_build_generate_metadata)
|
|
set(options)
|
|
set(one_value OUTPUT_FILE BINARY EXECUTABLE HINTS_OUTPUT_FILE)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT DEFINED ARG_BINARY AND NOT DEFINED ARG_EXECUTABLE)
|
|
idf_die("BINARY or EXECUTABLE option is required")
|
|
endif()
|
|
|
|
if(DEFINED ARG_BINARY)
|
|
# The EXECUTABLE_TARGET property is set by the idf_build_binary or
|
|
# the idf_sign_binary function.
|
|
get_target_property(ARG_EXECUTABLE "${ARG_BINARY}" EXECUTABLE_TARGET)
|
|
if(NOT ARG_EXECUTABLE)
|
|
idf_die("Binary target '${ARG_BINARY}' is missing 'EXECUTABLE_TARGET' property.")
|
|
endif()
|
|
|
|
# The BINARY_PATH property is set by the idf_build_binary or
|
|
# the idf_sign_binary function.
|
|
get_target_property(binary_path ${ARG_BINARY} BINARY_PATH)
|
|
if(NOT binary_path)
|
|
idf_die("Binary target '${ARG_BINARY}' is missing 'BINARY_PATH' property.")
|
|
endif()
|
|
get_filename_component(PROJECT_BIN "${binary_path}" NAME)
|
|
endif()
|
|
__get_executable_library_or_die(TARGET "${ARG_EXECUTABLE}" OUTPUT library)
|
|
|
|
idf_build_get_property(PROJECT_NAME PROJECT_NAME)
|
|
idf_build_get_property(PROJECT_VER PROJECT_VER)
|
|
idf_build_get_property(PROJECT_PATH PROJECT_DIR)
|
|
idf_build_get_property(IDF_PATH IDF_PATH)
|
|
idf_build_get_property(BUILD_DIR BUILD_DIR)
|
|
idf_build_get_property(SDKCONFIG SDKCONFIG)
|
|
idf_build_get_property(SDKCONFIG_DEFAULTS SDKCONFIG_DEFAULTS)
|
|
set(PROJECT_EXECUTABLE "$<TARGET_FILE_NAME:${ARG_EXECUTABLE}>")
|
|
if(NOT PROJECT_BIN)
|
|
set(PROJECT_BIN "")
|
|
endif()
|
|
if(CONFIG_APP_BUILD_TYPE_RAM)
|
|
set(PROJECT_BUILD_TYPE ram_app)
|
|
else()
|
|
set(PROJECT_BUILD_TYPE flash_app)
|
|
endif()
|
|
idf_build_get_property(IDF_VER IDF_VER)
|
|
|
|
idf_build_get_property(common_component_reqs __COMPONENT_REQUIRES_COMMON)
|
|
list(SORT common_component_reqs)
|
|
__make_json_list("${common_component_reqs}" OUTPUT common_component_reqs_json)
|
|
|
|
idf_library_get_property(build_components "${library}" LIBRARY_COMPONENTS_LINKED)
|
|
idf_library_get_property(build_component_interfaces "${library}" LIBRARY_COMPONENT_INTERFACES_LINKED)
|
|
list(SORT build_components)
|
|
list(SORT build_component_interfaces)
|
|
__make_json_list("${build_components}" OUTPUT build_components_json)
|
|
|
|
set(build_component_paths)
|
|
set(COMPONENT_KCONFIGS)
|
|
set(COMPONENT_KCONFIGS_PROJBUILD)
|
|
foreach(component_interface IN LISTS build_component_interfaces)
|
|
__idf_component_get_property_unchecked(component_dir "${component_interface}" COMPONENT_DIR)
|
|
__idf_component_get_property_unchecked(component_kconfig "${component_interface}" __KCONFIG)
|
|
__idf_component_get_property_unchecked(component_kconfig_projbuild "${component_interface}" __KCONFIG_PROJBUILD)
|
|
list(APPEND build_component_paths "${component_dir}")
|
|
if(component_kconfig)
|
|
list(APPEND COMPONENT_KCONFIGS "${component_kconfig}")
|
|
endif()
|
|
if(component_kconfig_projbuild)
|
|
list(APPEND COMPONENT_KCONFIGS_PROJBUILD "${component_kconfig_projbuild}")
|
|
endif()
|
|
endforeach()
|
|
__make_json_list("${build_component_paths}" OUTPUT build_component_paths_json)
|
|
|
|
__get_components_metadata(COMPONENTS "${build_component_interfaces}" OUTPUT build_component_info_json)
|
|
|
|
idf_build_get_property(component_interfaces COMPONENT_INTERFACES)
|
|
__get_components_metadata(COMPONENTS "${component_interfaces}" OUTPUT all_component_info_json)
|
|
|
|
__generate_gdbinit()
|
|
idf_build_get_property(gdbinit_files_prefix_map GDBINIT_FILES_PREFIX_MAP)
|
|
idf_build_get_property(gdbinit_files_symbols GDBINIT_FILES_SYMBOLS)
|
|
idf_build_get_property(gdbinit_files_py_extensions GDBINIT_FILES_PY_EXTENSIONS)
|
|
idf_build_get_property(gdbinit_files_connect GDBINIT_FILES_CONNECT)
|
|
__get_openocd_options(debug_arguments_openocd)
|
|
|
|
if(NOT DEFINED ARG_OUTPUT_FILE)
|
|
set(ARG_OUTPUT_FILE "${BUILD_DIR}/project_description.json")
|
|
endif()
|
|
|
|
get_filename_component(ARG_OUTPUT_FILE "${ARG_OUTPUT_FILE}" ABSOLUTE BASE_DIR "${BUILD_DIR}")
|
|
|
|
if(DEFINED ARG_HINTS_OUTPUT_FILE)
|
|
set(HINTS_FILE "${ARG_HINTS_OUTPUT_FILE}")
|
|
else()
|
|
set(HINTS_FILE "")
|
|
endif()
|
|
configure_file("${IDF_PATH}/tools/cmake/project_description.json.in" "${ARG_OUTPUT_FILE}.templ")
|
|
file(READ "${ARG_OUTPUT_FILE}.templ" project_description_json_templ)
|
|
file(REMOVE "${ARG_OUTPUT_FILE}.templ")
|
|
file(GENERATE OUTPUT "${ARG_OUTPUT_FILE}"
|
|
CONTENT "${project_description_json_templ}")
|
|
|
|
# Assumption: all hints.yml files are bare YAML lists (no "---" document
|
|
# separators). Plain string concatenation is safe under this assumption.
|
|
# Note for consumers: yaml.safe_load() only parses the first YAML document,
|
|
# so document separators in source files would cause data loss.
|
|
if(DEFINED ARG_HINTS_OUTPUT_FILE)
|
|
set(_merged_hints "")
|
|
set(_global_hints_file "${IDF_PATH}/tools/idf_py_actions/hints.yml")
|
|
if(EXISTS "${_global_hints_file}")
|
|
file(READ "${_global_hints_file}" _hints_content)
|
|
string(APPEND _merged_hints "${_hints_content}\n")
|
|
endif()
|
|
foreach(_comp_dir ${build_component_paths})
|
|
set(_hints_file "${_comp_dir}/hints.yml")
|
|
if(EXISTS "${_hints_file}")
|
|
file(READ "${_hints_file}" _hints_content)
|
|
string(APPEND _merged_hints "${_hints_content}\n")
|
|
endif()
|
|
endforeach()
|
|
file(WRITE "${ARG_HINTS_OUTPUT_FILE}" "${_merged_hints}")
|
|
endif()
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_build_binary
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_binary(<executable>
|
|
TARGET <target>
|
|
OUTPUT_FILE <file>
|
|
[ALL])
|
|
|
|
*executable[in]*
|
|
|
|
Executable target for which to generate a binary image file.
|
|
|
|
*TARGET[in]*
|
|
|
|
The name of the target that will be created for the generated binary
|
|
image.
|
|
|
|
*OUTPUT_FILE[in]*
|
|
|
|
Output file path for storing the binary image file.
|
|
|
|
*ALL[in,opt]*
|
|
|
|
If specified, the target will be added to the default build target.
|
|
|
|
Create a binary image for the specified ``executable`` target and save it
|
|
in the file specified with the ``OUTPUT_FILE`` option. A custom target
|
|
named ``TARGET`` will be created for the generated binary image. The path
|
|
of the generated binary image will be stored in the ``BINARY_PATH``
|
|
property and the executable target in the ``EXECUTABLE_TARGET`` property of
|
|
the ``TARGET``.
|
|
#]]
|
|
function(idf_build_binary executable)
|
|
set(options ALL)
|
|
set(one_value OUTPUT_FILE TARGET)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT TARGET idf::esptool_py)
|
|
idf_die("The 'esptool_py' component is not available")
|
|
endif()
|
|
|
|
if(NOT CONFIG_APP_BUILD_GENERATE_BINARIES)
|
|
idf_die("Binary file generation is not enabled with 'CONFIG_APP_BUILD_GENERATE_BINARIES'")
|
|
endif()
|
|
|
|
if(NOT TARGET "${executable}")
|
|
idf_die("The executable '${executable}' is not a cmake target")
|
|
endif()
|
|
|
|
get_target_property(type "${executable}" TYPE)
|
|
if(NOT "${type}" STREQUAL "EXECUTABLE")
|
|
idf_die("The executable target '${executable}' is not of the EXECUTABLE type")
|
|
endif()
|
|
|
|
idf_build_get_property(build_dir BUILD_DIR)
|
|
|
|
get_target_property(executable_name ${executable} OUTPUT_NAME)
|
|
if(NOT executable_name)
|
|
set(executable_name "${executable}")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_TARGET)
|
|
idf_die("TARGET option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT_FILE)
|
|
idf_die("OUTPUT_FILE option is required")
|
|
endif()
|
|
|
|
idf_component_get_property(esptool_py_cmd esptool_py ESPTOOLPY_CMD)
|
|
idf_component_get_property(esptool_elf2image_args esptool_py ESPTOOL_PY_ELF2IMAGE_ARGS)
|
|
|
|
get_filename_component(binary_name "${ARG_OUTPUT_FILE}" NAME)
|
|
|
|
# Create a custom command and target to generate a binary from an ELF file.
|
|
add_custom_command(OUTPUT "${ARG_OUTPUT_FILE}"
|
|
COMMAND ${esptool_py_cmd} elf2image ${esptool_elf2image_args}
|
|
-o "${ARG_OUTPUT_FILE}" "$<TARGET_FILE:${executable}>"
|
|
COMMAND ${CMAKE_COMMAND} -E echo "Generated ${ARG_OUTPUT_FILE}"
|
|
COMMAND ${CMAKE_COMMAND} -E md5sum "${ARG_OUTPUT_FILE}" > "${ARG_OUTPUT_FILE}.md5sum"
|
|
DEPENDS ${executable}
|
|
VERBATIM
|
|
WORKING_DIRECTORY ${build_dir}
|
|
COMMENT "Generating binary image '${binary_name}' from executable '${executable_name}'"
|
|
)
|
|
|
|
# Create a custom target to generate the binary file.
|
|
# If ALL is specified, include the target in the default build.
|
|
set(all_arg)
|
|
if(ARG_ALL)
|
|
set(all_arg ALL)
|
|
endif()
|
|
add_custom_target(${ARG_TARGET} ${all_arg} DEPENDS "${ARG_OUTPUT_FILE}")
|
|
|
|
# Store the path of the binary file in the BINARY_PATH property of the
|
|
# custom binary target, which is used by the idf_flash_binary.
|
|
set_target_properties(${ARG_TARGET} PROPERTIES BINARY_PATH ${ARG_OUTPUT_FILE})
|
|
|
|
# Store executable target name in the EXECUTABLE_TARGET property. This is used
|
|
# by the idf_build_generate_metadata function.
|
|
set_target_properties(${ARG_TARGET} PROPERTIES EXECUTABLE_TARGET ${executable})
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_sign_binary
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_sign_binary(<binary>
|
|
TARGET <target>
|
|
OUTPUT_FILE <file>
|
|
[KEYFILE <file>]
|
|
[ALL])
|
|
|
|
*binary[in]*
|
|
|
|
Binary image target for which to generate a signed binary image file.
|
|
The ``binary`` target is created by the :cmakev2:ref:`idf_build_binary`
|
|
function.
|
|
|
|
*TARGET[in]*
|
|
|
|
The name of the target that will be created for the signed binary
|
|
image.
|
|
|
|
*OUTPUT_FILE[in]*
|
|
|
|
Output file path for storing the signed binary image file.
|
|
|
|
*KEYFILE[in,opt]*
|
|
|
|
Optional path to the key file that should be used for signing. If not
|
|
provided, the key file specified by the
|
|
``CONFIG_SECURE_BOOT_SIGNING_KEY`` configuration option will be used.
|
|
|
|
*ALL[in,opt]*
|
|
|
|
If specified, the target will be added to the default build target.
|
|
|
|
Sign binary image specified by ``binary`` target with ``KEYFILE`` and save
|
|
it in the file specified with the `OUTPUT_FILE` option. A custom target
|
|
named ``TARGET`` will be created for the signed binary image. The path of
|
|
the signed binary image will be stored in the ``BINARY_PATH`` property and
|
|
the executable target in the ``EXECUTABLE_TARGET`` property of the
|
|
``TARGET``.
|
|
#]]
|
|
function(idf_sign_binary binary)
|
|
set(options ALL)
|
|
set(one_value OUTPUT_FILE TARGET KEYFILE)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT TARGET idf::esptool_py)
|
|
idf_die("The 'esptool_py' component is not available")
|
|
endif()
|
|
|
|
if(NOT CONFIG_APP_BUILD_GENERATE_BINARIES)
|
|
idf_die("Binary file generation is not enabled with 'CONFIG_APP_BUILD_GENERATE_BINARIES'")
|
|
endif()
|
|
|
|
if(NOT CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES)
|
|
idf_die("Binary file signing is not enabled with 'CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES'")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_TARGET)
|
|
idf_die("TARGET option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT_FILE)
|
|
idf_die("OUTPUT_FILE option is required")
|
|
endif()
|
|
|
|
if(ARG_KEYFILE)
|
|
set(keyfile "${ARG_KEYFILE}")
|
|
else()
|
|
idf_build_get_property(project_dir PROJECT_DIR)
|
|
get_filename_component(keyfile "${CONFIG_SECURE_BOOT_SIGNING_KEY}" ABSOLUTE BASE_DIR "${project_dir}")
|
|
endif()
|
|
|
|
if(CONFIG_SECURE_SIGNED_APPS_ECDSA_SCHEME)
|
|
set(secure_boot_version "1")
|
|
elseif(CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME OR CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME)
|
|
set(secure_boot_version "2")
|
|
endif()
|
|
|
|
idf_component_get_property(espsecure_py_cmd esptool_py ESPSECUREPY_CMD)
|
|
get_target_property(binary_path ${binary} BINARY_PATH)
|
|
if(NOT binary_path)
|
|
idf_die("Binary target '${binary}' is missing 'BINARY_PATH' property.")
|
|
endif()
|
|
get_filename_component(binary_name "${binary_path}" NAME)
|
|
get_filename_component(signed_binary_name "${ARG_OUTPUT_FILE}" NAME)
|
|
get_filename_component(key_name "${keyfile}" NAME)
|
|
|
|
add_custom_command(OUTPUT "${ARG_OUTPUT_FILE}"
|
|
COMMAND ${espsecure_py_cmd} sign-data
|
|
--version ${secure_boot_version} --keyfile "${keyfile}"
|
|
-o "${ARG_OUTPUT_FILE}" "${binary_path}"
|
|
COMMAND ${CMAKE_COMMAND} -E md5sum "${ARG_OUTPUT_FILE}" > "${ARG_OUTPUT_FILE}.md5sum"
|
|
DEPENDS "${binary}"
|
|
VERBATIM
|
|
COMMENT "Signing '${binary_name}' with key '${key_name}' into '${signed_binary_name}'"
|
|
)
|
|
# Create a custom target for the signed binary file.
|
|
# If ALL is specified, include the target in the default build.
|
|
set(all_arg)
|
|
if(ARG_ALL)
|
|
set(all_arg ALL)
|
|
endif()
|
|
add_custom_target(${ARG_TARGET} ${all_arg} DEPENDS "${ARG_OUTPUT_FILE}")
|
|
|
|
# Store the path of the binary file in the BINARY_PATH property of the
|
|
# custom signed binary target, which is used by the idf_flash_binary.
|
|
set_target_properties(${ARG_TARGET} PROPERTIES BINARY_PATH ${ARG_OUTPUT_FILE})
|
|
|
|
# Store executable target name in the EXECUTABLE_TARGET property. This is used
|
|
# by the idf_build_generate_metadata function.
|
|
get_target_property(executable "${binary}" EXECUTABLE_TARGET)
|
|
if(NOT executable)
|
|
idf_die("Binary target '${binary}' is missing 'EXECUTABLE_TARGET' property.")
|
|
endif()
|
|
set_target_properties(${ARG_TARGET} PROPERTIES EXECUTABLE_TARGET ${executable})
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_flash_binary
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_flash_binary(<binary>
|
|
TARGET <target>
|
|
[NAME <name>]
|
|
[FLASH])
|
|
|
|
*binary[in]*
|
|
|
|
Binary image target for which to create flash target. The ``binary``
|
|
target is created by the :cmakev2:ref:`idf_build_binary` function or
|
|
:cmakev2:ref:`idf_sign_binary`.
|
|
|
|
*TARGET[in]*
|
|
|
|
The name of the flash target that will be created for the binary image.
|
|
|
|
*NAME[in,opt]*
|
|
|
|
An optional name to be used as a prefix for the file containing
|
|
arguments for esptool, and as a logical name for the binary in
|
|
``flasher_args.json``. If not specified, the name of the ``TARGET`` is
|
|
used.
|
|
|
|
*FLASH[in,opt]*
|
|
|
|
If specified, the binary will also be added to the global ``flash``
|
|
target.
|
|
|
|
Create a new flash target for ``binary`` using the name specified in the
|
|
``TARGET`` option. The file path of the binary image should be stored in
|
|
the ``BINARY_PATH`` property of the ``binary`` target.
|
|
#]]
|
|
function(idf_flash_binary binary)
|
|
set(options FLASH)
|
|
set(one_value TARGET NAME)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
if(NOT TARGET idf::esptool_py)
|
|
idf_die("The 'esptool_py' component is not available")
|
|
endif()
|
|
|
|
if(NOT CONFIG_APP_BUILD_GENERATE_BINARIES)
|
|
idf_die("Binary file generation is not enabled with 'CONFIG_APP_BUILD_GENERATE_BINARIES'")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_TARGET)
|
|
idf_die("TARGET option is required")
|
|
endif()
|
|
|
|
if(NOT DEFINED ARG_NAME)
|
|
set(ARG_NAME "${ARG_TARGET}")
|
|
endif()
|
|
|
|
idf_component_get_property(main_args esptool_py FLASH_ARGS)
|
|
idf_component_get_property(sub_args esptool_py FLASH_SUB_ARGS)
|
|
|
|
partition_table_get_partition_info(offset "--partition-boot-default" "offset")
|
|
get_target_property(binary_path ${binary} BINARY_PATH)
|
|
if(NOT binary_path)
|
|
idf_die("Binary target '${binary}' is missing 'BINARY_PATH' property.")
|
|
endif()
|
|
|
|
idf_build_get_property(build_dir BUILD_DIR)
|
|
get_filename_component(binary_path "${binary_path}" ABSOLUTE BASE_DIR "${build_dir}")
|
|
|
|
esptool_py_flash_target("${ARG_TARGET}" "${main_args}" "${sub_args}"
|
|
FILENAME_PREFIX "${ARG_NAME}")
|
|
esptool_py_flash_target_image("${ARG_TARGET}" "${ARG_NAME}" "${offset}" "${binary_path}")
|
|
add_dependencies("${ARG_TARGET}" "${binary}")
|
|
|
|
if(ARG_FLASH)
|
|
esptool_py_flash_target_image(flash ${ARG_NAME} "${offset}" "${binary_path}")
|
|
add_dependencies(flash "${binary}")
|
|
endif()
|
|
endfunction()
|
|
|
|
#[[api
|
|
.. cmakev2:function:: idf_check_binary_size
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_check_binary_size(<binary>)
|
|
|
|
*binary[in]*
|
|
|
|
Binary image target to which to add a partition size check. The
|
|
``binary`` target is created by the :cmakev2:ref:`idf_build_binary` or
|
|
:cmakev2:ref:`idf_sign_binary` function.
|
|
|
|
Ensure that the generated binary image fits within the smallest available
|
|
application partition. The file path of the binary image should be stored
|
|
in the ``BINARY_PATH`` property of the ``binary`` target.
|
|
#]]
|
|
function(idf_check_binary_size binary)
|
|
if(NOT CONFIG_APP_BUILD_TYPE_APP_2NDBOOT)
|
|
return()
|
|
endif()
|
|
|
|
get_target_property(binary_path ${binary} BINARY_PATH)
|
|
if(NOT binary_path)
|
|
idf_die("Binary target '${binary}' is missing 'BINARY_PATH' property.")
|
|
endif()
|
|
|
|
partition_table_add_check_size_target("${binary}_check_size"
|
|
DEPENDS "${binary_path}"
|
|
BINARY_PATH "${binary_path}"
|
|
PARTITION_TYPE app)
|
|
add_dependencies("${binary}" "${binary}_check_size")
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_check_bootloader_size
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_check_bootloader_size(<binary>)
|
|
|
|
*binary[in]*
|
|
|
|
Binary image target to which to add a bootloader size check. The
|
|
``binary`` target is created by the :cmakev2:ref:`idf_build_binary` or
|
|
:cmakev2:ref:`idf_sign_binary` function.
|
|
|
|
Ensure that the bootloader binary image does not overlap the partition
|
|
table. The file path of the binary image should be stored in the
|
|
``BINARY_PATH`` property of the ``binary`` target.
|
|
#]]
|
|
function(idf_check_bootloader_size binary)
|
|
get_target_property(binary_path ${binary} BINARY_PATH)
|
|
if(NOT binary_path)
|
|
idf_die("Binary target '${binary}' is missing 'BINARY_PATH' property.")
|
|
endif()
|
|
|
|
partition_table_add_check_bootloader_size_target("${binary}_check_size"
|
|
DEPENDS "${binary_path}"
|
|
BOOTLOADER_BINARY_PATH "${binary_path}")
|
|
add_dependencies("${binary}" "${binary}_check_size")
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_check_binary_signed
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_check_binary_signed(<binary>)
|
|
|
|
*binary[in]*
|
|
|
|
Binary image target to which to add a partition size check. The
|
|
``binary`` target is created by the ``idf_build_binary``.
|
|
|
|
If the binary is not signed and signed applications are required with
|
|
CONFIG_SECURE_SIGNED_APPS, print a note indicating that the binary needs to
|
|
be signed manually. This situation can occur if the binary images are not
|
|
signed during the build because CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES is
|
|
not set.
|
|
#]]
|
|
function(idf_check_binary_signed binary)
|
|
if(NOT CONFIG_SECURE_SIGNED_APPS)
|
|
# Signed binaries are not required.
|
|
return()
|
|
endif()
|
|
|
|
if(CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES)
|
|
# Binary should be already signed with idf_sign_binary.
|
|
return()
|
|
endif()
|
|
|
|
get_target_property(binary_path ${binary} BINARY_PATH)
|
|
if(NOT binary_path)
|
|
idf_die("Binary target '${binary}' is missing 'BINARY_PATH' property.")
|
|
endif()
|
|
|
|
if(CONFIG_SECURE_SIGNED_APPS_ECDSA_SCHEME)
|
|
set(secure_boot_version "1")
|
|
elseif(CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME OR CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME)
|
|
set(secure_boot_version "2")
|
|
endif()
|
|
|
|
idf_component_get_property(espsecure_py_cmd esptool_py ESPSECUREPY_CMD)
|
|
string(REPLACE ";" " " espsecure_py_cmd "${espsecure_py_cmd}")
|
|
get_filename_component(binary_name "${binary_path}" NAME)
|
|
|
|
add_custom_command(TARGET "${binary}" POST_BUILD
|
|
COMMAND ${CMAKE_COMMAND} -E echo
|
|
"Binary '${binary_name}' not signed. Sign it before flashing with:"
|
|
COMMAND ${CMAKE_COMMAND} -E echo
|
|
"${espsecure_py_cmd} sign-data --keyfile KEYFILE --version ${secure_boot_version}"
|
|
"${binary_path}"
|
|
VERBATIM)
|
|
endfunction()
|
|
|
|
#[[
|
|
.. cmakev2:function:: idf_build_generate_depgraph
|
|
|
|
.. code-block:: cmake
|
|
|
|
idf_build_generate_depgraph(<executable>
|
|
[OUTPUT_FILE <file>])
|
|
|
|
*executable[in]*
|
|
|
|
Executable target for which to generate a dependency graph.
|
|
|
|
*OUTPUT_FILE[in,opt]*
|
|
|
|
Optional output file path for storing the dependency graph. If not
|
|
provided, the default path ``<build>/component_deps.dot`` is used.
|
|
|
|
Generate dependency graph for the specified ``executable`` and store it in
|
|
the specified ``OUTPUT_FILE``. If no ``OUTPUT_FILE`` is provided, the
|
|
default location ``<build>/component_deps.dot`` will be used.
|
|
#]]
|
|
function(idf_build_generate_depgraph executable)
|
|
set(options)
|
|
set(one_value OUTPUT_FILE)
|
|
set(multi_value)
|
|
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
|
|
|
|
idf_build_get_property(depgraph_enabled __BUILD_COMPONENT_DEPGRAPH_ENABLED)
|
|
if(NOT depgraph_enabled)
|
|
return()
|
|
endif()
|
|
|
|
__get_executable_library_or_die(TARGET "${executable}" OUTPUT library)
|
|
|
|
idf_build_set_property(__BUILD_COMPONENT_DEPGRAPH "")
|
|
idf_build_get_property(common_reqs __COMPONENT_REQUIRES_COMMON)
|
|
|
|
idf_library_get_property(build_components "${library}" LIBRARY_COMPONENTS_LINKED)
|
|
|
|
foreach(component_name IN LISTS build_components)
|
|
idf_component_get_property(reqs "${component_name}" REQUIRES)
|
|
idf_component_get_property(component_format "${component_name}" COMPONENT_FORMAT)
|
|
if("${component_format}" STREQUAL "CMAKEV1")
|
|
# For cmakev1 components, also include commonly required
|
|
# components.
|
|
list(APPEND reqs ${common_reqs})
|
|
endif()
|
|
|
|
foreach(req IN LISTS reqs)
|
|
depgraph_add_edge(${component_name} ${req} REQUIRES)
|
|
endforeach()
|
|
|
|
idf_component_get_property(priv_reqs "${component_name}" PRIV_REQUIRES)
|
|
foreach(priv_req IN LISTS priv_reqs)
|
|
depgraph_add_edge(${component_name} ${priv_req} PRIV_REQUIRES)
|
|
endforeach()
|
|
endforeach()
|
|
|
|
if(NOT DEFINED ARG_OUTPUT_FILE)
|
|
idf_build_get_property(build_dir BUILD_DIR)
|
|
set(ARG_OUTPUT_FILE "${build_dir}/component_deps.dot")
|
|
endif()
|
|
|
|
depgraph_generate("${ARG_OUTPUT_FILE}")
|
|
endfunction()
|