From ffd2e58e38aa26af103fff88fbed35ef211da2e9 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 13 Jan 2025 11:56:28 +0100 Subject: [PATCH 1/7] fix incubating CMakeDeps replace_requires --- .../cmake/cmakedeps2/target_configuration.py | 4 +- conans/model/build_info.py | 6 +- conans/model/dependencies.py | 2 +- .../graph/test_replace_requires.py | 278 ++++++++++++++++++ 4 files changed, 285 insertions(+), 5 deletions(-) diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py index 15b95930a2f..27ed4a0a5bb 100644 --- a/conan/tools/cmake/cmakedeps2/target_configuration.py +++ b/conan/tools/cmake/cmakedeps2/target_configuration.py @@ -67,10 +67,12 @@ def _requires(self, info, components): # It must be the interface pkgname::pkgname target assert required_pkg == required_comp comp = None + default_target = f"{dep.ref.name}::{dep.ref.name}" # replace_requires else: comp = required_comp + default_target = f"{required_pkg}::{required_comp}" dep_target = self._cmakedeps.get_property("cmake_target_name", dep, comp) - dep_target = dep_target or f"{required_pkg}::{required_comp}" + dep_target = dep_target or default_target result.append(dep_target) return result diff --git a/conans/model/build_info.py b/conans/model/build_info.py index 57b619dabd8..754c35407fe 100644 --- a/conans/model/build_info.py +++ b/conans/model/build_info.py @@ -285,7 +285,7 @@ def type(self): @type.setter def type(self, value): - self._type = value + self._type = PackageType(value) @property def location(self): @@ -789,7 +789,7 @@ def deduce_full_cpp_info(self, conanfile): assert not self.components, f"{conanfile} cpp_info shouldn't have .libs and .components" for lib in self.libs: c = _Component() # Do not do a full clone, we don't need the properties - c.type = self.type # This should be a string + c.type = self.type c.includedirs = self.includedirs c.libdirs = self.libdirs c.bindirs = self.bindirs @@ -798,7 +798,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: diff --git a/conans/model/dependencies.py b/conans/model/dependencies.py index 443eeee5463..27914da7119 100644 --- a/conans/model/dependencies.py +++ b/conans/model/dependencies.py @@ -130,7 +130,7 @@ def transitive_requires(self, other): for k, v in self._data.items(): for otherk, otherv in other._data.items(): if v == otherv: - data[k] = v + data[otherk] = v # Use otherk to respect original replace_requires return ConanFileDependencies(data) @property diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py index affa15e94aa..c6ff9e1b89c 100644 --- a/test/integration/graph/test_replace_requires.py +++ b/test/integration/graph/test_replace_requires.py @@ -373,3 +373,281 @@ def package_info(self): # There are actually 2 dependencies, pointing to the same node assert "libepoxy/0.1: DEP: opengl: libgl" in c.out assert "libepoxy/0.1: DEP: egl: libgl" in c.out + + +class TestReplaceRequiresTransitiveGenerators: + """ Generators are incorrectly managing replace_requires + # https://github.com/conan-io/conan/issues/17557 + """ + + @pytest.mark.parametrize("diamond", [True, False]) + def test_no_components(self, diamond): + c = TestClient() + zlib_ng = textwrap.dedent(""" + from conan import ConanFile + class ZlibNG(ConanFile): + name = "zlib-ng" + version = "0.1" + package_type = "static-library" + def package_info(self): + self.cpp_info.libs = ["zlib"] + self.cpp_info.type = "static-library" + self.cpp_info.location = "lib/zlib.lib" + self.cpp_info.set_property("cmake_file_name", "ZLIB") + self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB") + """) + openssl = textwrap.dedent(""" + from conan import ConanFile + class openssl(ConanFile): + name = "openssl" + version = "0.1" + package_type = "static-library" + requires = "zlib/0.1" + def package_info(self): + self.cpp_info.libs = ["crypto"] + self.cpp_info.type = "static-library" + self.cpp_info.location = "lib/crypto.lib" + self.cpp_info.requires = ["zlib::zlib"] + """) + zlib = '"zlib/0.1"' if diamond else "" + conanfile = textwrap.dedent(f""" + from conan import ConanFile + class App(ConanFile): + name = "app" + version = "0.1" + settings = "build_type" + requires = "openssl/0.1", {zlib} + package_type = "application" + generators = "CMakeDeps" + """) + profile = textwrap.dedent(""" + [settings] + build_type = Release + + [replace_requires] + zlib/0.1: zlib-ng/0.1 + """) + c.save({"zlibng/conanfile.py": zlib_ng, + "openssl/conanfile.py": openssl, + "app/conanfile.py": conanfile, + "profile": profile}) + + c.run("create zlibng") + c.run("create openssl -pr=profile") + c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") + assert "zlib/0.1: zlib-ng/0.1" in c.out + + cmake = c.load("app/ZLIB-Targets-release.cmake") + assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake + + cmake = c.load("app/openssl-Targets-release.cmake") + assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake + assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake + assert "target_link_libraries(openssl::openssl INTERFACE ZLIB::ZLIB)" in cmake + + @pytest.mark.parametrize("diamond", [True, False]) + def test_openssl_components(self, diamond): + c = TestClient() + zlib_ng = textwrap.dedent(""" + from conan import ConanFile + class ZlibNG(ConanFile): + name = "zlib-ng" + version = "0.1" + package_type = "static-library" + def package_info(self): + self.cpp_info.libs = ["zlib"] + self.cpp_info.type = "static-library" + self.cpp_info.location = "lib/zlib.lib" + self.cpp_info.set_property("cmake_file_name", "ZLIB") + self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB") + """) + openssl = textwrap.dedent(""" + from conan import ConanFile + class openssl(ConanFile): + name = "openssl" + version = "0.1" + package_type = "static-library" + requires = "zlib/0.1" + def package_info(self): + self.cpp_info.components["crypto"].libs = ["crypto"] + self.cpp_info.components["crypto"].type = "static-library" + self.cpp_info.components["crypto"].location = "lib/crypto.lib" + self.cpp_info.components["crypto"].requires = ["zlib::zlib"] + """) + zlib = '"zlib/0.1"' if diamond else "" + conanfile = textwrap.dedent(f""" + from conan import ConanFile + class App(ConanFile): + name = "app" + version = "0.1" + settings = "build_type" + requires = "openssl/0.1", {zlib} + package_type = "application" + generators = "CMakeDeps" + """) + profile = textwrap.dedent(""" + [settings] + build_type = Release + + [replace_requires] + zlib/0.1: zlib-ng/0.1 + """) + c.save({"zlibng/conanfile.py": zlib_ng, + "openssl/conanfile.py": openssl, + "app/conanfile.py": conanfile, + "profile": profile}) + + c.run("create zlibng") + c.run("create openssl -pr=profile") + c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") + assert "zlib/0.1: zlib-ng/0.1" in c.out + + cmake = c.load("app/ZLIB-Targets-release.cmake") + assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake + + cmake = c.load("app/openssl-Targets-release.cmake") + assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake + assert "add_library(openssl::crypto STATIC IMPORTED)" in cmake + assert "target_link_libraries(openssl::crypto INTERFACE ZLIB::ZLIB)" in cmake + + @pytest.mark.parametrize("diamond", [True, False]) + @pytest.mark.parametrize("explicit_requires", [True, False]) + def test_zlib_components(self, diamond, explicit_requires): + c = TestClient() + zlib_ng = textwrap.dedent(""" + from conan import ConanFile + class ZlibNG(ConanFile): + name = "zlib-ng" + version = "0.1" + package_type = "static-library" + def package_info(self): + self.cpp_info.components["myzlib"].libs = ["zlib"] + self.cpp_info.components["myzlib"].type = "static-library" + self.cpp_info.components["myzlib"].location = "lib/zlib.lib" + self.cpp_info.set_property("cmake_file_name", "ZLIB") + self.cpp_info.components["myzlib"].set_property("cmake_target_name", + "ZLIB::ZLIB") + """) + openssl = textwrap.dedent(f""" + from conan import ConanFile + class openssl(ConanFile): + name = "openssl" + version = "0.1" + package_type = "static-library" + requires = "zlib/0.1" + def package_info(self): + self.cpp_info.libs = ["crypto"] + self.cpp_info.type = "static-library" + self.cpp_info.location = "lib/crypto.lib" + if {explicit_requires}: + self.cpp_info.requires = ["zlib::zlib"] + """) + zlib = '"zlib/0.1"' if diamond else "" + conanfile = textwrap.dedent(f""" + from conan import ConanFile + class App(ConanFile): + name = "app" + version = "0.1" + settings = "build_type" + requires = "openssl/0.1", {zlib} + package_type = "application" + generators = "CMakeDeps" + """) + profile = textwrap.dedent(""" + [settings] + build_type = Release + + [replace_requires] + zlib/0.1: zlib-ng/0.1 + """) + c.save({"zlibng/conanfile.py": zlib_ng, + "openssl/conanfile.py": openssl, + "app/conanfile.py": conanfile, + "profile": profile}) + + c.run("create zlibng") + c.run("create openssl -pr=profile") + c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") + assert "zlib/0.1: zlib-ng/0.1" in c.out + + cmake = c.load("app/ZLIB-Targets-release.cmake") + assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake + + cmake = c.load("app/openssl-Targets-release.cmake") + assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake + assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake + # It should access the generic zlib-ng target + assert "target_link_libraries(openssl::openssl INTERFACE zlib-ng::zlib-ng)" in cmake + + @pytest.mark.parametrize("diamond", [True, False]) + @pytest.mark.parametrize("package_requires", [False, True]) + def test_both_components(self, diamond, package_requires): + c = TestClient() + zlib_ng = textwrap.dedent(""" + from conan import ConanFile + class ZlibNG(ConanFile): + name = "zlib-ng" + version = "0.1" + package_type = "static-library" + def package_info(self): + self.cpp_info.components["myzlib"].libs = ["zlib"] + self.cpp_info.components["myzlib"].type = "static-library" + self.cpp_info.components["myzlib"].location = "lib/zlib.lib" + self.cpp_info.set_property("cmake_file_name", "ZLIB") + self.cpp_info.components["myzlib"].set_property("cmake_target_name", + "ZLIB::ZLIB") + """) + openssl = textwrap.dedent(f""" + from conan import ConanFile + class openssl(ConanFile): + name = "openssl" + version = "0.1" + package_type = "static-library" + requires = "zlib/0.1" + def package_info(self): + self.cpp_info.components["crypto"].libs = ["crypto"] + self.cpp_info.components["crypto"].type = "static-library" + self.cpp_info.components["crypto"].location = "lib/crypto.lib" + if {package_requires}: + self.cpp_info.components["crypto"].requires = ["zlib::zlib"] + else: + self.cpp_info.components["crypto"].requires = ["zlib::myzlib"] + """) + zlib = '"zlib/0.1"' if diamond else "" + conanfile = textwrap.dedent(f""" + from conan import ConanFile + class App(ConanFile): + name = "app" + version = "0.1" + settings = "build_type" + requires = "openssl/0.1", {zlib} + package_type = "application" + generators = "CMakeDeps" + """) + profile = textwrap.dedent(""" + [settings] + build_type = Release + + [replace_requires] + zlib/0.1: zlib-ng/0.1 + """) + c.save({"zlibng/conanfile.py": zlib_ng, + "openssl/conanfile.py": openssl, + "app/conanfile.py": conanfile, + "profile": profile}) + + c.run("create zlibng") + c.run("create openssl -pr=profile") + c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") + assert "zlib/0.1: zlib-ng/0.1" in c.out + + cmake = c.load("app/ZLIB-Targets-release.cmake") + assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake + + cmake = c.load("app/openssl-Targets-release.cmake") + assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake + assert "add_library(openssl::crypto STATIC IMPORTED)" in cmake + if package_requires: + assert "target_link_libraries(openssl::crypto INTERFACE zlib-ng::zlib-ng)" in cmake + else: + assert "target_link_libraries(openssl::crypto INTERFACE ZLIB::ZLIB)" in cmake From e1f1f1bd7628328b431ed590810b7e81c5358b13 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 13 Jan 2025 12:10:56 +0100 Subject: [PATCH 2/7] fix tests --- conans/model/build_info.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/conans/model/build_info.py b/conans/model/build_info.py index 754c35407fe..b578a2de972 100644 --- a/conans/model/build_info.py +++ b/conans/model/build_info.py @@ -285,7 +285,7 @@ def type(self): @type.setter def type(self, value): - self._type = PackageType(value) + self._type = PackageType(value) if value is not None else None @property def location(self): @@ -599,7 +599,8 @@ def deduce_locations(self, conanfile, component_name=""): 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 is not None and self._type not in [PackageType.SHARED, PackageType.STATIC, + PackageType.APP]: return num_libs = len(self.libs) if num_libs == 0: From 7556383032d3870eafc67e47843992df1bea1796 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 13 Jan 2025 14:12:51 +0100 Subject: [PATCH 3/7] possible fix --- conans/model/dependencies.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/conans/model/dependencies.py b/conans/model/dependencies.py index 27914da7119..31ac64553bd 100644 --- a/conans/model/dependencies.py +++ b/conans/model/dependencies.py @@ -182,6 +182,8 @@ def get_transitive_requires(consumer, dependency): # The build dependencies cannot be transitive in generators like CMakeDeps, # even if users make them visible pkg_deps = dependency.dependencies.filter({"direct": True, "build": False}) - result = consumer.dependencies.transitive_requires(pkg_deps) - result = result.filter({"skip": False}) + # First we filter the skipped dependencies + result = consumer.dependencies.filter({"skip": False}) + # and we keep those that are really dependencies of the current package + result = result.transitive_requires(pkg_deps) return result From 0dfebbcc827ce69afa0fe10c63c89f3550f5e3dc Mon Sep 17 00:00:00 2001 From: memsharded Date: Tue, 14 Jan 2025 09:45:16 +0100 Subject: [PATCH 4/7] add PkgConfigDeps check --- test/integration/graph/test_replace_requires.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py index c6ff9e1b89c..707524c7196 100644 --- a/test/integration/graph/test_replace_requires.py +++ b/test/integration/graph/test_replace_requires.py @@ -395,6 +395,7 @@ def package_info(self): self.cpp_info.location = "lib/zlib.lib" self.cpp_info.set_property("cmake_file_name", "ZLIB") self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB") + self.cpp_info.set_property("pkg_config_name", "ZLIB") """) openssl = textwrap.dedent(""" from conan import ConanFile @@ -418,7 +419,7 @@ class App(ConanFile): settings = "build_type" requires = "openssl/0.1", {zlib} package_type = "application" - generators = "CMakeDeps" + generators = "CMakeDeps", "PkgConfigDeps" """) profile = textwrap.dedent(""" [settings] @@ -445,6 +446,10 @@ class App(ConanFile): assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake assert "target_link_libraries(openssl::openssl INTERFACE ZLIB::ZLIB)" in cmake + pkg_config = c.load("app/ZLIB.pc") + print(pkg_config) + # TODO: Check here the contents of both ZLIB.pc and openssl.pc + @pytest.mark.parametrize("diamond", [True, False]) def test_openssl_components(self, diamond): c = TestClient() From d0f6e17b20a4ae021119098c1d0818fd8a5e3b2a Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Tue, 21 Jan 2025 17:03:52 +0100 Subject: [PATCH 5/7] Fixed bug using the replace_requires mechanism in PkgConfigDeps. Some variables renames (clearer names) --- conan/tools/cmake/__init__.py | 2 +- conan/tools/gnu/pkgconfigdeps.py | 93 ++++++++++--------- .../graph/test_replace_requires.py | 40 ++++++-- 3 files changed, 85 insertions(+), 50 deletions(-) diff --git a/conan/tools/cmake/__init__.py b/conan/tools/cmake/__init__.py index 73c8a835b08..e57f14a6587 100644 --- a/conan/tools/cmake/__init__.py +++ b/conan/tools/cmake/__init__.py @@ -7,7 +7,7 @@ def CMakeDeps(conanfile): # noqa if conanfile.conf.get("tools.cmake.cmakedeps:new", choices=["will_break_next"]): from conan.tools.cmake.cmakedeps2.cmakedeps import CMakeDeps2 conanfile.output.warning("Using the new CMakeDeps generator, behind the " - "'tools.cmake.cmakedeps:new' gate conf. This conf will change" + "'tools.cmake.cmakedeps:new' gate conf. This conf will change " "next release, breaking, so use it only for testing and dev") return CMakeDeps2(conanfile) from conan.tools.cmake.cmakedeps.cmakedeps import CMakeDeps as _CMakeDeps diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py index 5ef67d55db0..47b9a3245fb 100644 --- a/conan/tools/gnu/pkgconfigdeps.py +++ b/conan/tools/gnu/pkgconfigdeps.py @@ -46,14 +46,14 @@ class _PCContentGenerator: {% endif %} """) - def __init__(self, conanfile, dep): + def __init__(self, conanfile, pkg): self._conanfile = conanfile - self._dep = dep + self._pkg = pkg def _get_prefix_path(self): # If editable, package_folder can be None - root_folder = self._dep.recipe_folder if self._dep.package_folder is None \ - else self._dep.package_folder + root_folder = self._pkg.recipe_folder if self._pkg.package_folder is None \ + else self._pkg.package_folder return root_folder.replace("\\", "/") def _get_pc_variables(self, cpp_info): @@ -139,12 +139,12 @@ def content(self, info): class _PCGenerator: - def __init__(self, pkgconfigdeps, require, dep): + def __init__(self, pkgconfigdeps, require, pkg): self._conanfile = pkgconfigdeps._conanfile # noqa self._properties = pkgconfigdeps._properties # noqa self._require = require - self._dep = dep - self._transitive_reqs = get_transitive_requires(self._conanfile, dep) + self._pkg = pkg + self._transitive_reqs = get_transitive_requires(self._conanfile, pkg) self._is_build_context = require.build self._build_context_folder = pkgconfigdeps.build_context_folder self._suffix = pkgconfigdeps.build_context_suffix.get(require.ref.name, "") \ @@ -170,23 +170,23 @@ def package_info(self): self.cpp_info.components["cmp"].requires = ["other::cmp1"] ``` """ - dep_ref_name = self._dep.ref.name + pkg_ref_name = self._pkg.ref.name ret = [] for req in cpp_info.requires: - pkg_ref_name, comp_ref_name = req.split("::") if "::" in req else (dep_ref_name, req) + required_dep, required_comp = req.split("::") if "::" in req else (pkg_ref_name, req) # For instance, dep == "hello/1.0" and req == "other::cmp1" -> hello != other - if dep_ref_name != pkg_ref_name: + if pkg_ref_name != required_dep: try: - req_conanfile = self._transitive_reqs[pkg_ref_name] + dep = self._transitive_reqs[required_dep] except KeyError: continue # If the dependency is not in the transitive, might be skipped else: # For instance, dep == "hello/1.0" and req == "hello::cmp1" -> hello == hello - req_conanfile = self._dep - comp_name = self._get_component_name(req_conanfile, comp_ref_name) + dep = self._pkg + comp_name = self._get_component_name(dep, required_dep, required_comp) if not comp_name: - pkg_name = self._get_package_name(req_conanfile) + pkg_name = self._get_package_name(dep) # Creating a component name with namespace, e.g., dep-comp1 - comp_name = self._get_name_with_namespace(pkg_name, comp_ref_name) + comp_name = self._get_name_with_namespace(pkg_name, required_comp) ret.append(comp_name) return ret @@ -197,23 +197,23 @@ def _components_info(self): :return: `list` of `_PCInfo` objects with all the components information """ - pkg_name = self._get_package_name(self._dep) + pkg_name = self._get_package_name(self._pkg) components_info = [] # Loop through all the package's components - for comp_ref_name, cpp_info in self._dep.cpp_info.get_sorted_components().items(): + for comp_ref_name, cpp_info in self._pkg.cpp_info.get_sorted_components().items(): # At first, let's check if we have defined some components requires, e.g., "dep::cmp1" comp_requires_names = self._get_cpp_info_requires_names(cpp_info) - comp_name = self._get_component_name(self._dep, comp_ref_name) + comp_name = self._get_component_name(self._pkg, pkg_name, comp_ref_name) if not comp_name: comp_name = self._get_name_with_namespace(pkg_name, comp_ref_name) comp_description = f"Conan component: {comp_name}" else: comp_description = f"Conan component: {pkg_name}-{comp_name}" - comp_aliases = self._get_component_aliases(self._dep, comp_ref_name) - comp_version = (self.get_property("component_version", self._dep, comp_ref_name) or - self.get_property("system_package_version", self._dep, comp_ref_name) or - self._dep.ref.version) - comp_custom_content = self.get_property("pkg_config_custom_content", self._dep, comp_ref_name) + comp_aliases = self._get_component_aliases(self._pkg, pkg_name, comp_ref_name) + comp_version = (self.get_property("component_version", self._pkg, comp_ref_name) or + self.get_property("system_package_version", self._pkg, comp_ref_name) or + self._pkg.ref.version) + comp_custom_content = self.get_property("pkg_config_custom_content", self._pkg, comp_ref_name) # Save each component information components_info.append(_PCInfo(comp_name, comp_version, comp_requires_names, comp_description, cpp_info, comp_aliases, comp_custom_content)) @@ -225,9 +225,9 @@ def _package_info(self): :return: `_PCInfo` object with the package information """ - pkg_name = self._get_package_name(self._dep) + pkg_name = self._get_package_name(self._pkg) # At first, let's check if we have defined some global requires, e.g., "other::cmp1" - requires = self._get_cpp_info_requires_names(self._dep.cpp_info) + requires = self._get_cpp_info_requires_names(self._pkg.cpp_info) # If we have found some component requires it would be enough if not requires: # If no requires were found, let's try to get all the direct visible dependencies, @@ -235,11 +235,11 @@ def _package_info(self): requires = [self._get_package_name(req) for req in self._transitive_reqs.values()] description = "Conan package: %s" % pkg_name - pkg_version = (self.get_property("system_package_version", self._dep) - or self._dep.ref.version) - aliases = self._get_package_aliases(self._dep) - cpp_info = self._dep.cpp_info - custom_content = self.get_property("pkg_config_custom_content", self._dep) + pkg_version = (self.get_property("system_package_version", self._pkg) + or self._pkg.ref.version) + aliases = self._get_package_aliases(self._pkg) + cpp_info = self._pkg.cpp_info + custom_content = self.get_property("pkg_config_custom_content", self._pkg) return _PCInfo(pkg_name, pkg_version, requires, description, cpp_info, aliases, custom_content) @property @@ -268,14 +268,14 @@ def _file_name(name): return f"{self._build_context_folder}/{name}.pc" if build else f"{name}.pc" def _add_pc_files(pc_info): - content_generator = _PCContentGenerator(self._conanfile, self._dep) + content_generator = _PCContentGenerator(self._conanfile, self._pkg) result = {_file_name(pc_info.name): content_generator.content(pc_info)} for alias in pc_info.aliases: result[_file_name(alias)] = alias_content(alias, pc_info.version, pc_info.name) return result # If the package has no components, then we have to calculate only the root pc file - if not self._dep.cpp_info.has_components: + if not self._pkg.cpp_info.has_components: pkg_pc_info = self._package_info() return _add_pc_files(pkg_pc_info) @@ -291,15 +291,15 @@ def _add_pc_files(pc_info): # Second, let's load the root package's PC file ONLY # if it does not already exist in components one # Issue related: https://github.com/conan-io/conan/issues/10341 - pkg_name = self._get_package_name(self._dep) - pkg_version = (self.get_property("system_package_version", self._dep) - or self._dep.ref.version) + pkg_name = self._get_package_name(self._pkg) + pkg_version = (self.get_property("system_package_version", self._pkg) + or self._pkg.ref.version) if f"{pkg_name}.pc" not in pc_files: package_info = _PCInfo(pkg_name, pkg_version, pkg_requires, f"Conan package: {pkg_name}", - self._dep.cpp_info, self._get_package_aliases(self._dep), - self.get_property("pkg_config_custom_content", self._dep)) + self._pkg.cpp_info, self._get_package_aliases(self._pkg), + self.get_property("pkg_config_custom_content", self._pkg)) pc_files.update(_add_pc_files(package_info)) return pc_files @@ -314,10 +314,14 @@ def _get_package_aliases(self, dep): pkg_aliases = self.get_property("pkg_config_aliases", dep, check_type=list) return pkg_aliases or [] - def _get_component_aliases(self, dep, comp_name): + def _get_component_aliases(self, dep, pkg_name, comp_name): + # TODO: LET'S DEPRECATE ALL THE ALIASES MECHANISM!! if comp_name not in dep.cpp_info.components: - # foo::foo might be referencing the root cppinfo - if dep.ref.name == comp_name: + # Either foo::foo might be referencing the root cpp_info + if (dep.ref.name == comp_name or + # Or a "replace_require" is used and cpp_info.requires is the root one, e.g., + # zlib/*: zlib-ng/*, and self.cpp_info.requires = ["zlib::zlib"] + (dep.ref.name != pkg_name and pkg_name == comp_name)): return self._get_package_aliases(dep) raise ConanException("Component '{name}::{cname}' not found in '{name}' " "package requirement".format(name=dep.ref.name, @@ -329,10 +333,13 @@ def _get_package_name(self, dep): pkg_name = self.get_property("pkg_config_name", dep) or dep.ref.name return f"{pkg_name}{self._suffix}" - def _get_component_name(self, dep, comp_name): + def _get_component_name(self, dep, pkg_name, comp_name): if comp_name not in dep.cpp_info.components: - # foo::foo might be referencing the root cppinfo - if dep.ref.name == comp_name: + # Either foo::foo might be referencing the root cpp_info + if (dep.ref.name == comp_name or + # Or a "replace_require" is used and cpp_info.requires is the root one, e.g., + # zlib/*: zlib-ng/*, and self.cpp_info.requires = ["zlib::zlib"] + (dep.ref.name != pkg_name and pkg_name == comp_name)): return self._get_package_name(dep) raise ConanException("Component '{name}::{cname}' not found in '{name}' " "package requirement".format(name=dep.ref.name, diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py index b7b55957c42..d8e9b725764 100644 --- a/test/integration/graph/test_replace_requires.py +++ b/test/integration/graph/test_replace_requires.py @@ -438,17 +438,32 @@ class App(ConanFile): c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") assert "zlib/0.1: zlib-ng/0.1" in c.out + pc_content = c.load("app/ZLIB.pc") + assert textwrap.dedent("""\n + Name: ZLIB + Description: Conan package: ZLIB + Version: 0.1 + Libs: -L"${libdir}" -lzlib + Cflags: -I"${includedir}" + """) in pc_content + pc_content = c.load("app/openssl.pc") + assert textwrap.dedent("""\n + Name: openssl + Description: Conan package: openssl + Version: 0.1 + Libs: -L"${libdir}" -lcrypto + Cflags: -I"${includedir}" + Requires: ZLIB + """) in pc_content + cmake = c.load("app/ZLIB-Targets-release.cmake") assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake cmake = c.load("app/openssl-Targets-release.cmake") assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake - assert "target_link_libraries(openssl::openssl INTERFACE ZLIB::ZLIB)" in cmake - - pkg_config = c.load("app/ZLIB.pc") - print(pkg_config) - # TODO: Check here the contents of both ZLIB.pc and openssl.pc + assert "set_target_properties(openssl::openssl PROPERTIES INTERFACE_LINK_LIBRARIES\n" \ + ' "$<$:ZLIB::ZLIB>")' in cmake @pytest.mark.parametrize("diamond", [True, False]) def test_openssl_components(self, diamond): @@ -465,6 +480,7 @@ def package_info(self): self.cpp_info.location = "lib/zlib.lib" self.cpp_info.set_property("cmake_file_name", "ZLIB") self.cpp_info.set_property("cmake_target_name", "ZLIB::ZLIB") + self.cpp_info.set_property("pkg_config_name", "ZLIB") """) openssl = textwrap.dedent(""" from conan import ConanFile @@ -488,7 +504,7 @@ class App(ConanFile): settings = "build_type" requires = "openssl/0.1", {zlib} package_type = "application" - generators = "CMakeDeps" + generators = "CMakeDeps", "PkgConfigDeps" """) profile = textwrap.dedent(""" [settings] @@ -507,6 +523,18 @@ class App(ConanFile): c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") assert "zlib/0.1: zlib-ng/0.1" in c.out + pc_content = c.load("app/ZLIB.pc") + assert 'Libs: -L"${libdir}" -lzlib' in pc_content + pc_content = c.load("app/openssl-crypto.pc") + assert textwrap.dedent("""\n + Name: openssl-crypto + Description: Conan component: openssl-crypto + Version: 0.1 + Libs: -L"${libdir}" -lcrypto + Cflags: -I"${includedir}" + Requires: ZLIB + """) in pc_content + cmake = c.load("app/ZLIB-Targets-release.cmake") assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake From 3c27e00df292eb7ba8dde4b3f2a8c48561b55c96 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Tue, 21 Jan 2025 17:51:31 +0100 Subject: [PATCH 6/7] More tests --- .../graph/test_replace_requires.py | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py index d8e9b725764..2e6bd49f92a 100644 --- a/test/integration/graph/test_replace_requires.py +++ b/test/integration/graph/test_replace_requires.py @@ -439,22 +439,9 @@ class App(ConanFile): assert "zlib/0.1: zlib-ng/0.1" in c.out pc_content = c.load("app/ZLIB.pc") - assert textwrap.dedent("""\n - Name: ZLIB - Description: Conan package: ZLIB - Version: 0.1 - Libs: -L"${libdir}" -lzlib - Cflags: -I"${includedir}" - """) in pc_content + assert 'Libs: -L"${libdir}" -lzlib' in pc_content pc_content = c.load("app/openssl.pc") - assert textwrap.dedent("""\n - Name: openssl - Description: Conan package: openssl - Version: 0.1 - Libs: -L"${libdir}" -lcrypto - Cflags: -I"${includedir}" - Requires: ZLIB - """) in pc_content + assert 'Requires: ZLIB' in pc_content cmake = c.load("app/ZLIB-Targets-release.cmake") assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake @@ -526,14 +513,7 @@ class App(ConanFile): pc_content = c.load("app/ZLIB.pc") assert 'Libs: -L"${libdir}" -lzlib' in pc_content pc_content = c.load("app/openssl-crypto.pc") - assert textwrap.dedent("""\n - Name: openssl-crypto - Description: Conan component: openssl-crypto - Version: 0.1 - Libs: -L"${libdir}" -lcrypto - Cflags: -I"${includedir}" - Requires: ZLIB - """) in pc_content + assert 'Requires: ZLIB' in pc_content cmake = c.load("app/ZLIB-Targets-release.cmake") assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake @@ -559,6 +539,7 @@ def package_info(self): self.cpp_info.components["myzlib"].type = "static-library" self.cpp_info.components["myzlib"].location = "lib/zlib.lib" self.cpp_info.set_property("cmake_file_name", "ZLIB") + self.cpp_info.components["myzlib"].set_property("pkg_config_name", "ZLIB") self.cpp_info.components["myzlib"].set_property("cmake_target_name", "ZLIB::ZLIB") """) @@ -585,7 +566,7 @@ class App(ConanFile): settings = "build_type" requires = "openssl/0.1", {zlib} package_type = "application" - generators = "CMakeDeps" + generators = "CMakeDeps", "PkgConfigDeps" """) profile = textwrap.dedent(""" [settings] @@ -604,6 +585,13 @@ class App(ConanFile): c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") assert "zlib/0.1: zlib-ng/0.1" in c.out + pc_content = c.load("app/zlib-ng.pc") + assert 'Requires: ZLIB' in pc_content + pc_content = c.load("app/ZLIB.pc") + assert 'Libs: -L"${libdir}" -lzlib' in pc_content + pc_content = c.load("app/openssl.pc") + assert 'Requires: zlib-ng' in pc_content + cmake = c.load("app/ZLIB-Targets-release.cmake") assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake @@ -629,6 +617,7 @@ def package_info(self): self.cpp_info.components["myzlib"].type = "static-library" self.cpp_info.components["myzlib"].location = "lib/zlib.lib" self.cpp_info.set_property("cmake_file_name", "ZLIB") + self.cpp_info.components["myzlib"].set_property("pkg_config_name", "ZLIB") self.cpp_info.components["myzlib"].set_property("cmake_target_name", "ZLIB::ZLIB") """) @@ -657,7 +646,7 @@ class App(ConanFile): settings = "build_type" requires = "openssl/0.1", {zlib} package_type = "application" - generators = "CMakeDeps" + generators = "CMakeDeps", "PkgConfigDeps" """) profile = textwrap.dedent(""" [settings] @@ -676,6 +665,13 @@ class App(ConanFile): c.run("install app -pr=profile -c tools.cmake.cmakedeps:new=will_break_next") assert "zlib/0.1: zlib-ng/0.1" in c.out + pc_content = c.load("app/zlib-ng.pc") + assert 'Requires: ZLIB' in pc_content + pc_content = c.load("app/ZLIB.pc") + assert 'Libs: -L"${libdir}" -lzlib' in pc_content + pc_content = c.load("app/openssl-crypto.pc") + assert f'Requires: {"zlib-ng" if package_requires else "ZLIB"}' in pc_content + cmake = c.load("app/ZLIB-Targets-release.cmake") assert "add_library(ZLIB::ZLIB STATIC IMPORTED)" in cmake From 6ef8c1ca7c958a322475044d6184c4fb349fb2ee Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Tue, 21 Jan 2025 17:59:22 +0100 Subject: [PATCH 7/7] Minor changes --- conan/tools/gnu/pkgconfigdeps.py | 74 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py index 47b9a3245fb..205b2d8728e 100644 --- a/conan/tools/gnu/pkgconfigdeps.py +++ b/conan/tools/gnu/pkgconfigdeps.py @@ -46,14 +46,14 @@ class _PCContentGenerator: {% endif %} """) - def __init__(self, conanfile, pkg): + def __init__(self, conanfile, dep): self._conanfile = conanfile - self._pkg = pkg + self._dep = dep def _get_prefix_path(self): # If editable, package_folder can be None - root_folder = self._pkg.recipe_folder if self._pkg.package_folder is None \ - else self._pkg.package_folder + root_folder = self._dep.recipe_folder if self._dep.package_folder is None \ + else self._dep.package_folder return root_folder.replace("\\", "/") def _get_pc_variables(self, cpp_info): @@ -139,12 +139,12 @@ def content(self, info): class _PCGenerator: - def __init__(self, pkgconfigdeps, require, pkg): + def __init__(self, pkgconfigdeps, require, dep): self._conanfile = pkgconfigdeps._conanfile # noqa self._properties = pkgconfigdeps._properties # noqa self._require = require - self._pkg = pkg - self._transitive_reqs = get_transitive_requires(self._conanfile, pkg) + self._dep = dep + self._transitive_reqs = get_transitive_requires(self._conanfile, dep) self._is_build_context = require.build self._build_context_folder = pkgconfigdeps.build_context_folder self._suffix = pkgconfigdeps.build_context_suffix.get(require.ref.name, "") \ @@ -170,23 +170,23 @@ def package_info(self): self.cpp_info.components["cmp"].requires = ["other::cmp1"] ``` """ - pkg_ref_name = self._pkg.ref.name + dep_ref_name = self._dep.ref.name ret = [] for req in cpp_info.requires: - required_dep, required_comp = req.split("::") if "::" in req else (pkg_ref_name, req) + pkg_ref_name, comp_ref_name = req.split("::") if "::" in req else (dep_ref_name, req) # For instance, dep == "hello/1.0" and req == "other::cmp1" -> hello != other - if pkg_ref_name != required_dep: + if dep_ref_name != pkg_ref_name: try: - dep = self._transitive_reqs[required_dep] + req_conanfile = self._transitive_reqs[pkg_ref_name] except KeyError: continue # If the dependency is not in the transitive, might be skipped else: # For instance, dep == "hello/1.0" and req == "hello::cmp1" -> hello == hello - dep = self._pkg - comp_name = self._get_component_name(dep, required_dep, required_comp) + req_conanfile = self._dep + comp_name = self._get_component_name(req_conanfile, pkg_ref_name, comp_ref_name) if not comp_name: - pkg_name = self._get_package_name(dep) + pkg_name = self._get_package_name(req_conanfile) # Creating a component name with namespace, e.g., dep-comp1 - comp_name = self._get_name_with_namespace(pkg_name, required_comp) + comp_name = self._get_name_with_namespace(pkg_name, comp_ref_name) ret.append(comp_name) return ret @@ -197,23 +197,23 @@ def _components_info(self): :return: `list` of `_PCInfo` objects with all the components information """ - pkg_name = self._get_package_name(self._pkg) + pkg_name = self._get_package_name(self._dep) components_info = [] # Loop through all the package's components - for comp_ref_name, cpp_info in self._pkg.cpp_info.get_sorted_components().items(): + for comp_ref_name, cpp_info in self._dep.cpp_info.get_sorted_components().items(): # At first, let's check if we have defined some components requires, e.g., "dep::cmp1" comp_requires_names = self._get_cpp_info_requires_names(cpp_info) - comp_name = self._get_component_name(self._pkg, pkg_name, comp_ref_name) + comp_name = self._get_component_name(self._dep, pkg_name, comp_ref_name) if not comp_name: comp_name = self._get_name_with_namespace(pkg_name, comp_ref_name) comp_description = f"Conan component: {comp_name}" else: comp_description = f"Conan component: {pkg_name}-{comp_name}" - comp_aliases = self._get_component_aliases(self._pkg, pkg_name, comp_ref_name) - comp_version = (self.get_property("component_version", self._pkg, comp_ref_name) or - self.get_property("system_package_version", self._pkg, comp_ref_name) or - self._pkg.ref.version) - comp_custom_content = self.get_property("pkg_config_custom_content", self._pkg, comp_ref_name) + comp_aliases = self._get_component_aliases(self._dep, pkg_name, comp_ref_name) + comp_version = (self.get_property("component_version", self._dep, comp_ref_name) or + self.get_property("system_package_version", self._dep, comp_ref_name) or + self._dep.ref.version) + comp_custom_content = self.get_property("pkg_config_custom_content", self._dep, comp_ref_name) # Save each component information components_info.append(_PCInfo(comp_name, comp_version, comp_requires_names, comp_description, cpp_info, comp_aliases, comp_custom_content)) @@ -225,9 +225,9 @@ def _package_info(self): :return: `_PCInfo` object with the package information """ - pkg_name = self._get_package_name(self._pkg) + pkg_name = self._get_package_name(self._dep) # At first, let's check if we have defined some global requires, e.g., "other::cmp1" - requires = self._get_cpp_info_requires_names(self._pkg.cpp_info) + requires = self._get_cpp_info_requires_names(self._dep.cpp_info) # If we have found some component requires it would be enough if not requires: # If no requires were found, let's try to get all the direct visible dependencies, @@ -235,11 +235,11 @@ def _package_info(self): requires = [self._get_package_name(req) for req in self._transitive_reqs.values()] description = "Conan package: %s" % pkg_name - pkg_version = (self.get_property("system_package_version", self._pkg) - or self._pkg.ref.version) - aliases = self._get_package_aliases(self._pkg) - cpp_info = self._pkg.cpp_info - custom_content = self.get_property("pkg_config_custom_content", self._pkg) + pkg_version = (self.get_property("system_package_version", self._dep) + or self._dep.ref.version) + aliases = self._get_package_aliases(self._dep) + cpp_info = self._dep.cpp_info + custom_content = self.get_property("pkg_config_custom_content", self._dep) return _PCInfo(pkg_name, pkg_version, requires, description, cpp_info, aliases, custom_content) @property @@ -268,14 +268,14 @@ def _file_name(name): return f"{self._build_context_folder}/{name}.pc" if build else f"{name}.pc" def _add_pc_files(pc_info): - content_generator = _PCContentGenerator(self._conanfile, self._pkg) + content_generator = _PCContentGenerator(self._conanfile, self._dep) result = {_file_name(pc_info.name): content_generator.content(pc_info)} for alias in pc_info.aliases: result[_file_name(alias)] = alias_content(alias, pc_info.version, pc_info.name) return result # If the package has no components, then we have to calculate only the root pc file - if not self._pkg.cpp_info.has_components: + if not self._dep.cpp_info.has_components: pkg_pc_info = self._package_info() return _add_pc_files(pkg_pc_info) @@ -291,15 +291,15 @@ def _add_pc_files(pc_info): # Second, let's load the root package's PC file ONLY # if it does not already exist in components one # Issue related: https://github.com/conan-io/conan/issues/10341 - pkg_name = self._get_package_name(self._pkg) - pkg_version = (self.get_property("system_package_version", self._pkg) - or self._pkg.ref.version) + pkg_name = self._get_package_name(self._dep) + pkg_version = (self.get_property("system_package_version", self._dep) + or self._dep.ref.version) if f"{pkg_name}.pc" not in pc_files: package_info = _PCInfo(pkg_name, pkg_version, pkg_requires, f"Conan package: {pkg_name}", - self._pkg.cpp_info, self._get_package_aliases(self._pkg), - self.get_property("pkg_config_custom_content", self._pkg)) + self._dep.cpp_info, self._get_package_aliases(self._dep), + self.get_property("pkg_config_custom_content", self._dep)) pc_files.update(_add_pc_files(package_info)) return pc_files