pkg-config 学习笔记

术语对照表:
参数 - option
选项 - flag

基本概念

在编译和链接时,提供必要的库文件细节。元数据存储在 pkg-config 文件里面,文件后缀 .pc ,文件需要存放在 pkg-config 工具能够找到的特定位置。

文件内容包括预定义的元数据关键词和形式自由的变量。
示例内容如下:

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

Name: foo
Description: The foo library
Version: 1.0.0
Cflags: -I${includedir}/foo
Libs: -L${libdir} -lfoo

关键字定义比如 Name:XXX ,后面跟着一个冒号 : 和值。关键字由 pkg-config 工具定义。
变量比如 prifix=XXX ,后面跟着一个等号 = 和值。变量一方面可以简化 .pc 文件的书写,一方面可以存储一些 pkg-config 工具无法覆盖的数据。

下面是对关键字字段的一个简要介绍,更深入的部分见 编写 部分(也就是下一节):

  • Name:库或者包的一个别名,方面人类阅读。pkg-config 工具使用的名字是 .pc 文件的文件名,不会用到这个名字。

  • Description:对包的一个简要描述。

  • URL:人们可以从这个地址下载包,并获得更多有关包的信息。

  • Version:包的版本号

  • Requires:这个包依赖的包的列表。依赖的包的版本号可以通过比较操作符指定(例如 = 、<=)

  • Requires.private:这个包依赖的包的列表。但是目标应用无法使用这些包。

  • Conflicts:可选字段,描述哪些包与此包冲突。

  • Cflags:不支持 pkg-config 的一些编译选项和库依赖。如果依赖的库支持 pkg-config ,那么这个库就应该放到前面的 Requires 或者 Requires.private 字段中去。

  • Libs:不支持 pkg-config 的一些链接选项和库依赖。

  • Libs.private:不支持 pkg-config 的一些用于私有库依赖的链接选项。

编写

在给一个包创建 pkg-config 文件(后面简称为 pc 文件)的时候首先要决定这些文件的发布方式,最好是每个库都有一个对应的 pc 文件,这样每个包至少有跟库的数量一致的 pc 文件。

包的名字由 pc 元数据文件的文件名决定。也就是文件名中除去 .pc 后缀的那部分。一个常见的选择是让库名字和 .pc 文件的名字一致。例如,下载了 libfoo.so 的包应该有一个对应的 libfoo.pc 文件包含相关的 pkg-config 元数据。但是不一定要这要,把pc文件命名为 foo.pc 或者 foolib.pc 也是可行的。

Name、Description、URL字段都是纯信息,很容易填。 Version 字段有一点点麻烦,需要确保该数据能被用户所使用。pkg-config 使用 RPM 的算法来进行版本比较。建议使用通过句点 . 分开的十进制数,如果使用字母的话会出现不可预知的错误。版本号必须单调递增,并且足以制定这个库文件。通常使用包的版本号就足够了,而且这样方面用户进行追踪。

在介绍更有用的字段之前,先介绍一下变量的定义。变量最常见的用途是指定安装路径,使用变量可以使元数据字段保持简洁。变量是递归地进行展开的,因此跟自动生成的路径一起使用十分有用(这句话没看懂)。

prefix=/usr/local
includedir=${prefix}/include

Cflags: -I${includedir}/foo

pkg-config 里面最重要的元数据字段是 Requires、 Requires.private 、 Cflags 、 Libs 和 Libs.private 。它们定义了外部项目编译链接这个库时所需要元数据。

Requires 和 Requires.private 定义了这个库所需的其他模块。通常建议使用 Require 的 private 变体,以避免把不必要的库暴露给用户程序。如果用户程序不会用到依赖库的符号,那么这个库经不应该被直接链接到用户程序上。更详尽的讨论参见 overlinking

因为 pkg-config 总是暴露 Requires 库的链接选项,所以这些模块会成为程序的直接依赖。另一方面,Require.private 中的库在静态链接的时候只会被包含。因此,通常比较合适的做法是仅在 Requires 字段添加同一个包里面的模块。 (老外的写作水平也是堪忧啊,各种 包、模块、库词汇混杂在一起使用。)

Libs 包含使用库所必需的链接选项。此外,Libs 和 Libs.private 还包含 pkg-config 不支持的其他库的链接选项。同样的,推荐把外部库的链接选项放到 Libs.private 字段里面去。

Cflags 包含这个库所必需的编译选项。与 Libs 不同的是这个字段没有 private 变体。因为不管是怎么样的链接场景,数据类型和宏定义都是需要的。

使用

假设系统安装了 .pc 文件,那么 pkg-config 工具就是用来抽取元数据的。执行 pkg-config --help 可以查看其各个参数的简要描述。更详细的描述可以在 pkg-config(1) 手册页中找到。本节会简要介绍一些常用的情况。

假设一个系统有个两个模块,foo 和 bar 。他们的 pc 文件可以是这样:

foo.pc:

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

Name: foo
Description: The foo library
Version: 1.0.0
Cflags: -I${includedir}/foo
Libs: -L${libdir} -lfoo

bar.pc:

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

Name: bar
Description: The bar library
Version: 2.1.2
Requires.private: foo >= 0.7
Cflags: -I${includedir}
Libs: -L${libdir} -lbar

模块的版本号可以通过 --modversion 参数查看

$ pkg-config --modversion foo
1.0.0
$ pkg-config --modversion bar
2.1.2

每个模块所需要的链接选项可以通过 --libs 参数查看

$ pkg-config --libs foo
-lfoo
$ pkg-config --libs bar
-lbar

注意 pkg-config 隐去了部分 Libs 中的字段。这是因为当它看到 -L 选项时,知道 ${libdir} 路径 /usr/lib 是系统链接器的查找路径。使用 -L 可以避免 pkg-config 干涉链接操作。

而且,尽管 foo 被 bar 所依赖,foo 的链接选项却没有输出。这是因为 foo 并不是用户应用直接需要的模块。而如果要静态链接一个 bar 应用,需要同时设置两个链接选项。

$ pkg-config --libs --static bar
-lbar -lfoo

pkg-config 需要把两个链接选项都输出来,确保静态链接的应用能够找到所有需要的符号。另一方面,它会把所有 Cflags
输出。

$ pkg-config --cflags bar
-I/usr/include/foo
$ pkg-config --cflags --static bar
-I/usr/include/foo

另一个常用的参数 --exists ,可以用来检测一个模块的可用性。

$ pkg-config --exists foo
$ echo $?
0

pkg-config 的一个最优秀的特性是提供了版本检测。它可以检测是否存在满足条件的版本。

$ pkg-config --libs "bar >= 2.7"
Requested 'bar >= 2.7' but version of bar is 2.1.2

有些命令在使用 --print-errors 参数后会进行更详尽的输出。

$ pkg-config --exists --print-errors xoxo
Package xoxo was not found in the pkg-config search path.
Perhaps you should add the directory containing `xoxo.pc'
to the PKG_CONFIG_PATH environment variable
No package 'xoxo' found

上面的输出信息提到了 PKG_CONFIG_PATH 环境变量。这个变量用来增加 pkg-config 的搜索路径。在一个典型的 Unix 系统中,它会搜索 /usr/lib/pkgconfig/usr/share/pkgconfig 。这两个路径可以覆盖系统安装的模块。但是,有些本地模块可能下载到了其他路径中,例如 /usr/local
这种情况下,有必要把这个搜索路径添加进去以便 pkg-config 可以定位 pc 文件。

$ pkg-config --modversion hello
Package hello was not found in the pkg-config search path.
Perhaps you should add the directory containing `hello.pc'
to the PKG_CONFIG_PATH environment variable
No package 'hello' found
$ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
$ pkg-config --modversion hello
1.0.0

把 pkg-config 模块集成到用户项目中的时候,使用自动配置(autoconf)的宏可以简化这个过程。

  • PKG_PREREQ(MIN-VERSION):确保所使用的自动配置宏高于或等于 MIN-VERSION 。

  • PKG_PROG_PKG_CONFIG([MIN-VERSION]):定位 pkg-config 工具在系统中的位置,并检测其版本的兼容性。

  • PKG_CHECK_EXISTS(MODULES,[ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]):检测某些模块是否存在。

  • PKG_CHECK_MODULES(VARIABLE-PREFIX,MODULES,[ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]):检测某些模块是否存在,如果存在,根据 pkg-config --cflags 和 pkg-config --libs 的输出设置 _CFLAGS 和 _LIBS 。

常见问题

你可能感兴趣的:(pkg-config 学习笔记)