From 431ee5c0587bea32ccbdfb7bb490790eff36df69 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 29 Oct 2024 22:29:22 +0100 Subject: [PATCH] refactor(cmake): collect libraries from ELF, don't depend on idf_build_target --- tools/cmake/build.cmake | 3 + tools/cmake/component.cmake | 1 - tools/cmake/ldgen.cmake | 140 ++++++++++++++++++++++++++++++++---- 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/tools/cmake/build.cmake b/tools/cmake/build.cmake index f9d97d50b7..b9a3ebd967 100644 --- a/tools/cmake/build.cmake +++ b/tools/cmake/build.cmake @@ -712,6 +712,9 @@ endmacro() # files used for linking, targets which should execute before creating the specified executable, # generating additional binary files, generating files related to flashing, etc.) function(idf_build_executable elf) + # Create a target for linker script generation for this executable + __ldgen_create_target(${elf}) + # Set additional link flags for the executable idf_build_get_property(link_options LINK_OPTIONS) set_property(TARGET ${elf} APPEND PROPERTY LINK_OPTIONS "${link_options}") diff --git a/tools/cmake/component.cmake b/tools/cmake/component.cmake index ebc3aa569c..653533d3e9 100644 --- a/tools/cmake/component.cmake +++ b/tools/cmake/component.cmake @@ -494,7 +494,6 @@ function(idf_component_register) __component_add_include_dirs(${component_lib} "${__PRIV_INCLUDE_DIRS}" PRIVATE) __component_add_include_dirs(${component_lib} "${config_dir}" PUBLIC) set_target_properties(${component_lib} PROPERTIES OUTPUT_NAME ${COMPONENT_NAME} LINKER_LANGUAGE C) - __ldgen_add_component(${component_lib}) else() add_library(${component_lib} INTERFACE) __component_set_property(${component_target} COMPONENT_TYPE CONFIG_ONLY) diff --git a/tools/cmake/ldgen.cmake b/tools/cmake/ldgen.cmake index ffb3f4504f..4728177a5e 100644 --- a/tools/cmake/ldgen.cmake +++ b/tools/cmake/ldgen.cmake @@ -15,25 +15,134 @@ function(__ldgen_add_fragment_files fragment_files) idf_build_set_property(__LDGEN_FRAGMENT_FILES "${_fragment_files}" APPEND) endfunction() -# __ldgen_add_component -# -# Generate sections info for specified target to be used in linker script generation -function(__ldgen_add_component component_lib) - idf_build_set_property(__LDGEN_LIBRARIES "$" APPEND) - idf_build_set_property(__LDGEN_DEPENDS ${component_lib} APPEND) -endfunction() - # __ldgen_process_template # # Passes a linker script template to the linker script generation tool for -# processing +# processing. This internal function is called from the public "target_linker_script" function. +# It simply records the template and output file names. The actual linker script generation +# target is created later by the __ldgen_create_target function. +# function(__ldgen_process_template template output) + idf_build_set_property(__LDGEN_TEMPLATES "${template}" APPEND) + idf_build_set_property(__LDGEN_OUTPUTS "${output}" APPEND) +endfunction() + +# __ldgen_get_lib_deps_of_target +# +# Helper function to get the list of libraries that a target depends on, +# recursively, and append it to the out_list. The 'target' argument is +# typically an executable. +# +function(__ldgen_get_lib_deps_of_target target out_list_var) + set(out_list ${${out_list_var}}) + if(target IN_LIST out_list) + # This target is already in the list, meaning that it has been + # processed already. Bail out. + return() + endif() + + # The target is not in the list yet. Add it, and then add all its dependencies. + list(APPEND out_list ${target}) + set(${out_list_var} ${out_list} PARENT_SCOPE) + + get_target_property(target_deps ${target} LINK_LIBRARIES) + if(NOT target_deps) + # This target has no dependencies, nothing to do. + return() + endif() + + foreach(dep ${target_deps}) + if(dep IN_LIST out_list) + # This dependency has already been processed, skip it. + continue() + endif() + + if(NOT TARGET ${dep}) + # LINK_LIBRARIES may contain various non-library-related linker flags + # (-u, -L, -l, etc.), skip them. + if(dep MATCHES "^-" OR dep MATCHES "^\:\:") + continue() + endif() + + # If the dependency is not a target, it may be a library. Add it to the list. + list(APPEND out_list ${dep}) + else() + # Recursively add the dependencies of this target. + __ldgen_get_lib_deps_of_target(${dep} out_list) + endif() + endforeach() + + set(${out_list_var} ${out_list} PARENT_SCOPE) +endfunction() + + +# __ldgen_create_target +# +# Internal function which creates a custom target for the linker script generation tool. +# This function is called from the public idf_build_executable function. +# +function(__ldgen_create_target exe_target) idf_build_get_property(idf_target IDF_TARGET) idf_build_get_property(idf_path IDF_PATH) + idf_build_get_property(templates __LDGEN_TEMPLATES) + idf_build_get_property(outputs __LDGEN_OUTPUTS) + + if(NOT templates) + # No templates were passed to ldgen, nothing to do. + return() + endif() + + list(LENGTH templates num_templates) + if(NOT num_templates EQUAL 1) + # This limitation can be removed, if necessary, by looping over the list of templates + # and creating a target for each one of them. + # However, this is not needed in IDF for now, hence the simpler implementation. + message(FATAL_ERROR "Only one template file can be passed to __ldgen_process, " + "got ${num_templates}: ${templates}") + endif() + + list(GET templates 0 template) + list(GET outputs 0 output) + + # Collect all the libraries that the executable depends on, recursively. + # This is needed to pass the list of libraries to the linker script generator tool. + set(ldgen_libraries) + __ldgen_get_lib_deps_of_target(${exe_target} ldgen_libraries) + list(REMOVE_ITEM ldgen_libraries ${exe_target}) + set(ldgen_deps) + foreach(lib ${ldgen_libraries}) + if(TARGET ${lib}) + get_target_property(lib_type ${lib} TYPE) + if(lib_type STREQUAL "INTERFACE_LIBRARY") + continue() + endif() + list(APPEND ldgen_libraries_expr "$") + list(APPEND ldgen_deps ${lib}) + else() + # Here we have two cases: + # + # 1. ${lib} is actually a target, but the target is outside the current scope. + # This is the case for imported library targets (such as those added by + # add_prebuilt_library), since by default imported targets are not + # visible outside the directory where they are defined, unless they are + # marked as GLOBAL. + # This case covers many (but not all) of IDF's prebuilt libraries. + # + # 2. ${lib} is the name of a library, which the linker can find in its built-in + # or specified search paths. + # This is the case for toolchain libraries (m, c, gcc, stdc++) as well + # as for some prebuilt libraries which have been added using `-lname -Lpath` + # style flags. + # + # If we can successfully find the absolute path of each such library, this + # will allow us to pass them to ldgen, enabling us to place functions from + # prebuilt libraries to specific sections in the linker script (IDF-12049) + endif() + endforeach() + list(JOIN ldgen_libraries_expr "\n" ldgen_libraries_str) idf_build_get_property(build_dir BUILD_DIR) - idf_build_get_property(ldgen_libraries __LDGEN_LIBRARIES GENERATOR_EXPRESSION) - file(GENERATE OUTPUT ${build_dir}/ldgen_libraries.in CONTENT $) + file(WRITE ${build_dir}/ldgen_libraries.in "${ldgen_libraries_str}") file(GENERATE OUTPUT ${build_dir}/ldgen_libraries INPUT ${build_dir}/ldgen_libraries.in) set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" @@ -42,7 +151,7 @@ function(__ldgen_process_template template output) "${build_dir}/ldgen_libraries") idf_build_get_property(ldgen_fragment_files __LDGEN_FRAGMENT_FILES GENERATOR_EXPRESSION) - idf_build_get_property(ldgen_depends __LDGEN_DEPENDS GENERATOR_EXPRESSION) + # Create command to invoke the linker script generator tool. idf_build_get_property(sdkconfig SDKCONFIG) idf_build_get_property(root_kconfig __ROOT_KCONFIG) @@ -69,12 +178,15 @@ function(__ldgen_process_template template output) --libraries-file "${build_dir}/ldgen_libraries" --objdump "${CMAKE_OBJDUMP}" ${ldgen_check} - DEPENDS ${template} ${ldgen_fragment_files} ${ldgen_depends} ${SDKCONFIG} + DEPENDS ${template} ${ldgen_fragment_files} ${ldgen_deps} ${SDKCONFIG} VERBATIM ) + # The executable depends on the output of the ldgen command. get_filename_component(_name ${output} NAME) add_custom_target(__ldgen_output_${_name} DEPENDS ${output}) - add_dependencies(__idf_build_target __ldgen_output_${_name}) + add_dependencies(${exe_target} __ldgen_output_${_name}) + + # Add the output of the ldgen command to the list of link dependencies. idf_build_set_property(__LINK_DEPENDS ${output} APPEND) endfunction()