diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py index b0c93aad17b..8f5a83e1e7b 100644 --- a/conan/tools/gnu/pkgconfigdeps.py +++ b/conan/tools/gnu/pkgconfigdeps.py @@ -43,22 +43,22 @@ def _get_component_aliases(dep, comp_name): return comp_aliases or [] -def _get_package_name(dep, build_context_suffix=None): +def _get_package_name(req, dep, build_context_suffix=None): pkg_name = dep.cpp_info.get_property("pkg_config_name") or _get_package_reference_name(dep) - suffix = _get_suffix(dep, build_context_suffix) + suffix = _get_suffix(req, build_context_suffix) return f"{pkg_name}{suffix}" -def _get_component_name(dep, comp_name, build_context_suffix=None): +def _get_component_name(req, dep, comp_name, build_context_suffix=None): if comp_name not in dep.cpp_info.components: # foo::foo might be referencing the root cppinfo if _get_package_reference_name(dep) == comp_name: - return _get_package_name(dep, build_context_suffix) + return _get_package_name(req, dep, build_context_suffix) raise ConanException("Component '{name}::{cname}' not found in '{name}' " "package requirement".format(name=_get_package_reference_name(dep), cname=comp_name)) comp_name = dep.cpp_info.components[comp_name].get_property("pkg_config_name") - suffix = _get_suffix(dep, build_context_suffix) + suffix = _get_suffix(req, build_context_suffix) return f"{comp_name}{suffix}" if comp_name else None @@ -67,11 +67,11 @@ def _get_suffix(req, build_context_suffix=None): Get the package name suffix coming from PkgConfigDeps.build_context_suffix attribute, but only for requirements declared as build requirement. - :param req: requirement ConanFile instance + :param req: requirement :param build_context_suffix: `dict` with all the suffixes :return: `str` with the suffix """ - if not build_context_suffix or not req.is_build_context: + if not build_context_suffix or not req.build: return "" return build_context_suffix.get(req.ref.name, "") @@ -201,8 +201,9 @@ def content(self, info): class _PCGenerator: - def __init__(self, conanfile, dep, build_context_suffix=None): + def __init__(self, conanfile, require, dep, build_context_suffix=None): self._conanfile = conanfile + self._require = require self._build_context_suffix = build_context_suffix or {} self._dep = dep self._content_generator = _PCContentGenerator(self._conanfile, self._dep) @@ -240,9 +241,9 @@ def package_info(self): 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 = _get_component_name(req_conanfile, comp_ref_name, self._build_context_suffix) + comp_name = _get_component_name(self._require, req_conanfile, comp_ref_name, self._build_context_suffix) if not comp_name: - pkg_name = _get_package_name(req_conanfile, self._build_context_suffix) + pkg_name = _get_package_name(self._require, req_conanfile, self._build_context_suffix) # Creating a component name with namespace, e.g., dep-comp1 comp_name = _get_name_with_namespace(pkg_name, comp_ref_name) ret.append(comp_name) @@ -256,13 +257,14 @@ def components_info(self): :return: `list` of `_PCInfo` objects with all the components information """ - pkg_name = _get_package_name(self._dep, self._build_context_suffix) + pkg_name = _get_package_name(self._require, self._dep, self._build_context_suffix) components_info = [] # Loop through all the package's components 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 = _get_component_name(self._dep, comp_ref_name, self._build_context_suffix) + comp_name = _get_component_name(self._require, self._dep, comp_ref_name, + self._build_context_suffix) if not comp_name: comp_name = _get_name_with_namespace(pkg_name, comp_ref_name) comp_description = f"Conan component: {comp_name}" @@ -281,14 +283,14 @@ def package_info(self): :return: `_PCInfo` object with the package information """ - pkg_name = _get_package_name(self._dep, self._build_context_suffix) + pkg_name = _get_package_name(self._require, self._dep, self._build_context_suffix) # 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) # 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, # e.g., requires = "other_pkg/1.0" - requires = [_get_package_name(req, self._build_context_suffix) + requires = [_get_package_name(self._require, req, self._build_context_suffix) for req in self._transitive_reqs.values()] description = "Conan package: %s" % pkg_name aliases = _get_package_aliases(self._dep) @@ -337,7 +339,7 @@ def _update_pc_files(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 = _get_package_name(self._dep, self._build_context_suffix) + pkg_name = _get_package_name(self._require, self._dep, self._build_context_suffix) if f"{pkg_name}.pc" not in pc_files: package_info = _PCInfo(pkg_name, pkg_requires, f"Conan package: {pkg_name}", self._dep.cpp_info, _get_package_aliases(self._dep)) @@ -400,7 +402,8 @@ def content(self): if require.build and dep.ref.name not in self.build_context_activated: continue - pc_generator = _PCGenerator(self._conanfile, dep, build_context_suffix=self.build_context_suffix) + pc_generator = _PCGenerator(self._conanfile, require, dep, + build_context_suffix=self.build_context_suffix) pc_files.update(pc_generator.pc_files) return pc_files diff --git a/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py b/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py index fee4f70c7da..201812eb333 100644 --- a/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py +++ b/conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py @@ -808,6 +808,103 @@ def build(self): c.assert_listed_require({"example/1.0": "Cache"}, build=True) +class TestPCGenerationBuildContext: + """ + https://github.com/conan-io/conan/issues/14920 + """ + def test_pc_generate(self): + c = TestClient() + tool = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.gnu import PkgConfigDeps + + class Example(ConanFile): + name = "tool" + version = "1.0" + requires = "wayland/1.0" + tool_requires = "wayland/1.0" + + def generate(self): + deps = PkgConfigDeps(self) + deps.build_context_activated = ["wayland", "dep"] + deps.build_context_suffix = {"wayland": "_BUILD", "dep": "_BUILD"} + deps.generate() + + def build(self): + assert os.path.exists("wayland.pc") + assert os.path.exists("wayland_BUILD.pc") + assert os.path.exists("dep.pc") + assert os.path.exists("dep_BUILD.pc") + """) + c.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_package_type("shared-library"), + "wayland/conanfile.py": GenConanfile("wayland", "1.0").with_requires("dep/1.0"), + "tool/conanfile.py": tool, + "app/conanfile.py": GenConanfile().with_tool_requires("tool/1.0")}) + c.run("export dep") + c.run("export wayland") + c.run("export tool") + c.run("install app --build=missing") + assert "Install finished successfully" in c.out # the asserts in build() didn't fail + # Now make sure we can actually build with build!=host context + c.run("install app -s:h build_type=Debug --build=missing") + assert "Install finished successfully" in c.out # the asserts in build() didn't fail + + def test_pc_generate_components(self): + c = TestClient() + tool = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.gnu import PkgConfigDeps + + class Example(ConanFile): + name = "tool" + version = "1.0" + requires = "wayland/1.0" + tool_requires = "wayland/1.0" + + def generate(self): + deps = PkgConfigDeps(self) + deps.build_context_activated = ["wayland", "dep"] + deps.build_context_suffix = {"wayland": "_BUILD", "dep": "_BUILD"} + deps.generate() + + def build(self): + assert os.path.exists("wayland.pc") + assert os.path.exists("wayland-client.pc") + assert os.path.exists("wayland-server.pc") + assert os.path.exists("wayland_BUILD.pc") + assert os.path.exists("wayland_BUILD-client.pc") + assert os.path.exists("wayland_BUILD-server.pc") + assert os.path.exists("dep.pc") + assert os.path.exists("dep_BUILD.pc") + """) + wayland = textwrap.dedent(""" + from conan import ConanFile + + class Pkg(ConanFile): + name = "wayland" + version = "1.0" + requires = "dep/1.0" + + def package_info(self): + self.cpp_info.components["client"].libs = [] + self.cpp_info.components["server"].libs = [] + """) + c.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_package_type("shared-library"), + "wayland/conanfile.py": wayland, + "tool/conanfile.py": tool, + "app/conanfile.py": GenConanfile().with_tool_requires("tool/1.0")}) + c.run("export dep") + c.run("export wayland") + c.run("export tool") + c.run("install app --build=missing") + assert "Install finished successfully" in c.out # the asserts in build() didn't fail + # Now make sure we can actually build with build!=host context + c.run("install app -s:h build_type=Debug --build=missing") + assert "Install finished successfully" in c.out # the asserts in build() didn't fail + + def test_pkg_config_deps_and_private_deps(): """ Testing that no errors are raised when the dependency tree has a private one in the middle