From 72e96e4907b12e76cffd2f757b132605060e2d3f Mon Sep 17 00:00:00 2001 From: Frantisek Hrbata Date: Sun, 5 Oct 2025 11:44:53 +0200 Subject: [PATCH] fix(cmakev2/component): fix component library link with whole archive The current approach involves wrapping the library target within the library interface target with the whole-archive flags, such as for the GCC linker. However, this does not work as expected because the library target is expanded, and the whole-archive flags are also applied to other library targets that the wrapped library target depends on. IOW the whole-archive flags surround multiple archives, not just the one requrested. Generally, using linker flags like whole-archive in the library INTERFACE(INTERFACE_LINK_LIBRARIES) does not seem to work as CMake may perform deduplication and rearrange the flags [1]. The proof of concept used `CMAKE_LINK_LIBRARY_USING_` with the WHOLE_ARCHIVE feature, which was introduced in CMake 3.24 to specify how the library target should be linked. There are two issues with this. First, this feature is only available from CMake 3.24 onwards, while our minimum CMake version is set to 3.22. More importantly, all occurrences of a library on the link line are wrapped. For example, if a library like `vfs` appears multiple times on the link line due to dependencies, each occurrence will be wrapped with whole-archive, causing the linker to complain about multiple symbol definitions. Therefore, even though WHOLE_ARCHIVE is recommended for handling whole-archive linkage, it does not seem suitable for our purposes. This was overlooked in the PoC because only a simple testing component was used to check the WHOLE_ARCHIVE behavior. One way to address this issue is to adopt the same approach used in cmakev1, which involves specifying the whole-archive flags when linking component library targets to the final executable. In this case, CMake retains the flags without any alteration, unlike when the flags are specified in INTERFACE_LINK_LIBRARIES for the component interface targets. While this approach is feasible, it would alter the current logic, where we have a single library interface for the entire idf library that can be linked to the executable. This change would also complicate the direct use of the idf library, as it would no longer be possible to simply link it to the executable. Instead, the executable would need to correctly link the component libraries with the whole-archive flags. We could encapsulate this process within the idf_build_executable function, but projects that only use idf_build_library would need to implement the same solution. It appears possible to address this issue by using target_link_options instead. This allows to specify exactly what should appear on the link command line. One side effect of this approach is that the library appears on the link command line multiple times: first when used with target_link_options and second when used in target_link_libraries. However, this does not seem to pose a problem. The flags specified with target_link_options appear on the command line first, ensuring that the whole-archive is prioritized, and the component archive libraries may be repeated on the link line anyway due to dependencies. Essentially, the final link command line is very similar to the one from cmakev1. We may revisit this approach in the future, but for now, it seems to work as expected. [1] https://gitlab.kitware.com/cmake/cmake/-/issues/20078 [2] https://cmake.org/cmake/help/latest/variable/CMAKE_LINK_LIBRARY_USING_FEATURE.html [3] https://discourse.cmake.org/t/automatically-wrapping-a-static-library- in-whole-archive-no-whole-archive-when-used-during-linking/5883 Signed-off-by: Frantisek Hrbata --- tools/cmakev2/component.cmake | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/cmakev2/component.cmake b/tools/cmakev2/component.cmake index bdbb6a7e030..ced972449e9 100644 --- a/tools/cmakev2/component.cmake +++ b/tools/cmakev2/component.cmake @@ -913,11 +913,13 @@ function(idf_component_include name) if(whole_archive) idf_build_get_property(linker_type LINKER_TYPE) if(linker_type STREQUAL "GNU") - target_link_libraries("${component_interface}" INTERFACE - "-Wl,--whole-archive" "${component_real_target}" "-Wl,--no-whole-archive") + target_link_options("${component_interface}" INTERFACE + "SHELL:-Wl,--whole-archive $ -Wl,--no-whole-archive") + target_link_libraries("${component_interface}" INTERFACE "${component_real_target}") elseif(linker_type STREQUAL "Darwin") - target_link_libraries("${component_interface}" INTERFACE - "-Wl,-force_load" "${component_real_target}") + target_link_options("${component_interface}" INTERFACE + "SHELL:-Wl,-force_load $") + target_link_libraries("${component_interface}" INTERFACE "${component_real_target}") endif() else() target_link_libraries("${component_interface}" INTERFACE "${component_real_target}")