pkg-config and pc files
Intro
If you are creating a Conan package for a library (A) and the build system uses .pc
files to locate
its dependencies (B and C), Conan packages too, you can follow different approaches.
The main issue to solve is the absolute paths. When an user installs a package in the local cache, the directory will probably be different from the directory where the package was created, because of the different computer, conan home directory or even different user or channel:
In the machine where the packages were created:
/home/user/lasote/.data/storage/zlib/1.2.11/conan/stable
In the machine where some user are reusing the library:
/custom/dir/.data/storage/zlib/1.2.11/conan/testing
So the .pc
files containing absolute paths won’t work to locate the dependencies.
Example of a .pc
file with an absolute path:
prefix=/Users/lasote/.conan/data/zlib/1.2.11/lasote/stable/package/b5d68b3533204ad67e01fa587ad28fb8ce010527
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
sharedlibdir=${libdir}
includedir=${prefix}/include
Name: zlib
Description: zlib compression library
Version: 1.2.11
Requires:
Libs: -L${libdir} -L${sharedlibdir} -lz
Cflags: -I${includedir}
Approach 1: Import and patch the prefix in the pc files
Following this approach your library A will import to a local directory the .pc
files from B and C, then,
as they will contain absolute paths, the recipe for A will patch the paths to match the current installation
directory.
You will need to package the pc files from your dependencies.
You can adjust the PKG_CONFIG_PATH to let pkg-config
tool locate your .pc
files.
import os
from conans import ConanFile, tools
class LibAConan(ConanFile):
name = "libA"
version = "1.0"
settings = "os", "compiler", "build_type", "arch"
exports_sources = "*.cpp"
requires = "libB/1.0@conan/stable"
def build(self):
lib_b_path = self.deps_cpp_info["libB"].rootpath
copyfile(os.path.join(lib_b_path, "libB.pc"), "libB.pc")
# Patch copied file with the libB path
tools.replace_prefix_in_pc_file("libB.pc", lib_b_path)
with tools.environment_append({"PKG_CONFIG_PATH": os.getcwd()}):
# CALL YOUR BUILD SYSTEM (configure, make etc)
# E.j: self.run('g++ main.cpp $(pkg-config libB --libs --cflags) -o main')
Approach 2: Prepare and package pc files before package them
With this approach you will patch the pc
files from B and C before package them.
The goal is to replace the absolute path (the variable part of the path) with a variable placeholder.
Then in the consumer package A, declare the variable using --define-variable
when calling the
pkg-config command.
This approach is cleaner than approach 1, because the packaged files are already prepared to be
reused with or without conan, just declaring the needed variable. And it’s not needed to import the pc
files to the consumer package. However, you need B and C libraries to package the pc
files correctly.
Library B recipe (preparing the pc
file):
from conans import ConanFile, tools
class LibraryBrecipe(ConanFile):
....
def build(self):
...
tools.replace_prefix_in_pc_file("mypcfile.pc", "${package_root_path_lib_b}")
def package(self):
self.copy(pattern="*.pc", dst="", keep_path=False)
Library A recipe (importing and consuming pc
file):
class LibraryArecipe(ConanFile):
....
requires = "libB/1.0@conan/stable, libC/1.0@conan/stable"
def build(self):
args = '--define-variable package_root_path_lib_b=%s' % self.deps_cpp_info["libB"].rootpath
args += ' --define-variable package_root_path_lib_c=%s' % self.deps_cpp_info["libC"].rootpath
pkgconfig_exec = 'pkg-config ' + args
vars = {'PKG_CONFIG': pkgconfig_exec, # Used by autotools
'PKG_CONFIG_PATH': "%s:%s" % (self.deps_cpp_info["libB"].rootpath,
self.deps_cpp_info["libC"].rootpath)}
with tools.environment_append(vars):
# Call autotools (./configure ./make, will read PKG_CONFIG)
# Or directly declare the variables:
self.run('g++ main.cpp $(pkg-config %s libB --libs --cflags) -o main' % args)
Approach 3: Use –define-prefix
If you have available pkg-config
>= 0.29 and you have only one dependency, you can use directly
the --define-prefix
option to declare a custom prefix
variable. With this approach you won’t
need to patch anything, just declare the correct variable.
Approach 4: Use PKG_CONFIG_$PACKAGE_$VARIABLE
If you have available pkg-config
>= 0.29.1 you can manage multiple dependencies declaring N variables
with the prefixes:
class LibraryArecipe(ConanFile):
....
requires = "libB/1.0@conan/stable, libC/1.0@conan/stable"
def build(self):
vars = {'PKG_CONFIG_libB_PREFIX': self.deps_cpp_info["libB"].rootpath,
'PKG_CONFIG_libC_PREFIX': self.deps_cpp_info["libC"].rootpath,
'PKG_CONFIG_PATH': "%s:%s" % (self.deps_cpp_info["libB"].rootpath,
self.deps_cpp_info["libC"].rootpath)}
with tools.environment_append(vars):
# Call the build system
Approach 5: Use the pkg_config
generator
If you use package_info()
in libB and libC, and specify all the library names and any other needed flag,
you can use the pkg_config
generator during the libA
. Those files doesn’t need to be patched, because
are dynamically generated with the correct path.
So it can be a good solution in case you are building libA
with a build system that manages pc files
like
Meson Build or AutoTools:
Meson Build
from conans import ConanFile, tools, Meson
import os
class ConanFileToolsTest(ConanFile):
generators = "pkg_config"
requires = "LIB_A/0.1@conan/stable"
settings = "os", "compiler", "build_type"
def build(self):
meson = Meson(self)
meson.configure()
meson.build()
Autotools
from conans import ConanFile, tools, Meson
import os
class ConanFileToolsTest(ConanFile):
generators = "pkg_config"
requires = "LIB_A/0.1@conan/stable"
settings = "os", "compiler", "build_type"
def build(self):
autotools = AutoToolsBuildEnvironment(self)
# When using the pkg_config generator, self.build_folder will be added to PKG_CONFIG_PATH
# so pkg_config will be able to locate the generated pc files from the requires (LIB_A)
autotools.configure()
autotools.make()
See also
Check the tools.PkgConfig() class,
a wrapper of the pkg-config
tool that allows to extract flags, library paths, etc for any pc
file.