布景与问题

在进行 C++ 项目开发的过程中,多少会依靠一些外部库,这些依靠有些或许是经过 git submodule 源码的方式直接引入到自己的工程中进行静态链接,而有时期望控制编译产品的巨细,把一些比较通用的依靠期望以动态链接的方式依靠,比方图片编解码库 libpng、libwebp 或许文本塑形库 harfbuzz 之类的。

这种情况下在进行编译装备的时分,需求依靠的库纷歧定都在 /usr/local/include 目录下,大部分情况下需求对编译器手动指定 include 目录,同理也或许需求对链接器手动指定 lib 地点目录,而因为不同系统下装置的库地点的目录纷歧定共同,会导致编译装备难以跨渠道执行。

咱们能够用 pkg-config 来帮咱们处理这个问题,当需求依靠某个外部库时,能够让 pkg-config 来告知咱们它在当时环境下要依靠它的话,编译链接指令应该是什么,让咱们看下详细是怎么运用的吧。

装置 pkg-config

pkg-config 最初是为 Linux 开发的,但现在是跨渠道的,支撑 Mac、Linux、Windows,假如你的环境下还没有 pkg-config 的话,首要装置它:

# macos
brew install pkg-config
# ubuntu
apt-get install pkg-config
# 其他渠道相信你能找到办法

开始运用 pkg-config

pkg-config 自身是一个指令行东西,咱们能够试着输出一个比方 libwebp 在当时环境的编译选项:

pkg-config libwebp --cflags

在我的环境下的输出为:

-I/usr/local/Cellar/webp/1.2.0/include

再试着输出一下 libwebp 的链接选项:

pkg-config libwebp --libs

在我的环境下的输出为:

-L/usr/local/Cellar/webp/1.2.0/lib -lwebp

能够指定多个选项让 pkg-config 一同输出,比方一同输出 cflags 和 libs 选项:

pkg-config libwebp --libs --cflags

此刻的输出为:

-I/usr/local/Cellar/webp/1.2.0/include -L/usr/local/Cellar/webp/1.2.0/lib -lwebp

你乃至能够在一条指令中指定多个外部库,比方我同时指定 libwebp 和 libpng:

pkg-config libwebp libpng --libs --cflags

测试的输出为:

-I/usr/local/Cellar/webp/1.2.0/include -I/usr/local/Cellar/libpng/1.6.37/include/libpng16 -L/usr/local/Cellar/webp/1.2.0/lib -L/usr/local/Cellar/libpng/1.6.37/lib -lwebp -lpng16 -lz

能够发现它的输出是能够直接运用在编译东西上的,所以在指令行下和编译东西能够直接结合起来:

gcc -o test test.c `pkg-config --libs --cflags libwebp`

你乃至能够用 pkg-config 查看外部库在当时环境下的版本号:

pkg-config libwebp --version

能够指令参数能够参阅 –help 的输出。

在 CMake 中运用 pkg-config

当然一般正式的项目都会运用元构建东西来进行跨渠道的工程编译装备的生成,我这边运用的是 CMake,在 CMake 中也能够十分方便的运用 pkg-config:

find_package(PkgConfig REQUIRED)
if (PKG_CONFIG_FOUND)
  pkg_check_modules(my_deps REQUIRED IMPORTED_TARGET libpng libwebp)
endif()

首要运用 CMake 的 find_package 机制找到本地的 pkg-config,假如成功找到,则有两种办法查找外部库:

  • pkg_check_modules:根据列表中给的外部库,在当时环境下都试着去找到
  • pkg_search_module:找到列表中第一个成功找到的外部库

能够根据实际需求运用,大部分情况下运用 pkg_check_modules,第一个参数为匹配前缀,当你需求依靠多个外部库时,经过这个前缀,能够一次性的消费成果。你也能够指定 REQUIRED 来表明依靠对这次构建是有必要的,不然直接失利终止构建。接下来就是运用 pkg-config 的成果了,在 CMake 上下文中能够运用 PkgConfig::${prefix} 来消费成果,直接作为 target_link_libraries 的参数即可:

target_link_libraries(${PROJECT_NAME} PkgConfig::my_deps)

假如你的 CMake 版本小于 3.6,也能够运用一下变量

  • _LDFLAGS
  • _CFLAGS

关于在 CMake 中运用 pkg-config 的更多细节,能够参阅其官方文档:cmake.org/cmake/help/…。

pkg-config 怎么查找依靠

假如咱们让 pkg-config 查找一个未装置的外部库会怎么样?

pkg-config libxxx --cflags

会输出:

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

在聊怎么处理之前,首要需求介绍下 pkg-config 是怎么查找依靠的,首要介绍一些 pc 文件,它在上面的错误信息中也呈现了,pkg-config 是经过读取目录下的 pc 文件在确认查找成果的,这个目录一般是 libdir/pkgconfig,比方你的 libwebp 装置在 /usr/local/lib 下,那么放 pc 文件就是 /usr/local/lib/pkgconfig 下的 libwebp.pc 文件。pc 文件就是普通的文本文件,咱们来看一下 pc 文件的内容:

prefix=/usr/local/Cellar/webp/1.2.0
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: libwebp
Description: Library for the WebP graphics format
Version: 1.2.0
Cflags: -I${includedir}
Libs: -L${libdir} -lwebp
Libs.private: -lm -D_THREAD_SAFE -pthread

能够看到这个文件中界说了这个依靠的称号、版本号、在当时环境的目录前缀、编译选项等等,看到这儿你应该理解 pkg-config 的输出是哪里来的了。

外部依靠之间或许也会有依靠关系,pkg-config 会帮你处理这个依靠问题,其实库的依靠也是会在 pc 文件中界说的,比方咱们看下 harfbuzz 的 pc 文件:

prefix=/usr/local/Cellar/harfbuzz/2.8.0_1
libdir=${prefix}/lib
includedir=${prefix}/include
Name: harfbuzz
Description: HarfBuzz text shaping library
Version: 2.8.0
Requires.private: freetype2, graphite2, glib-2.0
Libs: -L${libdir} -lharfbuzz
Libs.private: -lm -framework ApplicationServices
Cflags: -I${includedir}/harfbuzz

能够发现这个库同时依靠了 freetype2、graphite2、glib-2.0 这三个库,只要在 pc 文件中有经过 Requires 或许 Requires.private 声明过依靠,在 cflags 或许 libs 的输出成果中也会带有依靠的编译选项:

-I/usr/local/Cellar/harfbuzz/2.8.0_1/include/harfbuzz -I/usr/local/opt/freetype/include/freetype2 -I/usr/local/Cellar/graphite2/1.3.14/include -I/usr/local/Cellar/glib/2.68.1/include/glib-2.0 -I/usr/local/Cellar/glib/2.68.1/lib/glib-2.0/include -I/usr/local/opt/gettext/include -I/usr/local/Cellar/pcre/8.44/include

一般一个 lib 对应一个 pc 文件,而有时有些项目有多个 lib,那么它也会分别界说多个 pc 文件,比方 ffmpeg 就有这些 pc 文件:

  • libavcodec.pc
  • libavdevice.pc
  • libavfilter.pc
  • libavformat.pc
  • libavutil.pc

pkg-config 查找途径装备

其实在运用 pkg-config 过程中遇到库找不到的情况,纷歧定是外部库没有装置,默许情况下 pkg-config 的查找途径为 /usr/lib/pkgconfig 和 /usr/share/pkgconfig,能够经过环境变量 PKG_CONFIG_PATH 在额外指定 pkg-config 的查找途径,比方:

export PKG_CONFIG_PATH="/usr/local/opt/icu4c/lib/pkgconfig:${PKG_CONFIG_PATH}"
export PKG_CONFIG_PATH="/usr/local/opt/jpeg-turbo/lib/pkgconfig:${PKG_CONFIG_PATH}"

生成 pc 文件

现在大部分闻名的库应该都存在 pc 文件,假如你的依靠没有带 pc 文件,你能够自己为它编写 pc 文件,但是不难发现,假如手动编写 pc 文件,那么文件内的途径依然是和当时环境绑定的绝对途径,那么问题仍是没有处理,大部分情况下运用东西主动生成 pc 文件才是正确的姿势,这儿简略介绍一下怎么运用 Autotools 生成 pc 文件,首要需求在你的项目中界说 pc.in 文件作为模板,比方 libwebp 项目的 pc.in 文件:

prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: libwebp
Description: Library for the WebP graphics format
Version: @PACKAGE_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -lwebp
Libs.private: -lm @PTHREAD_CFLAGS@ @PTHREAD_LIBS@

其中 @variable@ 是变量,会被 configure 替换。接下来就是在 configure.ac 文件中经过 AC_CONFIG_FILES 添加 pc 文件,接下来在库被装置的时分,经过 configure 正确设置变量后,就能够动态生成 pc 文件和库的其他文件一同装置在合适的方位了。

最终

经过 pkg-config 基本处理了跨渠道编译是三方库依靠的编译装备问题,并且运用简略,能够很简略的和指令行或许很多元构建东西结合起来运用。当然 pkg-config 并不是包管理东西,虽然大部分闻名的库都有 pc 文件,但有时也需求在特定环境下为 pkg-config 装备正确的查找途径。假如是你自己界说的库需求被其他项目愉快的依靠,那么就要自己经过东西去生成 pc 文件啦。