feat(cmakev2/ldgen): add ldgen integration

Integrate the ldgen into cmakev2. With this change, it becomes possible
to actually link the project executables.

In cmakev2, the handling of linker scripts is deferred to
idf_build_library, unlike in cmakev1, where linker scripts were added
and generated during the target_linker_script call. In cmakev2, the
target_linker_script only adds the linker scripts and templates, along
with the output filenames for the linker scripts generated from the
templates, to the component property. When idf_build_library is called
and all the requested components are included, it uses the
__get_target_dependencies function to obtain all transitively linked
targets to the library interface target. These targets are mapped to the
components, and the LIBRARY_COMPONENTS_LINKED library property is set.
It contains all components linked to the library interface target. The
components from LIBRARY_COMPONENTS_LINKED are used to collect linker
fragments and linker scripts utilized in the library. Additionally, all
targets transitively linked to the library are used to identify archive
files used in the library. This includes component archives and archives
added with the add_prebuilt_library function. The archives and
ldfragments related to the components linked to the library are used
when ldgen generates the linker scripts from templates.

The linker scripts, both static and generated by ldgen, are added to the
library interface link options and INTERFACE_LINK_DEPENDS property. For
generated linker scripts, a custom target is created and added as a
dependency for the library interface to ensure they are generated before
the link.

The difference compared to cmakev1 is that the generated linker scripts,
currently only sections.ld, are not global in the project but are
generated per library. This means there might be multiple versions of
sections.ld depending on the components included in the library. For
example, a component like esp_system may be linked to multiple library
interface targets, each with a different set of components. This results
in different sets of fragment files and library archives and different
versions of the sections.ld linker script. This should ensure proper
dependencies between targets. In other words, if a component changes its
linker fragment, only executables linked to libraries using this
component should be re-linked. As a consequence of this approach, the
generated linker scripts for different libraries need to have different
names or be stored in different directories to avoid overwriting the
linker script for one library with the linker script for another library
using the same component. This is handled with a suffix, which is based
on the library interface target name and appended to the generated
linker script. So, for example, there is no sections.ld, but instead
sections.ld_fatfs_lib or sections.ld_hello_world_lib. As a next step, we
can add a DEFAULT option to idf_build_library and avoid adding the
suffix for the default library.

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
Frantisek Hrbata
2025-08-25 14:15:30 +02:00
parent 8439bc1fa2
commit cbd2482e00
5 changed files with 221 additions and 14 deletions
+89 -6
View File
@@ -280,22 +280,105 @@ function(idf_build_library)
target_link_libraries("${ARG_INTERFACE}" INTERFACE "${component_interface}")
endforeach()
# Identify the components linked to the library by obtaining all targets
# that are transitively linked to the library and mapping these targets to
# components.
# Get all targets transitively linked to the library interface target.
__get_target_dependencies(TARGET "${ARG_INTERFACE}" OUTPUT dependencies)
set(dep_component_interfaces)
# 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 interfaces in
# LIBRARY_COMPONENTS_LINKED property.
set(component_interfaces_linked)
foreach(dep IN LISTS dependencies)
__get_component_interface(COMPONENT "${dep}" OUTPUT component_interface)
if(NOT component_interface)
continue()
endif()
if("${component_interface}" IN_LIST dep_component_interfaces)
if("${component_interface}" IN_LIST component_interfaces_linked)
continue()
endif()
list(APPEND dep_component_interfaces "${component_interface}")
list(APPEND component_interfaces_linked "${component_interface}")
idf_component_get_property(component_name "${component_interface}" COMPONENT_NAME)
idf_library_set_property("${ARG_INTERFACE}" LIBRARY_COMPONENTS_LINKED "${component_name}" 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.
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("${ARG_INTERFACE}" __LDGEN_FRAGMENT_FILES "${ldfragments}" 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 "${ARG_INTERFACE}" __LDGEN_DEPENDS)
if(NOT "${dep}" IN_LIST ldgen_depends)
idf_library_set_property("${ARG_INTERFACE}" __LDGEN_LIBRARIES "$<TARGET_FILE:${dep}>" APPEND)
idf_library_set_property("${ARG_INTERFACE}" __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(REGEX REPLACE "[^A-Za-z0-9_]" "_" suffix "_${ARG_INTERFACE}")
foreach(component_interface IN LISTS component_interfaces_linked)
# Generate linker scripts from templates.
# LINKER_SCRIPTS_TEMPLATE and LINKER_SCRIPTS_GENERATED are parallel
# lists. The first holds the template linker script path, and the
# second holds the generated linker script path.
idf_component_get_property(template_scripts "${component_interface}" LINKER_SCRIPTS_TEMPLATE)
idf_component_get_property(generated_scripts "${component_interface}" LINKER_SCRIPTS_GENERATED)
set(scripts)
foreach(template script IN ZIP_LISTS template_scripts generated_scripts)
set(script "${script}${suffix}")
__ldgen_process_template(LIBRARY "${ARG_INTERFACE}"
TEMPLATE "${template}"
SUFFIX "${suffix}"
OUTPUT "${script}")
list(APPEND scripts "${script}")
# 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(REGEX REPLACE "[^A-Za-z0-9_]" "_" basename "${basename}")
add_custom_target(__ldgen_output_${basename} DEPENDS "${script}")
add_dependencies("${ARG_INTERFACE}" __ldgen_output_${basename})
endforeach()
# Add linker scripts.
idf_component_get_property(scripts_static "${component_interface}" LINKER_SCRIPTS_STATIC)
list(PREPEND scripts "${scripts_static}")
foreach(script IN LISTS scripts)
get_filename_component(script_dir "${script}" DIRECTORY)
get_filename_component(script_name "${script}" NAME)
# Add linker script directory to the linker search path.
target_link_directories("${ARG_INTERFACE}" INTERFACE "${script_dir}")
# Add linker script to link. Regarding the usage of SHELL, see
# https://cmake.org/cmake/help/latest/command/target_link_options.html#option-de-duplication
target_link_options("${ARG_INTERFACE}" INTERFACE "SHELL:-T ${script_name}")
# Add the linker script as a dependency to ensure the executable is
# re-linked if the script changes.
set_property(TARGET "${ARG_INTERFACE}" APPEND PROPERTY INTERFACE_LINK_DEPENDS "${script}")
endforeach()
endforeach()
endfunction()
+38 -2
View File
@@ -131,11 +131,47 @@ endfunction()
.. code-block:: cmake
target_linker_script(<target> <deptype> <scriptfile>...)
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)
# FIXME: This is just a placeholder without implementation.
# 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_STATIC ${scriptfile} APPEND)
endif()
endforeach()
endfunction()
#[[api
-6
View File
@@ -629,12 +629,6 @@ function(idf_component_include name)
target_add_binary_data(${COMPONENT_TARGET} "${file}" "TEXT")
endforeach()
idf_component_get_property(ldfragments "${component_name}" LDFRAGMENTS)
if(ldfragments)
# FIXME: Enable this once the ldgen integration is implemented.
# ldgen_add_fragment_files("${ldfragments}")
endif()
# Components for cmakev1 use the idf_component_register call and are
# managed in cmakev2 through a shim. This shim sets the COMPONENT_FORMAT
# property to CMAKEV1 and applies global compilation options and
+1
View File
@@ -22,6 +22,7 @@ include(build)
include(kconfig)
include(project)
include(compat)
include(ldgen)
include(GetGitRevisionDescription)
# For backward compatibility, since externalproject_add is used by
# project_include.cmake in the bootloader component. The ExternalProject
+93
View File
@@ -0,0 +1,93 @@
include_guard(GLOBAL)
set(one_value LIBRARY TEMPLATE SUFFIX OUTPUT)
#[[
__ldgen_process_template(LIBRARY <target>
TEMPLATE <template>
OUTPUT <output>
[SUFFIX <suffix>])
:TARGET[in]: A library interface target that already has the properties
__LDGEN_LIBRARIES, __LDGEN_FRAGMENT_FILES, and __LDGEN_DEPENDS
set. These properties include libraries and fragment files for
components linked to the library interface.
:TEMPLATE[in]: Linker script template to process.
:OUTPUT[in]: The output filename where the generated linker script will be
saved.
:SUFFIX[in,opt]: Optional suffix for the generated files containing the list
of linked library archives.
Pass the linker script ``TEMPLATE`` to the linker script generation tool,
ldgen, for processing. The processed linker script is stored in the file
specified by the ``OUTPUT`` option.
This function can be called multiple times to generate multiple linker
scripts from a single template. For example, a component like esp_system may
be linked to multiple library interface targets, each with a different set
of components. This results in different sets of fragment files and library
archives. The suffix is used to ensure that the files listing the archives
are not overwritten.
#]]
function(__ldgen_process_template)
set(options)
set(one_value LIBRARY TEMPLATE SUFFIX 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_TEMPLATE)
idf_die("TEMPLATE option is required")
endif()
if(NOT DEFINED ARG_OUTPUT)
idf_die("OUTPUT option is required")
endif()
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(root_kconfig __ROOT_KCONFIG)
idf_build_get_property(python PYTHON)
idf_build_get_property(config_env_path __CONFIG_ENV_PATH)
idf_library_get_property(ldgen_libraries "${ARG_LIBRARY}" __LDGEN_LIBRARIES)
idf_library_get_property(ldgen_fragment_files "${ARG_LIBRARY}" __LDGEN_FRAGMENT_FILES)
idf_library_get_property(ldgen_depends "${ARG_LIBRARY}" __LDGEN_DEPENDS)
list(JOIN ldgen_libraries "\n" ldgen_libraries)
file(WRITE "${build_dir}/ldgen_libraries.in${ARG_SUFFIX}" "${ldgen_libraries}")
file(GENERATE OUTPUT "${build_dir}/ldgen_libraries${ARG_SUFFIX}"
INPUT "${build_dir}/ldgen_libraries.in${ARG_SUFFIX}")
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
APPEND PROPERTY ADDITIONAL_CLEAN_FILES
"${build_dir}/ldgen_libraries.in${ARG_SUFFIX}"
"${build_dir}/ldgen_libraries${ARG_SUFFIX}")
if($ENV{LDGEN_CHECK_MAPPING})
set(ldgen_check "--check-mapping"
"--check-mapping-exceptions" "${idf_path}/tools/ci/check_ldgen_mapping_exceptions.txt")
idf_msg("Mapping check enabled in ldgen")
endif()
add_custom_command(
OUTPUT "${ARG_OUTPUT}"
COMMAND ${python} "${idf_path}/tools/ldgen/ldgen.py"
--config "${sdkconfig}"
--fragments-list "${ldgen_fragment_files}"
--input "${ARG_TEMPLATE}"
--output "${ARG_OUTPUT}"
--kconfig "${root_kconfig}"
--env-file "${config_env_path}"
--env "IDF_BUILD_V2=y"
--libraries-file "${build_dir}/ldgen_libraries${ARG_SUFFIX}"
--objdump "${CMAKE_OBJDUMP}"
${ldgen_check}
DEPENDS ${ARG_TEMPLATE} ${ldgen_fragment_files} ${ldgen_depends} ${sdkconfig}
VERBATIM
)
endfunction()