Tag Archives: pkg-config

A checklist for writing pkg-config files

tl;dr: Use AX_PKG_CHECK_MODULES to split public/private dependencies; use AC_CONFIG_FILES to magically include the API version in the .pc file name.

A few tips for creating a pkg-config file which you will never need to think about maintaining again — because one of the most common problems with pkg-config files is that their dependency lists are years out of date compared to the dependencies checked for in configure.ac. See lower down for some example automake snippets.

  • Include the project’s major API version1 in the pkg-config file name. e.g. libfoo-1.pc rather than libfoo.pc. This will allow parallel installation of two API-incompatible versions of the library if it becomes necessary in future.
  • Split private and public dependencies between Requires and Requires.private. This eliminates over-linking when dynamically linking against the project, since in that case the private dependencies are not needed. This is easily done using the AX_PKG_CHECK_MODULES macro (and perhaps using an upstream macro in future — see pkg-config bug #87154). A dependency is public when its symbols are exposed in public headers installed by your project; it is private otherwise.
  • Include useful ancillary variables, such as the paths to any utilities, directories or daemons which ship with the project. For example, glib-2.0.pc has variables giving the paths for its utilities: glib-genmarshal, gobject-query and glib-mkenums. libosinfo-1.0.pc has variables for its database directories. Ensure the variables use a variable form of ${prefix}, allowing it to be overridden when invoking pkg-config using pkg-config --define-variable=prefix=/some/other/prefix. This allows use of libraries installed in one (read only) prefix from binaries in another, while installing ancillary files (e.g. D-Bus service files) to the second prefix.
  • Substitute in the Name and Version using @PACKAGE_NAME@ and @PACKAGE_VERSION@ so they don’t fall out of sync.
  • Place the .pc.in template in the source code subdirectory for the library it’s for — so if your project produces multiple libraries (or might do in future), the .pc.in files don’t get mixed up at the top level.

Given all those suggestions, here’s a template libmy-project/my-project.pc.in file (updated to incorporate suggestions by Dan Nicholson):

prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@

my_project_utility=my-project-utility-binary-name
my_project_db_dir=@sysconfdir@/my-project/db

Name: @PACKAGE_NAME@
Description: Some brief but informative description
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lmy-project-@API_VERSION@
Cflags: -I${includedir}/my-project-@API_VERSION@
Requires: @AX_PACKAGE_REQUIRES@
Requires.private: @AX_PACKAGE_REQUIRES_PRIVATE@

And here’s a a few snippets from a template configure.ac:

# Release version
m4_define([package_version_major],[1])
m4_define([package_version_minor],[2])
m4_define([package_version_micro],[3])

# API version
m4_define([api_version],[1])

AC_INIT([my-project],
        [package_version_major.package_version_minor.package_version_micro],
        …)

# Dependencies
PKG_PROG_PKG_CONFIG
PKG_INSTALLDIR

glib_reqs=2.40
gio_reqs=2.42
gthread_reqs=2.40
nice_reqs=0.1.6

# The first list on each line is public; the second is private.
# The AX_PKG_CHECK_MODULES macro substitutes AX_PACKAGE_REQUIRES and
# AX_PACKAGE_REQUIRES_PRIVATE.
AX_PKG_CHECK_MODULES([GLIB],
                     [glib-2.0 >= $glib_reqs gio-2.0 >= $gio_reqs],
                     [gthread-2.0 >= $gthread_reqs])
AX_PKG_CHECK_MODULES([NICE],
                     [nice >= $nice_reqs],
                     [])

AC_SUBST([PACKAGE_VERSION_MAJOR],package_version_major)
AC_SUBST([PACKAGE_VERSION_MINOR],package_version_minor)
AC_SUBST([PACKAGE_VERSION_MICRO],package_version_micro)
AC_SUBST([API_VERSION],api_version)

# Output files
# Rename the template .pc file to include the API version on configure
AC_CONFIG_FILES([
libmy-project/my-project-$API_VERSION.pc:libmy-project/my-project.pc.in
…
],[],
[API_VERSION='$API_VERSION'])
AC_OUTPUT

And finally, the top-level Makefile.am:

# Install the pkg-config file; the directory is set using
# PKG_INSTALLDIR in configure.ac.
pkgconfig_DATA = libmy-project/my-project-$(API_VERSION).pc

Once that’s all built, you’ll end up with an installed my-project-1.pc file containing the following (assuming a prefix of /usr; note that by default autoconf substitutes in references to variables rather than the values themselves, so pkg-config can continue to be used with --define-variable to override the prefix):

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

my_project_utility=my-project-utility-binary-name
my_project_db_dir=${prefix}/etc/my-project/db

Name: my-project
Description: Some brief but informative description
Version: 1.2.3
Libs: -L${libdir} -lmy-project-1
Cflags: -I${includedir}/my-project-1
Requires: glib-2.0 >= 2.40 gio-2.0 >= 2.42 nice >= 0.1.6
Requires.private: gthread-2.0 >= 2.40

All code samples in this post are released into the public domain.


  1. Assuming this is the number which will change if backwards-incompatible API/ABI changes are made. 

Statically linking using pkg-config and JHBuild

tl;dr: Export PKG_CONFIG="pkg-config --static" or add it to your .jhbuildrc file.

Statically linking libraries which use libtool (specifically, the LT_INIT autoconf macro) is easy: you pass --enable-static --disable-shared to configure, and out pop static .a libraries instead of dynamic .so libraries.

However, things get more complex once you start to consider dependencies of your library. For example, if I’m statically linking libfoo against libbar, and libbar uses libwidgit internally (i.e. consumes the libwidgit symbols but does not expose any of them as part of libbar’s public interfaces), libfoo needs to know about libwidgit and needs to list it in the libfoo linker command. This is because UNIX archives (statically linked .a files) do not list their dependencies (whereas dynamically linked .so files do (on sensible UNIX platforms, including Linux)). Effectively the transitive closure of link dependencies needs to be resolved at link time, rather than at run time.

How can we cope with this when configuring libfoo? By using pkg-config --static instead of normal pkg-config. This can be achieved by setting the PKG_CONFIG environment variable to pkg-config --static or by adding the following lines to .jhbuildrc (which also set the libtool options mentioned above):

autogenargs = '--disable-shared --enable-static'
os.environ['PKG_CONFIG'] = 'pkg-config --static'

pkg-config’s --static option changes the program’s output to include the Libs.private and (recursively the) Requires.private variables. For example:

$ pkg-config --libs gobject-2.0
-L/opt/gnome3/build/lib64 -lgobject-2.0 -lglib-2.0
$
$ pkg-config --static --libs gobject-2.0
-pthread -L/opt/gnome3/build/lib64 -lgobject-2.0 -lffi -lglib-2.0

Here, libffi and pthread are both private dependencies of GObject: they’re used internally in GObject, but don’t appear in its external interface. When statically linking against GObject, though, their symbols must be resolved and hence they must appear in the linker command.

For more about the differences between pkg-config’s Libs and Libs.private variables, see the excellent guide to pkg-config. For more information about static linking with pkg-config, see the Autotools Mythbuster.

(There are probably errors or omissions in this post, since I’m nowhere near an expert on linking. Please comment with any corrections! Also, this is not meant to suggest that statically linking libraries is a good idea in general — there are only a few situations where it’s sensible. But for those situations, hopefully this post makes things a little easier.)