Skip to content

Commit b5dfad1

Browse files
[PkgConfigDeps][MesonToolchain] Added build context folder mechanism (#15813)
* Added build.pkg_config_path in MesonToolchain. Added build_context_folder and its mechanism in PkgConfigDeps. Added tests * Fix * Applying when naitve = true only * Removed default value * Improved test * Defaulting to None (backward-compatible) * Changed condition * Checking deprecation warning and error
1 parent a87d255 commit b5dfad1

File tree

4 files changed

+192
-25
lines changed

4 files changed

+192
-25
lines changed

conan/tools/gnu/pkgconfigdeps.py

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,16 @@ def content(self, info):
137137

138138
class _PCGenerator:
139139

140-
def __init__(self, conanfile, require, dep, build_context_suffix=None):
141-
self._conanfile = conanfile
140+
def __init__(self, pkgconfigdeps, require, dep):
141+
self._conanfile = pkgconfigdeps._conanfile # noqa
142142
self._require = require
143143
self._dep = dep
144144
self._content_generator = _PCContentGenerator(self._conanfile, self._dep)
145145
self._transitive_reqs = get_transitive_requires(self._conanfile, dep)
146-
self._suffix = "" if not build_context_suffix or not require.build else \
147-
build_context_suffix.get(require.ref.name, "")
146+
self._is_build_context = require.build
147+
self._build_context_folder = pkgconfigdeps.build_context_folder
148+
self._suffix = pkgconfigdeps.build_context_suffix.get(require.ref.name, "") \
149+
if self._is_build_context else ""
148150

149151
def _get_cpp_info_requires_names(self, cpp_info):
150152
"""
@@ -249,11 +251,22 @@ def pc_files(self):
249251
250252
* Apart from those PC files, if there are any aliases declared, they will be created too.
251253
"""
254+
def _fill_pc_files(pc_info):
255+
content = self._content_generator.content(pc_info)
256+
# If no suffix is defined, we can save the *.pc file in the build_context_folder
257+
if self._is_build_context and self._build_context_folder and not self._suffix:
258+
# Issue: https://github.com/conan-io/conan/issues/12342
259+
# Issue: https://github.com/conan-io/conan/issues/14935
260+
pc_files[f"{self._build_context_folder}/{pc_info.name}.pc"] = content
261+
else:
262+
# Saving also the suffixed names as usual
263+
pc_files[f"{pc_info.name}.pc"] = content
264+
252265
def _update_pc_files(info):
253-
pc_files[f"{info.name}.pc"] = self._content_generator.content(info)
266+
_fill_pc_files(info)
254267
for alias in info.aliases:
255268
alias_info = _PCInfo(alias, [info.name], f"Alias {alias} for {info.name}", None, [])
256-
pc_files[f"{alias}.pc"] = self._content_generator.content(alias_info)
269+
_fill_pc_files(alias_info)
257270

258271
pc_files = {}
259272
# If the package has no components, then we have to calculate only the root pc file
@@ -277,13 +290,7 @@ def _update_pc_files(info):
277290
if f"{pkg_name}.pc" not in pc_files:
278291
package_info = _PCInfo(pkg_name, pkg_requires, f"Conan package: {pkg_name}",
279292
self._dep.cpp_info, self._get_package_aliases(self._dep))
280-
# It'll be enough creating a shortened PC file. This file will be like an alias
281-
pc_files[f"{package_info.name}.pc"] = self._content_generator.content(package_info)
282-
for alias in package_info.aliases:
283-
alias_info = _PCInfo(alias, [package_info.name],
284-
f"Alias {alias} for {package_info.name}", None, [])
285-
pc_files[f"{alias}.pc"] = self._content_generator.content(alias_info)
286-
293+
_update_pc_files(package_info)
287294
return pc_files
288295

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

338355
def _validate_build_requires(self, host_req, build_req):
339356
"""
@@ -347,7 +364,7 @@ def _validate_build_requires(self, host_req, build_req):
347364
if r.ref.name in self.build_context_activated}
348365
common_names = {r.ref.name for r in host_req.values()}.intersection(activated_br)
349366
without_suffixes = [common_name for common_name in common_names
350-
if self.build_context_suffix.get(common_name) is None]
367+
if not self.build_context_suffix.get(common_name)]
351368
if without_suffixes:
352369
raise ConanException(f"The packages {without_suffixes} exist both as 'require' and as"
353370
f" 'build require'. You need to specify a suffix using the "
@@ -363,20 +380,26 @@ def content(self):
363380
host_req = self._conanfile.dependencies.host
364381
build_req = self._conanfile.dependencies.build # tool_requires
365382
test_req = self._conanfile.dependencies.test
366-
367-
# Check if it exists both as require and as build require without a suffix
368-
self._validate_build_requires(host_req, build_req)
383+
# If self.build_context_suffix is not defined, the build requires will be saved
384+
# in the self.build_context_folder
385+
# FIXME: Conan 3.x: Remove build_context_suffix attribute and the validation function
386+
if self.build_context_folder is None: # Legacy flow
387+
if self.build_context_suffix:
388+
# deprecation warning
389+
self._conanfile.output.warning("PkgConfigDeps.build_context_suffix attribute has been "
390+
"deprecated. Use PkgConfigDeps.build_context_folder instead.")
391+
# Check if it exists both as require and as build require without a suffix
392+
self._validate_build_requires(host_req, build_req)
393+
elif self.build_context_folder is not None and self.build_context_suffix:
394+
raise ConanException("It's not allowed to define both PkgConfigDeps.build_context_folder "
395+
"and PkgConfigDeps.build_context_suffix (deprecated).")
369396

370397
for require, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
371-
# Require is not used at the moment, but its information could be used,
372-
# and will be used in Conan 2.0
373398
# Filter the build_requires not activated with PkgConfigDeps.build_context_activated
374399
if require.build and dep.ref.name not in self.build_context_activated:
375400
continue
376-
377-
pc_generator = _PCGenerator(self._conanfile, require, dep,
378-
build_context_suffix=self.build_context_suffix)
379-
pc_files.update(pc_generator.pc_files)
401+
# Save all the *.pc files and their contents
402+
pc_files.update(_PCGenerator(self, require, dep).pc_files)
380403
return pc_files
381404

382405
def generate(self):

conan/tools/meson/toolchain.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class MesonToolchain(object):
6565
{% if cpp_std %}cpp_std = '{{cpp_std}}' {% endif %}
6666
{% if backend %}backend = '{{backend}}' {% endif %}
6767
{% if pkg_config_path %}pkg_config_path = '{{pkg_config_path}}'{% endif %}
68+
{% if build_pkg_config_path %}build.pkg_config_path = '{{build_pkg_config_path}}'{% endif %}
6869
# C/C++ arguments
6970
c_args = {{c_args}} + preprocessor_definitions
7071
c_link_args = {{c_link_args}}
@@ -145,7 +146,10 @@ def __init__(self, conanfile, backend=None):
145146

146147
#: Defines the Meson ``pkg_config_path`` variable
147148
self.pkg_config_path = self._conanfile.generators_folder
148-
149+
#: Defines the Meson ``build.pkg_config_path`` variable (build context)
150+
# Issue: https://github.com/conan-io/conan/issues/12342
151+
# Issue: https://github.com/conan-io/conan/issues/14935
152+
self.build_pkg_config_path = None
149153
self.libcxx, self.gcc_cxx11_abi = libcxx_flags(self._conanfile)
150154

151155
#: Dict-like object with the build, host, and target as the Meson machine context
@@ -175,6 +179,7 @@ def __init__(self, conanfile, backend=None):
175179
elif compiler == "gcc":
176180
default_comp = "gcc"
177181
default_comp_cpp = "g++"
182+
178183
if "Visual" in compiler or compiler == "msvc":
179184
default_comp = "cl"
180185
default_comp_cpp = "cl"
@@ -422,6 +427,7 @@ def _sanitize_format(v):
422427
"objcpp_args": to_meson_value(self._filter_list_empty_fields(self.objcpp_args)),
423428
"objcpp_link_args": to_meson_value(self._filter_list_empty_fields(self.objcpp_link_args)),
424429
"pkg_config_path": self.pkg_config_path,
430+
"build_pkg_config_path": self.build_pkg_config_path,
425431
"preprocessor_definitions": self.preprocessor_definitions,
426432
"cross_build": self.cross_build,
427433
"is_apple_system": self._is_apple_system

conans/test/integration/toolchains/gnu/test_pkgconfigdeps.py

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import os
33
import textwrap
44

5+
import pytest
6+
57
from conans.test.assets.genconanfile import GenConanfile
68
from conans.test.utils.tools import TestClient
79
from conans.util.files import load
@@ -723,7 +725,7 @@ def build_requirements(self):
723725
assert pc_files == []
724726

725727

726-
def test_tool_requires_raise_exception_if_exist_both_require_and_build_one():
728+
def test_tool_requires_error_if_no_build_suffix():
727729
"""
728730
Testing if same dependency exists in both require and build require (without suffix)
729731
"""
@@ -846,6 +848,8 @@ def build(self):
846848
c.run("export tool")
847849
c.run("install app --build=missing")
848850
assert "Install finished successfully" in c.out # the asserts in build() didn't fail
851+
# Deprecation warning!
852+
assert "PkgConfigDeps.build_context_suffix attribute has been deprecated" in c.out
849853
# Now make sure we can actually build with build!=host context
850854
c.run("install app -s:h build_type=Debug --build=missing")
851855
assert "Install finished successfully" in c.out # the asserts in build() didn't fail
@@ -878,6 +882,13 @@ def build(self):
878882
assert os.path.exists("wayland_BUILD-server.pc")
879883
assert os.path.exists("dep.pc")
880884
assert os.path.exists("dep_BUILD.pc")
885+
886+
# Issue: https://github.com/conan-io/conan/issues/12342
887+
# Issue: https://github.com/conan-io/conan/issues/14935
888+
assert not os.path.exists("build/wayland.pc")
889+
assert not os.path.exists("build/wayland-client.pc")
890+
assert not os.path.exists("build/wayland-server.pc")
891+
assert not os.path.exists("build/dep.pc")
881892
""")
882893
wayland = textwrap.dedent("""
883894
from conan import ConanFile
@@ -904,6 +915,104 @@ def package_info(self):
904915
c.run("install app -s:h build_type=Debug --build=missing")
905916
assert "Install finished successfully" in c.out # the asserts in build() didn't fail
906917

918+
@pytest.mark.parametrize("build_folder_name", ["build", ""])
919+
def test_pc_generate_components_in_build_context_folder(self, build_folder_name):
920+
c = TestClient()
921+
tool = textwrap.dedent("""
922+
import os
923+
from conan import ConanFile
924+
from conan.tools.gnu import PkgConfigDeps
925+
926+
class Example(ConanFile):
927+
name = "tool"
928+
version = "1.0"
929+
requires = "wayland/1.0"
930+
tool_requires = "wayland/1.0"
931+
932+
def generate(self):
933+
deps = PkgConfigDeps(self)
934+
deps.build_context_activated = ["wayland", "dep"]
935+
deps.build_context_folder = "{build_folder_name}"
936+
deps.generate()
937+
938+
def build(self):
939+
assert os.path.exists("wayland.pc")
940+
assert os.path.exists("wayland-client.pc")
941+
assert os.path.exists("wayland-server.pc")
942+
assert os.path.exists("dep.pc")
943+
944+
# Issue: https://github.com/conan-io/conan/issues/12342
945+
# Issue: https://github.com/conan-io/conan/issues/14935
946+
if "{build_folder_name}":
947+
assert os.path.exists("{build_folder_name}/wayland.pc")
948+
assert os.path.exists("{build_folder_name}/wayland-client.pc")
949+
assert os.path.exists("{build_folder_name}/wayland-server.pc")
950+
assert os.path.exists("{build_folder_name}/dep.pc")
951+
""".format(build_folder_name=build_folder_name))
952+
wayland = textwrap.dedent("""
953+
from conan import ConanFile
954+
955+
class Pkg(ConanFile):
956+
name = "wayland"
957+
version = "1.0"
958+
requires = "dep/1.0"
959+
960+
def package_info(self):
961+
self.cpp_info.components["client"].libs = []
962+
self.cpp_info.components["server"].libs = []
963+
""")
964+
c.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_package_type("shared-library"),
965+
"wayland/conanfile.py": wayland,
966+
"tool/conanfile.py": tool,
967+
"app/conanfile.py": GenConanfile().with_tool_requires("tool/1.0")})
968+
c.run("export dep")
969+
c.run("export wayland")
970+
c.run("export tool")
971+
c.run("install app --build=missing")
972+
assert "Install finished successfully" in c.out # the asserts in build() didn't fail
973+
# Now make sure we can actually build with build!=host context
974+
c.run("install app -s:h build_type=Debug --build=missing")
975+
assert "Install finished successfully" in c.out # the asserts in build() didn't fail
976+
977+
def test_tool_requires_error_if_folder_and_suffix(self):
978+
client = TestClient()
979+
conanfile = textwrap.dedent("""
980+
from conan import ConanFile
981+
982+
class PkgConfigConan(ConanFile):
983+
984+
def package_info(self):
985+
self.cpp_info.libs = ["libtool"]
986+
""")
987+
client.save({"conanfile.py": conanfile})
988+
client.run("create . --name tool --version 1.0")
989+
990+
conanfile = textwrap.dedent("""
991+
from conan import ConanFile
992+
from conan.tools.gnu import PkgConfigDeps
993+
994+
class PkgConfigConan(ConanFile):
995+
name = "demo"
996+
version = "1.0"
997+
998+
def requirements(self):
999+
self.requires("tool/1.0")
1000+
1001+
def build_requirements(self):
1002+
self.build_requires("tool/1.0")
1003+
1004+
def generate(self):
1005+
tc = PkgConfigDeps(self)
1006+
tc.build_context_activated = ["tool"]
1007+
tc.build_context_folder = "build"
1008+
tc.build_context_suffix = {"tool": "_bt"}
1009+
tc.generate()
1010+
""")
1011+
client.save({"conanfile.py": conanfile}, clean_first=True)
1012+
client.run("install . -pr:h default -pr:b default", assert_error=True)
1013+
assert ("It's not allowed to define both PkgConfigDeps.build_context_folder "
1014+
"and PkgConfigDeps.build_context_suffix (deprecated).") in client.out
1015+
9071016

9081017
def test_pkg_config_deps_and_private_deps():
9091018
"""

conans/test/integration/toolchains/meson/test_mesontoolchain.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import platform
23
import textwrap
34

@@ -280,3 +281,31 @@ def test_check_c_cpp_ld_list_formats():
280281
assert "c = ['aarch64-poky-linux-gcc', '-mcpu=cortex-a53', '-march=armv8-a+crc+crypto']" in content
281282
assert "cpp = ['aarch64-poky-linux-g++', '-mcpu=cortex-a53', '-march=armv8-a+crc+crypto']" in content
282283
assert "ld = ['aarch64-poky-linux-ld', '--sysroot=/opt/sysroots/cortexa53-crypto-poky-linux']" in content
284+
285+
286+
def test_check_pkg_config_paths():
287+
# Issue: https://github.com/conan-io/conan/issues/12342
288+
# Issue: https://github.com/conan-io/conan/issues/14935
289+
t = TestClient()
290+
t.save({"conanfile.txt": "[generators]\nMesonToolchain"})
291+
t.run("install .")
292+
content = t.load(MesonToolchain.native_filename)
293+
assert f"pkg_config_path = '{t.current_folder}'" in content
294+
assert f"build.pkg_config_path = " not in content
295+
conanfile = textwrap.dedent("""
296+
import os
297+
from conan import ConanFile
298+
from conan.tools.meson import MesonToolchain
299+
class Pkg(ConanFile):
300+
settings = "os", "compiler", "arch", "build_type"
301+
def generate(self):
302+
tc = MesonToolchain(self)
303+
tc.build_pkg_config_path = os.path.join(self.generators_folder, "build")
304+
tc.generate()
305+
""")
306+
t.save({"conanfile.py": conanfile}, clean_first=True)
307+
t.run("install .")
308+
content = t.load(MesonToolchain.native_filename)
309+
base_folder = t.current_folder
310+
assert f"pkg_config_path = '{base_folder}'" in content
311+
assert f"build.pkg_config_path = '{os.path.join(base_folder, 'build')}'" in content

0 commit comments

Comments
 (0)