diff --git a/conan/tools/cmake/cmakedeps/templates/macros.py b/conan/tools/cmake/cmakedeps/templates/macros.py index 9d3965c0ef4..e5f3f3a22eb 100644 --- a/conan/tools/cmake/cmakedeps/templates/macros.py +++ b/conan/tools/cmake/cmakedeps/templates/macros.py @@ -41,7 +41,7 @@ def template(self): endif() endmacro() - function(conan_package_library_targets libraries package_libdir deps out_libraries out_libraries_target config_suffix package_name) + function(conan_package_library_targets libraries package_libdir deps out_libraries_target config package_name) set(_out_libraries "") set(_out_libraries_target "") set(_CONAN_ACTUAL_TARGETS "") @@ -51,20 +51,20 @@ def template(self): NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) if(CONAN_FOUND_LIBRARY) message(VERBOSE "Conan: Library ${_LIBRARY_NAME} found ${CONAN_FOUND_LIBRARY}") - list(APPEND _out_libraries ${CONAN_FOUND_LIBRARY}) # Create a micro-target for each lib/a found # Allow only some characters for the target name string(REGEX REPLACE "[^A-Za-z0-9.+_-]" "_" _LIBRARY_NAME ${_LIBRARY_NAME}) - set(_LIB_NAME CONAN_LIB::${package_name}_${_LIBRARY_NAME}${config_suffix}) + set(_LIB_NAME CONAN_LIB::${package_name}_${_LIBRARY_NAME}) if(NOT TARGET ${_LIB_NAME}) # Create a micro-target for each lib/a found add_library(${_LIB_NAME} UNKNOWN IMPORTED) - set_target_properties(${_LIB_NAME} PROPERTIES IMPORTED_LOCATION ${CONAN_FOUND_LIBRARY}) - list(APPEND _CONAN_ACTUAL_TARGETS ${_LIB_NAME}) - else() - message(VERBOSE "Conan: Skipping already existing target: ${_LIB_NAME}") endif() + # Enable configuration only when it is available to avoid missing configs + set_property(TARGET ${_LIB_NAME} APPEND PROPERTY IMPORTED_CONFIGURATIONS ${config}) + # Link library file + set_target_properties(${_LIB_NAME} PROPERTIES IMPORTED_LOCATION_${config} ${CONAN_FOUND_LIBRARY}) + list(APPEND _CONAN_ACTUAL_TARGETS ${_LIB_NAME}) list(APPEND _out_libraries_target ${_LIB_NAME}) message(VERBOSE "Conan: Found: ${CONAN_FOUND_LIBRARY}") else() @@ -76,10 +76,15 @@ def template(self): # Add all dependencies to all targets string(REPLACE " " ";" deps_list "${deps}") foreach(_CONAN_ACTUAL_TARGET ${_CONAN_ACTUAL_TARGETS}) - set_property(TARGET ${_CONAN_ACTUAL_TARGET} PROPERTY INTERFACE_LINK_LIBRARIES "${deps_list}" APPEND) + set_property(TARGET ${_CONAN_ACTUAL_TARGET} PROPERTY INTERFACE_LINK_LIBRARIES $<$:${deps_list}> APPEND) + endforeach() + + # ONLY FOR DEBUGGING PURPOSES + foreach(_CONAN_ACTUAL_TARGET ${_CONAN_ACTUAL_TARGETS}) + get_target_property(linked_libs ${_CONAN_ACTUAL_TARGET} INTERFACE_LINK_LIBRARIES) + message(VERBOSE "Target Properties: ${_CONAN_ACTUAL_TARGET} INTERFACE_LINK_LIBRARIES ='${linked_libs}'") endforeach() - set(${out_libraries} ${_out_libraries} PARENT_SCOPE) set(${out_libraries_target} ${_out_libraries_target} PARENT_SCOPE) endfunction() diff --git a/conan/tools/cmake/cmakedeps/templates/target_configuration.py b/conan/tools/cmake/cmakedeps/templates/target_configuration.py index f969bdd253d..21bfa56ff35 100644 --- a/conan/tools/cmake/cmakedeps/templates/target_configuration.py +++ b/conan/tools/cmake/cmakedeps/templates/target_configuration.py @@ -31,6 +31,7 @@ def context(self): return {"pkg_name": self.pkg_name, "root_target_name": self.root_target_name, "config_suffix": self.config_suffix, + "config": self.configuration.upper(), "deps_targets_names": ";".join(deps_targets_names), "components_names": components_names, "configuration": self.cmakedeps.configuration, @@ -60,64 +61,44 @@ def template(self): conan_find_apple_frameworks({{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ '${' }}{{ pkg_name }}_FRAMEWORKS{{ config_suffix }}}" "{{ '${' }}{{ pkg_name }}_FRAMEWORK_DIRS{{ config_suffix }}}") # Gather all the libraries that should be linked to the targets (do not touch existing variables) - set(_{{ pkg_name }}_DEPENDENCIES{{ config_suffix }} "{{ '${' }}{{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }}} {{ '${' }}{{ pkg_name }}_SYSTEM_LIBS{{ config_suffix }}} {{ deps_targets_names }}") + # FIXME: Why is needed to pass all the dependencies to link with the micro-targets? + set(_{{ pkg_name }}_DEPENDENCIES{{ config_suffix }} "{{ deps_targets_names }}") set({{ pkg_name }}_LIBRARIES_TARGETS{{ config_suffix }} "") # Will be filled later - set({{ pkg_name }}_LIBRARIES{{ config_suffix }} "") # Will be filled later conan_package_library_targets("{{ '${' }}{{ pkg_name }}_LIBS{{ config_suffix }}}" # libraries "{{ '${' }}{{ pkg_name }}_LIB_DIRS{{ config_suffix }}}" # package_libdir "{{ '${' }}_{{ pkg_name }}_DEPENDENCIES{{ config_suffix }}}" # deps - {{ pkg_name }}_LIBRARIES{{ config_suffix }} # out_libraries - {{ pkg_name }}_LIBRARIES_TARGETS{{ config_suffix }} # out_libraries_targets - "{{ config_suffix }}" # config_suffix + {{ pkg_name }}_LIBRARIES_TARGETS # out_libraries_targets + "{{ config }}" # DEBUG, RELEASE ... "{{ pkg_name }}") # package_name + # The XXXX_LIBRARIES_RELEASE/DEBUG is used for the module (FindXXX.cmake) foreach(_FRAMEWORK {{ '${' }}{{ pkg_name }}_FRAMEWORKS_FOUND{{ config_suffix }}}) - list(APPEND {{ pkg_name }}_LIBRARIES_TARGETS{{ config_suffix }} ${_FRAMEWORK}) list(APPEND {{ pkg_name }}_LIBRARIES{{ config_suffix }} ${_FRAMEWORK}) endforeach() foreach(_SYSTEM_LIB {{ '${' }}{{ pkg_name }}_SYSTEM_LIBS{{ config_suffix }}}) - list(APPEND {{ pkg_name }}_LIBRARIES_TARGETS{{ config_suffix }} ${_SYSTEM_LIB}) list(APPEND {{ pkg_name }}_LIBRARIES{{ config_suffix }} ${_SYSTEM_LIB}) endforeach() # We need to add our requirements too - set({{ pkg_name }}_LIBRARIES_TARGETS{{ config_suffix }} {{ '"${' }}{{ pkg_name }}_LIBRARIES_TARGETS{{ config_suffix }}{{ '};' }}{{ deps_targets_names }}") - set({{ pkg_name }}_LIBRARIES{{ config_suffix }} {{ '"${' }}{{ pkg_name }}_LIBRARIES{{ config_suffix }}{{ '};' }}{{ deps_targets_names }}") + set({{ pkg_name }}_LIBRARIES{{ config_suffix }} "") + list(APPEND {{ pkg_name }}_LIBRARIES{{ config_suffix }} {{ deps_targets_names }}) # FIXME: What is the result of this for multi-config? All configs adding themselves to path? set(CMAKE_MODULE_PATH {{ '${' }}{{ pkg_name }}_BUILD_DIRS{{ config_suffix }}} {{ '${' }}CMAKE_MODULE_PATH}) set(CMAKE_PREFIX_PATH {{ '${' }}{{ pkg_name }}_BUILD_DIRS{{ config_suffix }}} {{ '${' }}CMAKE_PREFIX_PATH}) - {%- for comp_variable_name, comp_target_name in components_names %} - - ########## COMPONENT {{ comp_target_name }} FIND LIBRARIES & FRAMEWORKS / DYNAMIC VARS ############# - - set({{ pkg_name }}_{{ comp_variable_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "") - conan_find_apple_frameworks({{ pkg_name }}_{{ comp_variable_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORKS'+config_suffix+'}' }}" "{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORK_DIRS'+config_suffix+'}' }}") - - set({{ pkg_name }}_{{ comp_variable_name }}_LIB_TARGETS{{ config_suffix }} "") - set({{ pkg_name }}_{{ comp_variable_name }}_NOT_USED{{ config_suffix }} "") - set({{ pkg_name }}_{{ comp_variable_name }}_LIBS_FRAMEWORKS_DEPS{{ config_suffix }} {{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORKS_FOUND'+config_suffix+'}' }} {{ '${'+pkg_name+'_'+comp_variable_name+'_SYSTEM_LIBS'+config_suffix+'}' }} {{ '${'+pkg_name+'_'+comp_variable_name+'_DEPENDENCIES'+config_suffix+'}' }}) - conan_package_library_targets("{{ '${'+pkg_name+'_'+comp_variable_name+'_LIBS'+config_suffix+'}' }}" - "{{ '${'+pkg_name+'_'+comp_variable_name+'_LIB_DIRS'+config_suffix+'}' }}" - "{{ '${'+pkg_name+'_'+comp_variable_name+'_LIBS_FRAMEWORKS_DEPS'+config_suffix+'}' }}" - {{ pkg_name }}_{{ comp_variable_name }}_NOT_USED{{ config_suffix }} - {{ pkg_name }}_{{ comp_variable_name }}_LIB_TARGETS{{ config_suffix }} - "{{ config_suffix }}" - "{{ pkg_name }}_{{ comp_variable_name }}") - - set({{ pkg_name }}_{{ comp_variable_name }}_LINK_LIBS{{ config_suffix }} {{ '${'+pkg_name+'_'+comp_variable_name+'_LIB_TARGETS'+config_suffix+'}' }} {{ '${'+pkg_name+'_'+comp_variable_name+'_LIBS_FRAMEWORKS_DEPS'+config_suffix+'}' }}) - {%- endfor %} - - {% if not components_names %} ########## GLOBAL TARGET PROPERTIES {{ configuration }} ######################################## set_property(TARGET {{root_target_name}} PROPERTY INTERFACE_LINK_LIBRARIES - $<$:${{'{'}}{{pkg_name}}_OBJECTS{{config_suffix}}} - ${{'{'}}{{pkg_name}}_LIBRARIES_TARGETS{{config_suffix}}}> APPEND) + $<$:${{'{'}}{{pkg_name}}_OBJECTS{{config_suffix}}}> + ${{'{'}}{{pkg_name}}_LIBRARIES_TARGETS} + $<$:${{'{'}}_{{pkg_name}}_DEPENDENCIES{{config_suffix}}}> + $<$:${{'{'}}{{pkg_name}}_FRAMEWORKS_FOUND{{config_suffix}}}> + $<$:${{'{'}}{{pkg_name}}_SYSTEM_LIBS{{config_suffix}}}> + APPEND) set_property(TARGET {{root_target_name}} PROPERTY INTERFACE_LINK_OPTIONS @@ -146,10 +127,30 @@ def template(self): {%- for comp_variable_name, comp_target_name in components_names %} - ########## COMPONENT {{ comp_target_name }} TARGET PROPERTIES ###################################### - set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_LINK_LIBRARIES - $<$:{{tvalue(pkg_name, comp_variable_name, 'OBJECTS', config_suffix)}} - {{tvalue(pkg_name, comp_variable_name, 'LINK_LIBS', config_suffix)}}> APPEND) + ########## COMPONENT {{ comp_target_name }} FIND LIBRARIES & FRAMEWORKS / DYNAMIC VARS ############# + + set({{ pkg_name }}_{{ comp_variable_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "") + conan_find_apple_frameworks({{ pkg_name }}_{{ comp_variable_name }}_FRAMEWORKS_FOUND{{ config_suffix }} "{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORKS'+config_suffix+'}' }}" "{{ '${'+pkg_name+'_'+comp_variable_name+'_FRAMEWORK_DIRS'+config_suffix+'}' }}") + + set({{ pkg_name }}_{{ comp_variable_name }}_LIBRARIES_TARGETS "") + + conan_package_library_targets("{{ '${'+pkg_name+'_'+comp_variable_name+'_LIBS'+config_suffix+'}' }}" + "{{ '${'+pkg_name+'_'+comp_variable_name+'_LIB_DIRS'+config_suffix+'}' }}" + "{{ '${'+pkg_name+'_'+comp_variable_name+'_DEPENDENCIES'+config_suffix+'}' }}" + {{ pkg_name }}_{{ comp_variable_name }}_LIBRARIES_TARGETS + "{{ config }}" # DEBUG, RELEASE... + "{{ pkg_name }}_{{ comp_variable_name }}") + + ########## COMPONENT {{ comp_target_name }} TARGET PROPERTIES ##################################### + set_property(TARGET {{comp_target_name}} + PROPERTY INTERFACE_LINK_LIBRARIES + $<$:${{'{'}}{{pkg_name}}_{{comp_variable_name}}_OBJECTS{{config_suffix}}}> + ${{'{'}}{{pkg_name}}_{{comp_variable_name}}_LIBRARIES_TARGETS} + $<$:${{'{'}}{{pkg_name}}_{{comp_variable_name}}_DEPENDENCIES{{config_suffix}}}> + $<$:${{'{'}}{{pkg_name}}_{{comp_variable_name}}_FRAMEWORKS_FOUND{{config_suffix}}}> + $<$:${{'{'}}{{pkg_name}}_{{comp_variable_name}}_SYSTEM_LIBS{{config_suffix}}}> + APPEND) + set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_LINK_OPTIONS $<$:{{tvalue(pkg_name, comp_variable_name, 'LINKER_FLAGS', config_suffix)}}> APPEND) set_property(TARGET {{ comp_target_name }} PROPERTY INTERFACE_INCLUDE_DIRECTORIES diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py index eb78c4fd2f5..4e4f7b9f7d3 100644 --- a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py +++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps.py @@ -153,13 +153,14 @@ def package_info(self): if build_type == "Release": assert "System libs release: %s" % library_name in client.out assert "Libraries to Link release: lib1" in client.out - target_libs = "$<$:;CONAN_LIB::Test_lib1_RELEASE;sys1;>" else: assert "System libs debug: %s" % library_name in client.out assert "Libraries to Link debug: lib1" in client.out - target_libs = "$<$:;CONAN_LIB::Test_lib1_DEBUG;sys1d;>" - assert "Target libs: %s" % target_libs in client.out + # FIXME: This assert is ugly, empty configs comes from empty _OBJECTS, FRAMEWORKS etc + target_libs = f"$<$:>;CONAN_LIB::Test_lib1;$<$" \ + f":>;$<$:>;$<$:{library_name}>" + assert target_libs in client.out @pytest.mark.tool_cmake diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py index e2919cf0d08..e2b4a0cdc23 100644 --- a/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py +++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_components.py @@ -204,7 +204,9 @@ def build(self): t.save({"conanfile.py": conanfile, "CMakeLists.txt": cmakelists}) t.run("create . --build missing -s build_type=Release") - assert 'component libs: $<$:;system_lib_component>' in t.out + # FIXME: This assert is ugly, empty configs comes from empty _OBJECTS, FRAMEWORKS etc + assert 'component libs: $<$:>;$<$:>;$<$:>' \ + ';$<$:system_lib_component>' in t.out assert ('component options: ' '$<$:' '$<$,SHARED_LIBRARY>:>;' diff --git a/conans/test/functional/toolchains/cmake/cmakedeps/test_link_order.py b/conans/test/functional/toolchains/cmake/cmakedeps/test_link_order.py index bbfb66c3b6d..07b16e74221 100644 --- a/conans/test/functional/toolchains/cmake/cmakedeps/test_link_order.py +++ b/conans/test/functional/toolchains/cmake/cmakedeps/test_link_order.py @@ -1,5 +1,6 @@ import os import platform +import re import textwrap import pytest @@ -257,12 +258,19 @@ def _get_link_order_from_cmake(content): def _get_link_order_from_xcode(content): libs = [] - start_key = '-headerpad_max_install_names",' + + # Find the right Release block in the XCode file + results = re.finditer('/\* Release \*/ = {', content) + for r in results: + release_section = content[r.start():].split("name = Release;", 1)[0] + if "-headerpad_max_install_names" in release_section: + break + else: + raise Exception("Cannot find the Release block linking the expected libraries") + + start_key = '-Wl,-headerpad_max_install_names' end_key = ');' - libs_content = content.split(start_key, 1)[1].split(end_key, 1)[0] - if libs_content == '"$(inherited)"': - # FIXME: Dirty hack, sometimes the library list is not at the 1 but at 2 - libs_content = content.split(start_key, 1)[2].split(end_key, 1)[0] + libs_content = release_section.split(start_key, 1)[1].split(end_key, 1)[0] libs_unstripped = libs_content.split(",") for lib in libs_unstripped: if ".a" in lib: @@ -303,7 +311,10 @@ def _run_and_get_lib_order(t, generator): if generator == "Xcode": t.run_command("cmake . -G Xcode -DCMAKE_VERBOSE_MAKEFILE:BOOL=True" " -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake") + # This is building by default the Debug configuration that contains nothing, so it works t.run_command("cmake --build .") + # This is building the release and fails because invented system libraries are missing + t.run_command("cmake --build . --config Release", assert_error=True) # Get the actual link order from the CMake call libs = _get_link_order_from_xcode(t.load(os.path.join('executable.xcodeproj', 'project.pbxproj'))) diff --git a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py index a9467294572..558c1c83fe5 100644 --- a/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py +++ b/conans/test/integration/toolchains/cmake/cmakedeps/test_cmakedeps.py @@ -104,14 +104,15 @@ def package_info(self): client.run("install hello/1.0@ -g CMakeDeps -s arch=x86_64 -s build_type=Release") with open(os.path.join(client.current_folder, "hello-Target-release.cmake")) as f: content = f.read() - assert """set_property(TARGET hello::say PROPERTY INTERFACE_LINK_LIBRARIES - $<$:${hello_hello_say_OBJECTS_RELEASE} - ${hello_hello_say_LINK_LIBS_RELEASE}> APPEND)""" in content + assert """set_property(TARGET hello::say + PROPERTY INTERFACE_LINK_LIBRARIES + $<$:${hello_hello_say_OBJECTS_RELEASE}> + ${hello_hello_say_LIBRARIES_TARGETS}""" in content # If there are componets, there is not a global cpp so this is not generated assert """set_property(TARGET hello::hello PROPERTY INTERFACE_LINK_LIBRARIES - $<$:${hello_OBJECTS_RELEASE} - ${hello_LIBRARIES_TARGETS_RELEASE}> APPEND)""" not in content + $<$:${hello_OBJECTS_RELEASE}> + ${hello_LIBRARIES_TARGETS}""" not in content # But the global target is linked with the targets from the components assert "target_link_libraries(hello::hello INTERFACE hello::say)" in content