Skip to content

[PkgConfigDeps][MesonToolchain] Added build context folder mechanism #15813

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

Merged
Show file tree
Hide file tree
Changes from 6 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
62 changes: 39 additions & 23 deletions conan/tools/gnu/pkgconfigdeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,16 @@ def content(self, info):

class _PCGenerator:

def __init__(self, conanfile, require, dep, build_context_suffix=None):
self._conanfile = conanfile
def __init__(self, pkgconfigdeps, require, dep):
self._conanfile = pkgconfigdeps._conanfile # noqa
self._require = require
self._dep = dep
self._content_generator = _PCContentGenerator(self._conanfile, self._dep)
self._transitive_reqs = get_transitive_requires(self._conanfile, dep)
self._suffix = "" if not build_context_suffix or not require.build else \
build_context_suffix.get(require.ref.name, "")
self._is_build_context = require.build
self._build_context_folder = pkgconfigdeps.build_context_folder
self._suffix = pkgconfigdeps.build_context_suffix.get(require.ref.name, "") \
if self._is_build_context else ""

def _get_cpp_info_requires_names(self, cpp_info):
"""
Expand Down Expand Up @@ -249,11 +251,22 @@ def pc_files(self):

* Apart from those PC files, if there are any aliases declared, they will be created too.
"""
def _fill_pc_files(pc_info):
content = self._content_generator.content(pc_info)
# If no suffix is defined, we can save the *.pc file in the build_context_folder
if self._is_build_context and self._build_context_folder and not self._suffix:
# Issue: https://github.com/conan-io/conan/issues/12342
# Issue: https://github.com/conan-io/conan/issues/14935
pc_files[f"{self._build_context_folder}/{pc_info.name}.pc"] = content
else:
# Saving also the suffixed names as usual
pc_files[f"{pc_info.name}.pc"] = content

def _update_pc_files(info):
pc_files[f"{info.name}.pc"] = self._content_generator.content(info)
_fill_pc_files(info)
for alias in info.aliases:
alias_info = _PCInfo(alias, [info.name], f"Alias {alias} for {info.name}", None, [])
pc_files[f"{alias}.pc"] = self._content_generator.content(alias_info)
_fill_pc_files(alias_info)

pc_files = {}
# If the package has no components, then we have to calculate only the root pc file
Expand All @@ -277,13 +290,7 @@ def _update_pc_files(info):
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, self._get_package_aliases(self._dep))
# It'll be enough creating a shortened PC file. This file will be like an alias
pc_files[f"{package_info.name}.pc"] = self._content_generator.content(package_info)
for alias in package_info.aliases:
alias_info = _PCInfo(alias, [package_info.name],
f"Alias {alias} for {package_info.name}", None, [])
pc_files[f"{alias}.pc"] = self._content_generator.content(alias_info)

_update_pc_files(package_info)
return pc_files

@staticmethod
Expand Down Expand Up @@ -333,7 +340,16 @@ def __init__(self, conanfile):
self.build_context_activated = []
# If specified, the files/requires/names for the build context will be renamed appending
# a suffix. It is necessary in case of same require and build_require and will cause an error
# DEPRECATED: consumers should use build_context_folder instead
self.build_context_suffix = {}
# By default, the "[generators_folder]/build" folder will save all the *.pc files activated
# in the build_context_activated list.
# Notice that if the `build_context_suffix` attr is defined, the `build_context_folder` one
# will have no effect.
# Issue: https://github.com/conan-io/conan/issues/12342
# Issue: https://github.com/conan-io/conan/issues/14935
# FIXME: Conan 3.x: build_context_folder = "build" by default
self.build_context_folder = None # Keeping backward-compatibility

def _validate_build_requires(self, host_req, build_req):
"""
Expand All @@ -347,7 +363,7 @@ def _validate_build_requires(self, host_req, build_req):
if r.ref.name in self.build_context_activated}
common_names = {r.ref.name for r in host_req.values()}.intersection(activated_br)
without_suffixes = [common_name for common_name in common_names
if self.build_context_suffix.get(common_name) is None]
if not self.build_context_suffix.get(common_name)]
if without_suffixes:
raise ConanException(f"The packages {without_suffixes} exist both as 'require' and as"
f" 'build require'. You need to specify a suffix using the "
Expand All @@ -363,20 +379,20 @@ def content(self):
host_req = self._conanfile.dependencies.host
build_req = self._conanfile.dependencies.build # tool_requires
test_req = self._conanfile.dependencies.test

# Check if it exists both as require and as build require without a suffix
self._validate_build_requires(host_req, build_req)
# If self.build_context_suffix is not defined, the build requires will be saved
# in the self.build_context_folder
if self.build_context_suffix:
self._conanfile.output.warning("build_context_suffix attribute has been deprecated. Use the"
" build_context_folder one instead.")
# Check if it exists both as require and as build require without a suffix
self._validate_build_requires(host_req, build_req)

for require, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
# Require is not used at the moment, but its information could be used,
# and will be used in Conan 2.0
# Filter the build_requires not activated with PkgConfigDeps.build_context_activated
if require.build and dep.ref.name not in self.build_context_activated:
continue

pc_generator = _PCGenerator(self._conanfile, require, dep,
build_context_suffix=self.build_context_suffix)
pc_files.update(pc_generator.pc_files)
# Save all the *.pc files and their contents
pc_files.update(_PCGenerator(self, require, dep).pc_files)
return pc_files

def generate(self):
Expand Down
8 changes: 7 additions & 1 deletion conan/tools/meson/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class MesonToolchain(object):
{% if cpp_std %}cpp_std = '{{cpp_std}}' {% endif %}
{% if backend %}backend = '{{backend}}' {% endif %}
{% if pkg_config_path %}pkg_config_path = '{{pkg_config_path}}'{% endif %}
{% if build_pkg_config_path %}build.pkg_config_path = '{{build_pkg_config_path}}'{% endif %}
# C/C++ arguments
c_args = {{c_args}} + preprocessor_definitions
c_link_args = {{c_link_args}}
Expand Down Expand Up @@ -145,7 +146,10 @@ def __init__(self, conanfile, backend=None):

#: Defines the Meson ``pkg_config_path`` variable
self.pkg_config_path = self._conanfile.generators_folder

#: Defines the Meson ``build.pkg_config_path`` variable (build context)
# Issue: https://github.com/conan-io/conan/issues/12342
# Issue: https://github.com/conan-io/conan/issues/14935
self.build_pkg_config_path = None
self.libcxx, self.gcc_cxx11_abi = libcxx_flags(self._conanfile)

#: Dict-like object with the build, host, and target as the Meson machine context
Expand Down Expand Up @@ -175,6 +179,7 @@ def __init__(self, conanfile, backend=None):
elif compiler == "gcc":
default_comp = "gcc"
default_comp_cpp = "g++"

if "Visual" in compiler or compiler == "msvc":
default_comp = "cl"
default_comp_cpp = "cl"
Expand Down Expand Up @@ -422,6 +427,7 @@ def _sanitize_format(v):
"objcpp_args": to_meson_value(self._filter_list_empty_fields(self.objcpp_args)),
"objcpp_link_args": to_meson_value(self._filter_list_empty_fields(self.objcpp_link_args)),
"pkg_config_path": self.pkg_config_path,
"build_pkg_config_path": self.build_pkg_config_path,
"preprocessor_definitions": self.preprocessor_definitions,
"cross_build": self.cross_build,
"is_apple_system": self._is_apple_system
Expand Down
69 changes: 69 additions & 0 deletions conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import os
import textwrap

import pytest

from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient
from conans.util.files import load
Expand Down Expand Up @@ -756,6 +758,7 @@ def build_requirements(self):
def generate(self):
tc = PkgConfigDeps(self)
tc.build_context_activated = ["tool"]
tc.build_context_suffix = {"tool": ""} # No suffix defined
tc.generate()
""")
client.save({"conanfile.py": conanfile}, clean_first=True)
Expand Down Expand Up @@ -878,6 +881,13 @@ def build(self):
assert os.path.exists("wayland_BUILD-server.pc")
assert os.path.exists("dep.pc")
assert os.path.exists("dep_BUILD.pc")

# Issue: https://github.com/conan-io/conan/issues/12342
# Issue: https://github.com/conan-io/conan/issues/14935
assert not os.path.exists("build/wayland.pc")
assert not os.path.exists("build/wayland-client.pc")
assert not os.path.exists("build/wayland-server.pc")
assert not os.path.exists("build/dep.pc")
""")
wayland = textwrap.dedent("""
from conan import ConanFile
Expand All @@ -904,6 +914,65 @@ def package_info(self):
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

@pytest.mark.parametrize("build_folder_name", ["build", ""])
def test_pc_generate_components_in_build_context_folder(self, build_folder_name):
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_folder = "{build_folder_name}"
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("dep.pc")

# Issue: https://github.com/conan-io/conan/issues/12342
# Issue: https://github.com/conan-io/conan/issues/14935
if "{build_folder_name}":
assert os.path.exists("{build_folder_name}/wayland.pc")
assert os.path.exists("{build_folder_name}/wayland-client.pc")
assert os.path.exists("{build_folder_name}/wayland-server.pc")
assert os.path.exists("{build_folder_name}/dep.pc")
""".format(build_folder_name=build_folder_name))
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():
"""
Expand Down
29 changes: 29 additions & 0 deletions conans/test/integration/toolchains/meson/test_mesontoolchain.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import platform
import textwrap

Expand Down Expand Up @@ -280,3 +281,31 @@ def test_check_c_cpp_ld_list_formats():
assert "c = ['aarch64-poky-linux-gcc', '-mcpu=cortex-a53', '-march=armv8-a+crc+crypto']" in content
assert "cpp = ['aarch64-poky-linux-g++', '-mcpu=cortex-a53', '-march=armv8-a+crc+crypto']" in content
assert "ld = ['aarch64-poky-linux-ld', '--sysroot=/opt/sysroots/cortexa53-crypto-poky-linux']" in content


def test_check_pkg_config_paths():
# Issue: https://github.com/conan-io/conan/issues/12342
# Issue: https://github.com/conan-io/conan/issues/14935
t = TestClient()
t.save({"conanfile.txt": "[generators]\nMesonToolchain"})
t.run("install .")
content = t.load(MesonToolchain.native_filename)
assert f"pkg_config_path = '{t.current_folder}'" in content
assert f"build.pkg_config_path = " not in content
conanfile = textwrap.dedent("""
import os
from conan import ConanFile
from conan.tools.meson import MesonToolchain
class Pkg(ConanFile):
settings = "os", "compiler", "arch", "build_type"
def generate(self):
tc = MesonToolchain(self)
tc.build_pkg_config_path = os.path.join(self.generators_folder, "build")
tc.generate()
""")
t.save({"conanfile.py": conanfile}, clean_first=True)
t.run("install .")
content = t.load(MesonToolchain.native_filename)
base_folder = t.current_folder
assert f"pkg_config_path = '{base_folder}'" in content
assert f"build.pkg_config_path = '{os.path.join(base_folder, 'build')}'" in content