Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new CmakeDeps transitive linking fixes #17459

Draft
wants to merge 14 commits into
base: develop2
Choose a base branch
from
57 changes: 42 additions & 15 deletions conan/tools/cmake/cmakedeps2/target_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,21 @@ def filename(self):
return f"{f}-Targets{build}-{config}.cmake"

def _requires(self, info, components):
result = []
result = {}
requires = info.parsed_requires()
pkg_name = self._conanfile.ref.name
pkg_type = info.type
assert isinstance(pkg_type, PackageType), f"Pkg type {pkg_type} {type(pkg_type)}"
transitive_reqs = self._cmakedeps.get_transitive_requires(self._conanfile)

if not requires and not components: # global cpp_info without components definition
# require the pkgname::pkgname base (user defined) or INTERFACE base target
targets = []
for d in transitive_reqs.values():
dep_target = self._cmakedeps.get_property("cmake_target_name", d)
targets.append(dep_target or f"{d.ref.name}::{d.ref.name}")
return targets
dep_target = dep_target or f"{d.ref.name}::{d.ref.name}"
link = not (pkg_type is PackageType.SHARED and d.package_type is PackageType.SHARED)
result[dep_target] = link
return result

for required_pkg, required_comp in requires:
if required_pkg is None: # Points to a component of same package
Expand All @@ -53,7 +57,9 @@ def _requires(self, info, components):
dep_target = self._cmakedeps.get_property("cmake_target_name", self._conanfile,
required_comp)
dep_target = dep_target or f"{pkg_name}::{required_comp}"
result.append(dep_target)
link = not (pkg_type is PackageType.SHARED and
dep_comp.package_type is PackageType.SHARED)
result[dep_target] = link
else: # Different package
try:
dep = transitive_reqs[required_pkg]
Expand All @@ -73,12 +79,15 @@ def _requires(self, info, components):
default_target = f"{required_pkg}::{required_comp}"
dep_target = self._cmakedeps.get_property("cmake_target_name", dep, comp)
dep_target = dep_target or default_target
result.append(dep_target)
link = not (pkg_type is PackageType.SHARED and
dep_comp.package_type is PackageType.SHARED)
result[dep_target] = link
return result

@property
def _context(self):
cpp_info = self._conanfile.cpp_info.deduce_full_cpp_info(self._conanfile)
assert isinstance(cpp_info.type, PackageType)
pkg_name = self._conanfile.ref.name
# fallback to consumer configuration if it doesn't have build_type
config = self._conanfile.settings.get_safe("build_type", self._cmakedeps.configuration)
Expand Down Expand Up @@ -150,10 +159,10 @@ def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var):

includedirs = ";".join(self._path(i, pkg_folder, pkg_folder_var)
for i in info.includedirs) if info.includedirs else ""
requires = ";".join(self._requires(info, components))
requires = self._requires(info, components)
assert isinstance(requires, dict)
defines = " ".join(info.defines)
# TODO: Missing escaping?
# TODO: Missing link language
# FIXME: Filter by lib traits!!!!!
if not self._require.headers: # If not depending on headers, paths and
includedirs = defines = None
Expand All @@ -171,7 +180,7 @@ def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var):
if info.frameworks:
ConanOutput(scope=str(self._conanfile)).warning("frameworks not supported yet in new CMakeDeps generator")

if info.libs:
if info.libs: # TODO: to change to location
if len(info.libs) != 1:
raise ConanException(f"New CMakeDeps only allows 1 lib per component:\n"
f"{self._conanfile}: {info.libs}")
Expand Down Expand Up @@ -203,15 +212,14 @@ def _add_root_lib_target(self, libs, pkg_name, cpp_info):
if libs and root_target_name not in libs:
# Add a generic interface target for the package depending on the others
if cpp_info.default_components is not None:
all_requires = []
all_requires = {}
for defaultc in cpp_info.default_components:
target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile,
defaultc)
comp_name = target_name or f"{pkg_name}::{defaultc}"
all_requires.append(comp_name)
all_requires = ";".join(all_requires)
all_requires[comp_name] = True # It is an interface, full link
else:
all_requires = ";".join(libs.keys())
all_requires = {k: True for k in libs.keys()}
libs[root_target_name] = {"type": "INTERFACE",
"requires": all_requires}

Expand Down Expand Up @@ -339,10 +347,29 @@ def _template(self):
set_target_properties({{lib}} PROPERTIES IMPORTED_IMPLIB_{{config}}
"{{lib_info["link_location"]}}")
{% endif %}

{% if lib_info.get("requires") %}
set_target_properties({{lib}} PROPERTIES INTERFACE_LINK_LIBRARIES
"{{config_wrapper(config, lib_info["requires"])}}")
# Information of transitive dependencies
{% for require_target, link in lib_info["requires"].items() %}
# Requirement {{require_target}} => Full link: {{link}}

{% if link %}
# set property allows to append, and lib_info[requires] will iterate
set_property(TARGET {{lib}} APPEND PROPERTY INTERFACE_LINK_LIBRARIES
"{{config_wrapper(config, require_target)}}")
{% else %}
if(${CMAKE_VERSION} VERSION_LESS "3.27")
message(FATAL_ERROR "The 'CMakeToolchain' generator only works with CMake >= 3.15")
endif()
# If the headers trait is not there, this will do nothing
target_link_libraries({{lib}} INTERFACE
$<COMPILE_ONLY:{{config_wrapper(config, require_target)}}> )
set_property(TARGET {{lib}} APPEND PROPERTY IMPORTED_LINK_DEPENDENT_LIBRARIES_{{config}}
{{require_target}})
{% endif %}
{% endfor %}
{% endif %}

{% if lib_info.get("system_libs") %}
target_link_libraries({{lib}} INTERFACE {{lib_info["system_libs"]}})
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def test_libs_transitive(self, transitive_libraries, shared):
assert "Conan: Target declared imported STATIC library 'engine::engine'" in c.out

# if not using cmake >= 3.23 the intermediate gamelib_test linkage fail
@pytest.mark.tool("cmake", "3.23")
@pytest.mark.tool("cmake", "3.25")
@pytest.mark.parametrize("shared", [False, True])
def test_multilevel(self, shared):
# TODO: make this shared fixtures in conftest for multi-level shared testing
Expand Down Expand Up @@ -338,8 +338,8 @@ def package_info(self):
c.run(f"install app -c tools.cmake.cmakedeps:new={new_value} -g CMakeDeps")
targets_cmake = c.load("app/pkg-Targets-release.cmake")
assert "find_dependency(MyDep REQUIRED CONFIG)" in targets_cmake
assert 'set_target_properties(pkg::pkg PROPERTIES INTERFACE_LINK_LIBRARIES\n' \
' "$<$<CONFIG:RELEASE>:MyTargetDep>"' in targets_cmake
assert 'set_property(TARGET pkg::pkg APPEND PROPERTY INTERFACE_LINK_LIBRARIES\n' \
' "$<$<CONFIG:RELEASE>:MyTargetDep>")' in targets_cmake


class TestLibsLinkageTraits:
Expand All @@ -364,6 +364,7 @@ def test_linkage_shared_static(self):
assert "engine/0.1: Hello World Release!"
assert "game/0.1: Hello World Release!"

@pytest.mark.tool("cmake", "3.25")
@pytest.mark.parametrize("shared", [False, True])
def test_transitive_headers(self, shared):
c = TestClient()
Expand Down
Loading