一、背景
当开发过程中使用第三方库时,我们需要知道被使用的第三方库,它包含的头文件在哪儿、它依赖哪些库、它的编译选项有哪些、它的链接选项有哪些等等,也就是说需要知道这些信息我们才能正确的编译和链接它。当然可以通过直接在编译选项中指定的方式来使用,但是,对于一些大型或者比较复杂的第三方库,包含的头文件或者依赖库非常多、非常复杂的时候,这样做的成本就非常高,而且非常不便于在不同平台之间的移植。
pkg-config
就是解决这个问题的一种方案。
二、pkg-config是什么
pkg-config
是一个用于获取系统中安装的包/库的相关信息(例如依赖头文件目录、依赖库文件目录)的程序,通常用于编译和链接一个或多个库,典型的用法为:
# Makefile
program: program.c
cc program.c `pkg-config --cflags --libs XXX` # 此处的XXX表示库名称
pkg-config
通过读取一个名为<PackageName>.pc
的文件来获取<PackageName>
包的信息,一般会从/usr/lib/pkgconfig, /usr/share/pkgconfig, /usr/local/lib/pkgconfig, /usr/local/share/pkgconfig
来查找.pc
文件,除此之外,也会查找PKG_CONFIG_PATH
环境变量指定的路径。
三、如何使用pkg-config
Linux
或macOS
系统可以通过man pkg-config
来查看pkg-config
的用法,选项有很多,此处不一一介绍,最常用到的两个选项是pkg-config --cflags <PackageName>
和pkg-config --libs <PackageName>
,其含义分别为:
-
--cflags
:获取编译<PackageName>
包的所需要的预处理或者编译选项,例如-I/usr/include
,指定编译<PackageName>
包的头文件搜索路径。 -
--libs
:获取编译<PackageName>
包所需要的链接选项,例如-L/usr/lib
指定库文件搜索路径,或-lXXX
指定具体的库文件。
下面以一个例子来说明pkg-config
的用法,例子将会用到liblzma
库(可以使用pkg-config --list-all
来查看当前pkg-config
已经识别的库),程序很简单,就是通过调用一个接口打印出liblzma
的版本号,当然,接口是库liblzma
提供的:
// test.cpp
// 打印lzma库的版本号
#include "lzma.h"
#include <iostream>
int main(int argc, char** argv)
{
const char* version = lzma_version_string();
std::cout << "lzma version is: " << version << std::endl;
return 0;
}
lzma.h
是lzma
库提供的一个头文件,lzma_version_string
是lzma
库提供的一个接口,功能为获取lzma
库的版本号,不需要入参,返回一个常量字符串。程序调用此接口并将版本号打印出来。
直接使用g++ -o test test.cpp
是无法编译通过的,因为我们不知道关于lzma
库的任何信息(例如头文件lzma.h
头文件在哪个目录下,lzma
库在哪个目录下等等),此时就是pkg-config
发挥作用的时候了,编译命令如下:
// 在命令行中执行
g++ -o test test.cpp `pkg-config --cflags --libs liblzma`
编译通过后得到可执行文件test
,在我的环境中运行test
得到如下输出:
./test
lzma version is: 5.2.5
四、自己编写的库如何让pkg-config使用
从前面的介绍可以知道,pkg-config
会在常用的系统路径下查找.pc
文件,除此之外,还会查找PKG_CONFIG_PATH
环境变量指定的路径。
那么自己编写的库,想让pkg-config
找到并使用,需要两个步骤:
- 编写库相关的
.pc
文件; - 将
.pc
文件放在pkg-config
的查找路径之下;
第二点好办,将.pc
文件放在几个指定的路径或者环境变量PKG_CONFIG_PATH
指定的路径即可,那么.pc
文件怎么编写呢?
.pc
文件的格式
.pc
是用来描述一个库的名称、版本、编译选项、链接选项等内容的文件,因此它需要包含:名称、描述、版本信息、编译选项和链接选项几个部分,具体来说,是对Name、Description、Version、Cflags、Libs
五个变量的一个定义,定义方式为变量: 内容
,其中Cflags
和Libs
可能会使用编译或者链接的一些选项,例如-I
、-L
等。# mylib.pc Name: mylib Description: mylib for test Version: 1.0 Cflags:-I/XXX/mylib/include Libs:-L/XXX/mylib/lib –lmylib
当然,这个文件也可以定义其他变量,以方便使用。例如目录可以使用一个变量来定义,这样在
Cflags
或Libs
中就直接使用该变量即可,例如:# mylib.pc librootdir=/XXX/mylib Name: mylib Description: mylib for test Version: 1.0 Cflags:-I${librootdir}include Libs:-L${librootdir}/lib –lmylib
下面来写一个简单的示例,
4.1 库文件的编写
首先编写一个库文件,目录结构如下:
mymath # 库的顶层目录
--mymath.pc # .pc文件,以便pkg-config使用
--include # 库提供的头文件目录
----mymath.h # 库提供的头文件
--lib # 库文件目录
----libmymath.a # 库文件
库文件提供的内容很简单:一个add
接口用于计算两个整数的和并打印出来。由于是使用库的方式,mymath.cpp
文件只是在编译libmymath.a
时需要使用,并不需要对外提供,下面是mymath.cpp
和mymath.h
的内容:
// mymath.h
namespace mymath {
int add(int a, int b);
};
// mymath.cpp
#include "mymath.h"
#include <iostream>
namespace mymath {
int add(int a, int b)
{
std::cout << "Add " << a << " and " << b << " is " << a + b << std::endl;
return a + b;
}
};
4.2 .pc文件的编写
头文件和库文件所在的目录根据实际指定即可,此处以/XXX
代替.
# mymath.pc
librootdir=/XXX/mymath
Name: mymath
Description: a simple add function
Version: 1.0
Cflags: -I${librootdir}/include
Libs: -L${librootdir}/lib -lmymath
4.3 让.pc
文件被pkg-config
可见
可以把mymath.pc
拷贝到系统查找的几个目录或者将mymath.pc
文件所在的目录添加到环境变量PKG_CONFIG_PATH
中,此处使用第二种办法:
# 命令行中执行,/XXX需要替换成实际的目录
PKG_CONFIG_PATH=/XXX/mymath
export PKG_CONFIG_PATH
这样就可以使用pkg-config
命令来获取mymath
库的信息了:
# 命令行中执行
pkg-config --list-all | grep "mymath"
mymath mymath - a simple add function
pkg-config --cflags --libs mymath
-I/XXX/mymath/include -L/XXX/mymath/lib -lmymath
4.4 测试pkg-config
对库mymath
的使用
// test.cpp
#include "mymath.h"
int main(int argc, char** argv)
{
mymath::add(1, 2);
return 0;
}
编译命令如下:
g++ -o test test.cpp `pkg-config --cflags --libs mymath`
运行结果:
./test
Add 1 and 2 is 3
网友评论