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

fix PkgConfigDeps in build context #15763

Merged
merged 1 commit into from
Feb 27, 2024
Merged
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
35 changes: 19 additions & 16 deletions conan/tools/gnu/pkgconfigdeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 👏

It has to be solved for BazelDeps too.

return ""
return build_context_suffix.get(req.ref.name, "")

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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}"
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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

Expand Down
97 changes: 97 additions & 0 deletions conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down