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 2 commits into
base: develop2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 56 additions & 19 deletions conan/tools/cmake/cmakedeps2/target_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,31 @@ 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)

def _link_type(req):
if pkg_type is PackageType.STATIC:
if not req.libs: # not using the libs at all
if req.headers:
return "COMPILE_ONLY"
return None
else:
if req.headers:
return "FULL"
return "LINK_ONLY"

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():
for r, d in transitive_reqs.items():
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}"
result[dep_target] = {"link_type": _link_type(r)}
return result

for required_pkg, required_comp in requires:
if required_pkg is None: # Points to a component of same package
Expand All @@ -53,10 +67,11 @@ 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)
result[dep_target] = {"link_type": "FULL"}
else: # Different package
try:
dep = transitive_reqs[required_pkg]
# FIXME: This can be a problem for packages with requires+tool-requires to same
r, dep = transitive_reqs._get(required_pkg)
except KeyError: # The transitive dep might have been skipped
pass
else:
Expand All @@ -71,12 +86,13 @@ def _requires(self, info, components):
comp = required_comp
dep_target = self._cmakedeps.get_property("cmake_target_name", dep, comp)
dep_target = dep_target or f"{required_pkg}::{required_comp}"
result.append(dep_target)
result[dep_target] = {"link_type": _link_type(r)}
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 @@ -148,13 +164,13 @@ 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
#if not self._require.headers: # If not depending on headers, paths and
# includedirs = defines = None
system_libs = " ".join(info.system_libs)
target = {"type": "INTERFACE",
"includedirs": includedirs,
Expand Down Expand Up @@ -201,15 +217,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] = {} # It is an interface, it doesn't matter
else:
all_requires = " ".join(libs.keys())
all_requires = {k: {} for k in libs.keys()}
libs[root_target_name] = {"type": "INTERFACE",
"requires": all_requires}

Expand Down Expand Up @@ -341,13 +356,35 @@ def _template(self):
set_target_properties({{lib}} PROPERTIES IMPORTED_IMPLIB_{{config}}
"{{lib_info["link_location"]}}")
{% endif %}
{% if lib_info.get("requires") %}
target_link_libraries({{lib}} INTERFACE {{lib_info["requires"]}})
{% endif %}
{% if lib_info.get("system_libs") %}
target_link_libraries({{lib}} INTERFACE {{lib_info["system_libs"]}})
{% endif %}

# Information of transitive dependencies
{% for require_target, require_info in lib_info["requires"].items() %}
# Requirement {{require_target}} => {{require_info}}
{% if lib_info["type"] == "STATIC" %}
{% if require_info["link_type"] == "COMPILE_ONLY" %}
if(${CMAKE_VERSION} VERSION_LESS "3.27")
message(FATAL_ERROR "The 'CMakeDeps' generator only works with CMake >= 3.2")
endif()
set_target_properties({{lib}} PROPERTIES INTERFACE_LINK_LIBRARIES
$<COMPILE_ONLY:{{config_wrapper(config, require_target)}})
{% elif require_info["link_type"] == "LINK_ONLY" %}
set_target_properties({{lib}} PROPERTIES INTERFACE_LINK_LIBRARIES
$<LINK_ONLY:{{config_wrapper(config, require_target)}}>)
{% elif require_info["link_type"] == "FULL" %}
set_target_properties({{lib}} PROPERTIES INTERFACE_LINK_LIBRARIES
{{config_wrapper(config, require_target)}})
{% endif %}
{% elif lib_info["type"] == "SHARED" %}
set_target_properties({{lib}} PROPERTIES IMPORTED_LINK_DEPENDENT_LIBRARIES_{{config}}
{{require_target}})
{% else %}
target_link_libraries({{lib}} INTERFACE {{require_target}})
{% endif %}
{% endfor %}

{% endfor %}

################# Global variables for try compile and legacy ##############
Expand Down
6 changes: 4 additions & 2 deletions conans/model/build_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,13 +593,15 @@ def _find_matching(dirs, pattern):
out.warning(f"Lib {libname} deduced as '{self._type}, but 'package_type={pkg_type}'")

def deduce_locations(self, conanfile, component_name=""):
if self._type is None: # Get by default the package type of the recipe "package_type"
self._type = conanfile.package_type
if self._exe: # exe is a new field, it should have the correct location
return
if self._location or self._link_location:
if self._type is None or self._type is PackageType.HEADER:
raise ConanException("Incorrect cpp_info defining location without type or header")
return
if self._type not in [None, PackageType.SHARED, PackageType.STATIC, PackageType.APP]:
if self._type not in [PackageType.UNKNOWN, PackageType.SHARED, PackageType.STATIC, PackageType.APP]:
return
num_libs = len(self.libs)
if num_libs == 0:
Expand Down Expand Up @@ -798,7 +800,7 @@ def deduce_full_cpp_info(self, conanfile):

common = self._package.clone()
common.libs = []
common.type = str(PackageType.HEADER) # the type of components is a string!
common.type = PackageType.HEADER # the type of components is a string!
common.requires = list(result.components.keys()) + (self.requires or [])
result.components["_common"] = common
else:
Expand Down
Loading
Loading