pkg-config and .pc files
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) that are Conan packages too, you can follow different approaches.
The main issue to solve is the absolute paths. When a user installs a package in the local cache, the directory will probably be different from the directory where the package was created. This could be because of the different computer, the change in Conan home directory or even a different user or channel:
For example, in the machine where the packages were created:
/home/user/lasote/.data/storage/zlib/1.2.11/conan/stable
In the machine where the library is being reused:
/custom/dir/.data/storage/zlib/1.2.11/conan/testing
You can see that .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}
To solve this problem there are different approaches that can be followed.
Approach 1: Import and patch the prefix in the .pc files
In 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 them.
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.g., 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 packaging 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 by 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 LibBConan(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 LibAConan(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 pkg-config >= 0.29.1 available, you can manage multiple dependencies declaring N variables with the prefixes:
class LibAConan(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 library B and library C, and specify all the library names and any other needed flag,
you can use the pkg_config
generator for library bA. 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 library A 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, AutoToolsBuildEnvironment
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(), a wrapper of the pkg-config tool that allows to extract flags, library paths, etc. for any .pc file.