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

PkgConfig uses conanfile.run() instead of independent runner to use Conan environment #13985

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 8 additions & 2 deletions conan/tools/gnu/pkgconfig.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from io import StringIO

from conan.tools.build import cmd_args_to_string
from conan.tools.env import Environment
from conans.errors import ConanException
from conans.util.runners import check_output_runner


class PkgConfig:
Expand All @@ -23,11 +24,16 @@ def __init__(self, conanfile, library, pkg_config_path=None):
def _parse_output(self, option):
executable = self._conanfile.conf.get("tools.gnu:pkg_config", default="pkg-config")
command = cmd_args_to_string([executable, '--' + option, self._library, '--print-errors'])

env = Environment()
if self._pkg_config_path:
env.prepend_path("PKG_CONFIG_PATH", self._pkg_config_path)
with env.vars(self._conanfile).apply():
return check_output_runner(command).strip()
# This way we get the environment from ConanFile, from profile (default buildenv)
output = StringIO()
self._conanfile.run(command, output)
memsharded marked this conversation as resolved.
Show resolved Hide resolved
value = output.getvalue().strip()
return value

def _get_option(self, option):
if option not in self._info:
Expand Down
48 changes: 48 additions & 0 deletions conans/model/build_info.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import copy
import json
import os
from collections import OrderedDict, defaultdict

from conan.api.output import ConanOutput
from conans.errors import ConanException
from conans.util.files import save, load

_DIRS_VAR_NAMES = ["_includedirs", "_srcdirs", "_libdirs", "_resdirs", "_bindirs", "_builddirs",
"_frameworkdirs", "_objects"]
Expand Down Expand Up @@ -112,6 +114,34 @@ def serialize(self):
"properties": self._generator_properties
}

@staticmethod
def deserialize(contents):
result = _Component()
fields = [
"includedirs",
"srcdirs",
"libdirs",
"resdirs",
"bindirs",
"builddirs",
"frameworkdirs",
"system_libs",
"frameworks",
"libs",
"defines",
"cflags",
"cxxflags",
"sharedlinkflags",
"exelinkflags",
"objects",
"sysroot",
"requires",
"properties"
]
memsharded marked this conversation as resolved.
Show resolved Hide resolved
for f in fields:
setattr(result, f"_{f}", contents[f])
return result

@property
def includedirs(self):
if self._includedirs is None:
Expand Down Expand Up @@ -403,6 +433,16 @@ def __init__(self, set_defaults=False):
self._package = _Component(set_defaults)
self._aggregated = None # A _NewComponent object with all the components aggregated

def save(self, folder, file="conan_cpp_info.json"):
save(os.path.join(folder, file), json.dumps(self.serialize()))

@staticmethod
def load(folder=None, file="conan_cpp_info.json"):
if folder:
file = os.path.join(folder, file)
content = json.loads(load(file))
return CppInfo.deserialize(content)

def __getattr__(self, attr):
# all cpp_info.xxx of not defined things will go to the global package
return getattr(self._package, attr)
Expand All @@ -419,6 +459,14 @@ def serialize(self):
ret[component_name] = info.serialize()
return ret

@staticmethod
def deserialize(content):
result = CppInfo()
result._package = _Component.deserialize(content.pop("root"))
for component_name, info in content.items():
result.components[component_name] = _Component.deserialize(info)
return result

@property
def has_components(self):
return len(self.components) > 0
Expand Down
66 changes: 66 additions & 0 deletions conans/test/functional/toolchains/gnu/test_pkg_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import platform
import textwrap

import pytest

from conans.test.conftest import tools_locations
from conans.test.utils.tools import TestClient


Expand Down Expand Up @@ -68,3 +70,67 @@ def generate(self):
assert "conanfile.py: PROVIDES: libastral = 6.6.6" in c.out
assert "conanfile.py: VERSION: 6.6.6" in c.out
assert "conanfile.py: VARIABLES: /usr/local" in c.out


def test_pkg_config_round_tripe_cpp_info():
memsharded marked this conversation as resolved.
Show resolved Hide resolved
""" test that serialize and deserialize CppInfo works
"""
try:
version = tools_locations["pkg_config"]["default"]
exe = tools_locations["pkg_config"]["exe"]
os_ = platform.system()
pkg_config_path = tools_locations["pkg_config"][version]["path"][os_] + "/" + exe
except KeyError:
pytest.skip("pkg-config path not defined")
return

c = TestClient()
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.gnu import PkgConfig
from conans.model.build_info import CppInfo

class Pkg(ConanFile):
name = "pkg"
version = "0.1"
exports_sources = "*.pc"

def package(self):
pkg_config = PkgConfig(self, "libastral", pkg_config_path=".")
cpp_info = CppInfo()
pkg_config.fill_cpp_info(cpp_info, is_system=False, system_libs=["m"])
cpp_info.save(self.package_folder)

def package_info(self):
cpp_info = CppInfo.load()
self.cpp_info = cpp_info
""")
prefix = "C:" if platform.system() == "Windows" else ""
libastral_pc = textwrap.dedent("""\
PC FILE EXAMPLE:

prefix=%s/usr/local
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include

Name: libastral
Description: Interface library for Astral data flows
Version: 6.6.6
Libs: -L${libdir}/libastral -lastral -lm -Wl,--whole-archive
Cflags: -I${includedir}/libastral -D_USE_LIBASTRAL
""" % prefix)
c.save({"conanfile.py": conanfile,
"libastral.pc": libastral_pc,
"profile": f"[conf]\ntools.gnu:pkg_config={pkg_config_path}"})
c.run("export .")
c.run("install --requires=pkg/0.1 -pr=profile -g CMakeDeps --build=missing")
pkg_data = c.load("pkg-none-data.cmake")
assert 'set(pkg_DEFINITIONS_NONE "-D_USE_LIBASTRAL")' in pkg_data
assert 'set(pkg_SHARED_LINK_FLAGS_NONE "-Wl,--whole-archive")' in pkg_data
assert 'set(pkg_COMPILE_DEFINITIONS_NONE "_USE_LIBASTRAL")' in pkg_data
assert 'set(pkg_LIBS_NONE astral)' in pkg_data
assert 'set(pkg_SYSTEM_LIBS_NONE m)' in pkg_data
# paths
assert f'set(pkg_INCLUDE_DIRS_NONE "{prefix}/usr/local/include/libastral")' in pkg_data
assert f'set(pkg_LIB_DIRS_NONE "{prefix}/usr/local/lib/libastral")' in pkg_data