Skip to content

Commit 9ac4028

Browse files
AbrilRBSmemsharded
andauthored
Add a way to specify link features in CMakeConfigDeps (#19444)
* initial sketch * Some testing * more links * Last remaining link feature for interface libraries * Integreation tests * Add functional test with custom link feature * cleanup * Add version check * Update conan/tools/cmake/cmakeconfigdeps/target_configuration.py Co-authored-by: James <[email protected]> * Update conan/tools/cmake/cmakeconfigdeps/target_configuration.py Co-authored-by: James <[email protected]> --------- Co-authored-by: James <[email protected]>
1 parent ba83d20 commit 9ac4028

3 files changed

Lines changed: 176 additions & 12 deletions

File tree

conan/tools/cmake/cmakeconfigdeps/target_configuration.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@ def _requires(self, info, components):
5757
continue
5858
dep_target = self._cmakedeps.get_property("cmake_target_name", d)
5959
dep_target = dep_target or f"{d.ref.name}::{d.ref.name}"
60+
link_feature = self._cmakedeps.get_property("cmake_link_feature", d)
6061
link = not (pkg_type is PackageType.SHARED and d.package_type is PackageType.SHARED)
61-
result[dep_target] = link
62+
result[dep_target] = {
63+
"link": link,
64+
"link_feature": link_feature
65+
}
6266
return result
6367

6468
for required_pkg, required_comp in requires:
@@ -70,7 +74,12 @@ def _requires(self, info, components):
7074
dep_target = dep_target or f"{pkg_name}::{required_comp}"
7175
link = not (pkg_type is PackageType.SHARED and
7276
dep_comp.type is PackageType.SHARED)
73-
result[dep_target] = link
77+
link_feature = self._cmakedeps.get_property("cmake_link_feature", self._conanfile,
78+
required_comp)
79+
result[dep_target] = {
80+
"link": link,
81+
"link_feature": link_feature
82+
}
7483
else: # Different package
7584
try:
7685
dep = transitive_reqs[required_pkg]
@@ -102,8 +111,12 @@ def _requires(self, info, components):
102111

103112
dep_target = self._cmakedeps.get_property("cmake_target_name", dep, comp)
104113
dep_target = dep_target or default_target
114+
link_feature = self._cmakedeps.get_property("cmake_link_feature", dep, comp)
105115

106-
result[dep_target] = link
116+
result[dep_target] = {
117+
"link": link,
118+
"link_feature": link_feature
119+
}
107120
return result
108121

109122
@property
@@ -195,6 +208,7 @@ def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var, comp_name
195208
comp_name=comp_name, check_type=list) or []
196209
sources = [self._path(source, pkg_folder, pkg_folder_var) for source in info.sources]
197210
target = {"type": "INTERFACE",
211+
"comp_name": comp_name,
198212
"includedirs": includedirs,
199213
"defines": defines,
200214
"requires": requires,
@@ -264,9 +278,19 @@ def _add_root_lib_target(self, libs, pkg_name, cpp_info):
264278
target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile,
265279
defaultc)
266280
comp_name = target_name or f"{pkg_name}::{defaultc}"
267-
all_requires[comp_name] = True # It is an interface, full link
281+
link_feature = self._cmakedeps.get_property("cmake_link_feature", self._conanfile,
282+
defaultc)
283+
all_requires[comp_name] = {
284+
"link": True, # It is an interface, full link
285+
"link_feature": link_feature
286+
}
268287
else:
269-
all_requires = {k: True for k in libs.keys()}
288+
all_requires = {k: {
289+
"link": True,
290+
"link_feature": self._cmakedeps.get_property("cmake_link_feature", self._conanfile,
291+
v.get("comp_name"))
292+
}
293+
for k, v in libs.items()}
270294
# This target might have an alias, so we need to check it
271295
cmake_target_aliases = self._get_aliases()
272296
libs[root_target_name] = {"type": "INTERFACE",
@@ -407,16 +431,26 @@ def _template(self):
407431
408432
{% if lib_info.get("requires") %}
409433
# Information of transitive dependencies
410-
{% for require_target, link in lib_info["requires"].items() %}
411-
# Requirement {{require_target}} => Full link: {{link}}
434+
{% for require_target, link_info in lib_info["requires"].items() %}
412435
413-
{% if link %}
436+
# Requirement {{lib}} -> {{require_target}} (Full link: {{link_info["link"]}})
437+
{% if link_info["link"] %}
438+
{% if link_info["link_feature"] %}
439+
# Link feature: {{link_info["link_feature"]}}
440+
if(CMAKE_VERSION VERSION_LESS "3.24")
441+
message(FATAL_ERROR "The 'CMakeConfigDeps' generator LINK_FEATURE property only works with CMake >= 3.24")
442+
endif()
443+
{% endif %}
414444
# set property allows to append, and lib_info[requires] will iterate
415445
set_property(TARGET {{lib}} APPEND PROPERTY INTERFACE_LINK_LIBRARIES
446+
{% if link_info["link_feature"] %}
447+
"$<LINK_LIBRARY:{{link_info["link_feature"]}},{{config_wrapper(config, require_target)}}>")
448+
{% else %}
416449
"{{config_wrapper(config, require_target)}}")
450+
{% endif %}
417451
{% else %}
418-
if(${CMAKE_VERSION} VERSION_LESS "3.27")
419-
message(FATAL_ERROR "The 'CMakeToolchain' generator only works with CMake >= 3.27")
452+
if(CMAKE_VERSION VERSION_LESS "3.27")
453+
message(FATAL_ERROR "The 'CMakeConfigDeps' generator COMPILE_ONLY expression only works with CMake >= 3.27")
420454
endif()
421455
# If the headers trait is not there, this will do nothing
422456
target_link_libraries({{lib}} INTERFACE

test/functional/toolchains/cmake/cmakedeps2/test_cmakeconfigdeps_new.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,28 @@ def test_transitive_headers(self, shared):
402402
c.run(f"build . -o *:shared={shared} -c tools.cmake.cmakedeps:new={new_value}")
403403
# it works
404404

405+
@pytest.mark.tool("cmake", "3.27")
406+
def test_link_features(self):
407+
tc = TestClient()
408+
tc.run("new cmake_lib -d name=matrix -d version=0.1")
409+
conanfile = tc.load("conanfile.py")
410+
# So that the custom link feature works
411+
conanfile += (" self.cpp_info.set_property('cmake_extra_variables', "
412+
"{'CMAKE_LINK_LIBRARY_USING_MYFET_SUPPORTED': True,"
413+
"'CMAKE_LINK_LIBRARY_USING_MYFET': '<LIBRARY>'})\n")
414+
# And set this library to use the custom link feature
415+
conanfile += " self.cpp_info.set_property('cmake_link_feature', 'MYFET')"
416+
tc.save({"conanfile.py": conanfile})
417+
tc.run(f"create -c tools.cmake.cmakedeps:new={new_value}")
418+
419+
tc.save({}, clean_first=True)
420+
tc.run("new cmake_lib -d name=lib -d version=0.1 -d requires=matrix/0.1")
421+
tc.run(f"create -c tools.cmake.cmakedeps:new={new_value}")
422+
test_build_folder = tc.created_test_build_folder("lib/0.1")
423+
test_generators_folder = os.path.join("test_package", test_build_folder, "generators")
424+
libs_targets = tc.load(os.path.join(test_generators_folder, "lib-Targets-release.cmake"))
425+
assert '"$<LINK_LIBRARY:MYFET,$<$<CONFIG:RELEASE>:matrix::matrix>>"' in libs_targets
426+
405427

406428
@pytest.mark.tool("cmake")
407429
class TestLibsComponents:

test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ def package_info(self):
495495
targets = c.load("libtool-Targets-release.cmake")
496496
# The libtool shouldn't depend on the automake::automake target
497497
assert "automake::automake" not in targets
498-
assert "# Requirement automake::mylibapp => Full link: True" in targets
498+
assert "# Requirement libtool::libtool -> automake::mylibapp (Full link: True)" in targets
499499
assert "$<$<CONFIG:RELEASE>:automake::mylibapp>" in targets
500500

501501
def test_requires_from_library_component(self):
@@ -656,4 +656,112 @@ def package_info(self):
656656
client.run(f"install --requires=pkg/0.1 -g CMakeDeps -c tools.cmake.cmakedeps:new={new_value}")
657657
target = client.load("pkg-Targets-release.cmake")
658658
assert 'add_library(pkg::base INTERFACE IMPORTED)' in target
659-
assert "# Requirement pkg::base => Full link: True" in target
659+
assert "# Requirement pkg::comp -> pkg::base (Full link: True)" in target
660+
661+
662+
class TestLinkFeatures:
663+
def test_link_info_global_cpp_info(self):
664+
tc = TestClient()
665+
conanfile = textwrap.dedent("""
666+
from conan import ConanFile
667+
668+
class Pkg(ConanFile):
669+
name = "pkg"
670+
version = "1.0"
671+
settings = "os", "compiler", "build_type", "arch"
672+
673+
def package_info(self):
674+
self.cpp_info.set_property("cmake_link_feature", "MYFET")
675+
""")
676+
tc.save({"conanfile.py": conanfile})
677+
tc.run("create")
678+
679+
dep = textwrap.dedent("""
680+
from conan import ConanFile
681+
class Dep(ConanFile):
682+
name = "dep"
683+
version = "1.0"
684+
settings = "os", "compiler", "build_type", "arch"
685+
requires = "pkg/1.0"
686+
""")
687+
tc.save({"conanfile.py": dep})
688+
tc.run("create")
689+
tc.run("install --requires=dep/1.0 -g CMakeConfigDeps")
690+
# The requirement should propagate the link feature info
691+
target = tc.load("dep-Targets-release.cmake")
692+
assert "# Requirement dep::dep -> pkg::pkg (Full link: True)\n# Link feature: MYFET" in target
693+
694+
def test_link_info_local_component_from_interface(self):
695+
tc = TestClient()
696+
conanfile = textwrap.dedent("""
697+
from conan import ConanFile
698+
699+
class Pkg(ConanFile):
700+
name = "pkg"
701+
version = "1.0"
702+
settings = "os", "compiler", "build_type", "arch"
703+
704+
def package_info(self):
705+
self.cpp_info.components["compA"].set_property("cmake_link_feature", "MYFET")
706+
""")
707+
tc.save({"conanfile.py": conanfile})
708+
tc.run("create")
709+
tc.run("install --requires=pkg/1.0 -g CMakeConfigDeps")
710+
targets = tc.load("pkg-Targets-release.cmake")
711+
# The interface library created as a global target should have the requirement
712+
assert "# Requirement pkg::pkg -> pkg::compA (Full link: True)\n# Link feature: MYFET" in targets
713+
714+
def test_link_info_local_component_to_component_require(self):
715+
tc = TestClient()
716+
conanfile = textwrap.dedent("""
717+
from conan import ConanFile
718+
719+
class Pkg(ConanFile):
720+
name = "pkg"
721+
version = "1.0"
722+
settings = "os", "compiler", "build_type", "arch"
723+
724+
def package_info(self):
725+
self.cpp_info.components["compA"].set_property("cmake_link_feature", "MYFET")
726+
self.cpp_info.components["compB"].requires = ["compA"]
727+
""")
728+
tc.save({"conanfile.py": conanfile})
729+
tc.run("create")
730+
tc.run("install --requires=pkg/1.0 -g CMakeConfigDeps")
731+
targets = tc.load("pkg-Targets-release.cmake")
732+
# The component requirement should have the link feature info
733+
assert "# Requirement pkg::compB -> pkg::compA (Full link: True)\n# Link feature: MYFET" in targets
734+
735+
def test_link_info_lib_to_component_require(self):
736+
tc = TestClient()
737+
conanfile = textwrap.dedent("""
738+
from conan import ConanFile
739+
740+
class Pkg(ConanFile):
741+
name = "pkg"
742+
version = "1.0"
743+
settings = "os", "compiler", "build_type", "arch"
744+
745+
def package_info(self):
746+
self.cpp_info.components["compA"].set_property("cmake_link_feature", "MYFET")
747+
""")
748+
tc.save({"conanfile.py": conanfile})
749+
tc.run("create")
750+
751+
dep = textwrap.dedent("""
752+
from conan import ConanFile
753+
class Dep(ConanFile):
754+
name = "dep"
755+
version = "1.0"
756+
settings = "os", "compiler", "build_type", "arch"
757+
requires = "pkg/1.0"
758+
759+
def package_info(self):
760+
self.cpp_info.requires = ["pkg::compA"]
761+
""")
762+
tc.save({"conanfile.py": dep})
763+
tc.run("create")
764+
tc.run("install --requires=dep/1.0 -g CMakeConfigDeps")
765+
targets = tc.load("dep-Targets-release.cmake")
766+
# The requirement should have the link feature info
767+
assert "# Requirement dep::dep -> pkg::compA (Full link: True)\n# Link feature: MYFET" in targets

0 commit comments

Comments
 (0)