Skip to content

Commit

Permalink
CMakeDeps. global target: from merging cppinfo to link component targ…
Browse files Browse the repository at this point in the history
…ets (#11673)

Changelog: Feature: Changed CMakeDeps generator so the global target made for a package with components is a target linked with the targets of the components, instead of a target made from merging cpp_info objects from the components.

Changelog: Fix: The cmake_build_module property can only be declared in the self.cpp_info, not in components, where will be ignored.
  • Loading branch information
jcar87 committed Jul 26, 2022
2 parents 8389812 + 183d0ab commit 0fb3d62
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 54 deletions.
14 changes: 13 additions & 1 deletion conan/tools/cmake/cmakedeps/templates/target_configuration.py
Expand Up @@ -112,7 +112,7 @@ def template(self):
{%- endfor %}
{% if not components_names %}
########## GLOBAL TARGET PROPERTIES {{ configuration }} ########################################
set_property(TARGET {{root_target_name}}
PROPERTY INTERFACE_LINK_LIBRARIES
Expand All @@ -139,6 +139,9 @@ def template(self):
PROPERTY INTERFACE_LINK_DIRECTORIES
$<$<CONFIG:{{configuration}}>:${{'{'}}{{pkg_name}}_LIB_DIRS{{config_suffix}}}> APPEND)
{%- endif %}
{%- else %}
########## COMPONENTS TARGET PROPERTIES {{ configuration }} ########################################
{%- for comp_variable_name, comp_target_name in components_names %}
Expand Down Expand Up @@ -168,6 +171,15 @@ def template(self):
{%- endfor %}
########## AGGREGATED GLOBAL TARGET WITH THE COMPONENTS #####################
{%- for comp_variable_name, comp_target_name in components_names %}
target_link_libraries({{root_target_name}} INTERFACE {{ comp_target_name }})
{%- endfor %}
{%- endif %}
""")

def get_declared_components_targets_names(self):
Expand Down
16 changes: 10 additions & 6 deletions conan/tools/cmake/cmakedeps/templates/target_data.py
Expand Up @@ -49,6 +49,7 @@ def context(self):
package_folder = package_folder.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')

return {"global_cpp": global_cpp,
"has_components": self.conanfile.cpp_info.has_components,
"pkg_name": self.pkg_name,
"file_name": self.file_name,
"package_folder": package_folder,
Expand Down Expand Up @@ -84,6 +85,10 @@ def template(self):
########### VARIABLES #######################################################################
#############################################################################################
set({{ pkg_name }}_PACKAGE_FOLDER{{ config_suffix }} "{{ package_folder }}")
set({{ pkg_name }}_BUILD_MODULES_PATHS{{ config_suffix }} {{ global_cpp.build_modules_paths }})
{% if not has_components %}
set({{ pkg_name }}_INCLUDE_DIRS{{ config_suffix }} {{ global_cpp.include_paths }})
set({{ pkg_name }}_RES_DIRS{{ config_suffix }} {{ global_cpp.res_paths }})
set({{ pkg_name }}_DEFINITIONS{{ config_suffix }} {{ global_cpp.defines }})
Expand All @@ -98,13 +103,11 @@ def template(self):
set({{ pkg_name }}_SYSTEM_LIBS{{ config_suffix }} {{ global_cpp.system_libs }})
set({{ pkg_name }}_FRAMEWORK_DIRS{{ config_suffix }} {{ global_cpp.framework_paths }})
set({{ pkg_name }}_FRAMEWORKS{{ config_suffix }} {{ global_cpp.frameworks }})
set({{ pkg_name }}_BUILD_MODULES_PATHS{{ config_suffix }} {{ global_cpp.build_modules_paths }})
set({{ pkg_name }}_BUILD_DIRS{{ config_suffix }} {{ global_cpp.build_paths }})
{% else %}
set({{ pkg_name }}_COMPONENTS{{ config_suffix }} {{ components_names }})
{%- for comp_variable_name, comp_target_name, cpp in components_cpp %}
########### COMPONENT {{ comp_target_name }} VARIABLES #############################################
set({{ pkg_name }}_{{ comp_variable_name }}_INCLUDE_DIRS{{ config_suffix }} {{ cpp.include_paths }})
set({{ pkg_name }}_{{ comp_variable_name }}_LIB_DIRS{{ config_suffix }} {{ cpp.lib_paths }})
Expand All @@ -126,15 +129,16 @@ def template(self):
$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,MODULE_LIBRARY>{{ ':${' }}{{ pkg_name }}_{{ comp_variable_name }}_SHARED_LINK_FLAGS{{ config_suffix }}}>
$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>{{ ':${' }}{{ pkg_name }}_{{ comp_variable_name }}_EXE_LINK_FLAGS{{ config_suffix }}}>
)
list(APPEND {{ pkg_name }}_BUILD_MODULES_PATHS{{ config_suffix }} {{ cpp.build_modules_paths }})
{%- endfor %}
{%- endif %}
""")
return ret

def _get_global_cpp_cmake(self):
global_cppinfo = self.conanfile.cpp_info.aggregated_components()
pfolder_var_name = "{}_PACKAGE_FOLDER{}".format(self.pkg_name, self.config_suffix)
return _TargetDataContext(global_cppinfo, pfolder_var_name, self.conanfile.package_folder)
return _TargetDataContext(self.conanfile.cpp_info, pfolder_var_name,
self.conanfile.package_folder)

def _get_required_components_cpp(self):
"""Returns a list of (component_name, DepsCppCMake)"""
Expand Down
Expand Up @@ -6,8 +6,7 @@


@pytest.mark.tool_cmake
@pytest.mark.parametrize("use_components", [False, True])
def test_build_modules_alias_target(use_components):
def test_build_modules_alias_target():
client = TestClient()
conanfile = textwrap.dedent("""
import os
Expand All @@ -24,21 +23,13 @@ def package(self):
def package_info(self):
module = os.path.join("share", "cmake", "target-alias.cmake")
{}
self.cpp_info.set_property("cmake_build_modules", [module])
""")
if use_components:
info = """
self.cpp_info.components["comp"].set_property("cmake_build_modules", [module])
"""
else:
info = """
self.cpp_info.set_property("cmake_build_modules", [module])
"""

target_alias = textwrap.dedent("""
add_library(otherhello INTERFACE IMPORTED)
target_link_libraries(otherhello INTERFACE {target_name})
""").format(target_name="namespace::comp" if use_components else "hello::hello")
conanfile = conanfile.format(info)
target_link_libraries(otherhello INTERFACE hello::hello)
""")
client.save({"conanfile.py": conanfile, "target-alias.cmake": target_alias})
client.run("create .")

Expand Down Expand Up @@ -66,20 +57,13 @@ def build(self):
""")
client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
client.run("create .")
if use_components:
assert "otherhello link libraries: namespace::comp" in client.out
else:
assert "otherhello link libraries: hello::hello" in client.out
assert "otherhello link libraries: hello::hello" in client.out


@pytest.mark.tool_cmake
def test_build_modules_components_selection_is_not_possible():
def test_build_modules_components_is_not_possible():
"""
If openssl declares different cmake_build_modules on ssl and crypto, in the consumer both
are included even if the cpp_info of the consumer declares:
def package_info(self):
self.cpp_info.requires = ["openssl::crypto"]
Because that information is defined later, not at "generate" time (building time).
The "cmake_build_module" property declared in the components is useless
"""
client = TestClient()
conanfile = textwrap.dedent("""
Expand All @@ -90,27 +74,19 @@ class Conan(ConanFile):
name = "openssl"
version = "1.0"
settings = "os", "arch", "compiler", "build_type"
exports_sources = ["ssl.cmake", "crypto.cmake", "root.cmake"]
exports_sources = ["crypto.cmake", "root.cmake"]
def package(self):
self.copy("*.cmake", dst="share/cmake")
def package_info(self):
ssl_module = os.path.join("share", "cmake", "ssl.cmake")
self.cpp_info.components["ssl"].set_property("cmake_build_modules", [ssl_module])
crypto_module = os.path.join("share", "cmake", "crypto.cmake")
self.cpp_info.components["crypto"].set_property("cmake_build_modules", [crypto_module])
root_module = os.path.join("share", "cmake", "root.cmake")
self.cpp_info.set_property("cmake_build_modules", [root_module])
""")

ssl_cmake = textwrap.dedent("""
function(ssl_message MESSAGE_OUTPUT)
message("SSL MESSAGE:${ARGV${0}}")
endfunction()
""")
crypto_cmake = textwrap.dedent("""
function(crypto_message MESSAGE_OUTPUT)
message("CRYPTO MESSAGE:${ARGV${0}}")
Expand All @@ -122,7 +98,6 @@ def package_info(self):
endfunction()
""")
client.save({"conanfile.py": conanfile,
"ssl.cmake": ssl_cmake,
"crypto.cmake": crypto_cmake,
"root.cmake": root_cmake})
client.run("create .")
Expand Down Expand Up @@ -151,14 +126,15 @@ def package_info(self):
project(test)
find_package(openssl CONFIG)
crypto_message("hello!")
ssl_message("hello!")
root_message("hello!")
""")
client.save({"conanfile.py": consumer, "CMakeLists.txt": cmakelists})
# As we are requiring only "crypto" but it doesn't matter, it is not possible to include
# only crypto build_modules
client.run("create .")
assert "SSL MESSAGE:hello!" in client.out
assert "CRYPTO MESSAGE:hello!" in client.out
assert "ROOT MESSAGE:hello!" in client.out
client.run("create .", assert_error=True)
assert 'Unknown CMake command "crypto_message"' in client.out

# Comment the function call
client.save({"CMakeLists.txt": cmakelists.replace("crypto", "#crypto")})
assert "ROOT MESSAGE:hello!" not in client.out

Expand Up @@ -107,15 +107,22 @@ def package_info(self):
assert """set_property(TARGET hello::say PROPERTY INTERFACE_LINK_LIBRARIES
$<$<CONFIG:Release>:${hello_hello_say_OBJECTS_RELEASE}
${hello_hello_say_LINK_LIBS_RELEASE}> APPEND)""" 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
$<$<CONFIG:Release>:${hello_OBJECTS_RELEASE}
${hello_LIBRARIES_TARGETS_RELEASE}> APPEND)""" in content
${hello_LIBRARIES_TARGETS_RELEASE}> APPEND)""" 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

with open(os.path.join(client.current_folder, "hello-release-x86_64-data.cmake")) as f:
content = f.read()
assert 'set(hello_OBJECTS_RELEASE "${hello_PACKAGE_FOLDER_RELEASE}/mycomponent.o")' in content
assert 'set(hello_hello_say_OBJECTS_RELEASE "${hello_PACKAGE_FOLDER_RELEASE}/mycomponent.o")' in content
# Not global variables
assert 'set(hello_OBJECTS_RELEASE "${hello_PACKAGE_FOLDER_RELEASE}/mycomponent.o")' \
not in content
# But component variables
assert 'set(hello_hello_say_OBJECTS_RELEASE "${hello_PACKAGE_FOLDER_RELEASE}/' \
'mycomponent.o")' in content


def test_cpp_info_component_error_aggregate():
Expand Down
12 changes: 7 additions & 5 deletions conans/test/unittests/tools/cmake/test_cmakedeps.py
Expand Up @@ -61,8 +61,8 @@ def test_cpp_info_name_cmakedeps_components():
conanfile.settings.arch = "x64"

cpp_info = CppInfo("mypkg", "dummy_root_folder1")
cpp_info.set_property("cmake_target_name", "GlobakPkgName1::GlobakPkgName1")
cpp_info.components["mycomp"].set_property("cmake_target_name", "GlobakPkgName1::MySuperPkg1")
cpp_info.set_property("cmake_target_name", "GlobalPkgName1::GlobalPkgName1")
cpp_info.components["mycomp"].set_property("cmake_target_name", "GlobalPkgName1::MySuperPkg1")
cpp_info.set_property("cmake_file_name", "ComplexFileName1")

conanfile_dep = ConanFile(Mock(), None)
Expand All @@ -81,11 +81,13 @@ def test_cpp_info_name_cmakedeps_components():

cmakedeps = CMakeDeps(conanfile)
files = cmakedeps.content
assert "TARGET GlobakPkgName1::MySuperPkg1" in files["ComplexFileName1-Target-debug.cmake"]
assert "TARGET GlobalPkgName1::MySuperPkg1" in files["ComplexFileName1-Target-debug.cmake"]
# No global variables for the packages
assert 'set(OriginalDepName_INCLUDE_DIRS_DEBUG ' \
'"${OriginalDepName_PACKAGE_FOLDER_DEBUG}/include")' \
in files["ComplexFileName1-debug-x64-data.cmake"]
assert 'set(OriginalDepName_GlobakPkgName1_MySuperPkg1_INCLUDE_DIRS_DEBUG ' \
not in files["ComplexFileName1-debug-x64-data.cmake"]
# But components
assert 'set(OriginalDepName_GlobalPkgName1_MySuperPkg1_INCLUDE_DIRS_DEBUG ' \
'"${OriginalDepName_PACKAGE_FOLDER_DEBUG}/include")' \
in files["ComplexFileName1-debug-x64-data.cmake"]

Expand Down

0 comments on commit 0fb3d62

Please sign in to comment.