第九章 查找依赖库:find_library()、find_package()、find_path()、find_file()、pkg_check_modules()

做 NDK 开发,说白了就是跟各种库打交道。系统库、第三方库、自己编译的库……你得告诉 CMake 它们在哪。我刚开始带团队的时候,有个同事把库路径写死了,结果换了个手机型号就编译不过。嗯,从那以后我要求所有人必须用 CMake 的查找命令。

这一章,我们就来彻底搞懂 CMake 里查找依赖库的五种武器。它们各有各的脾气,用对了事半功倍。

9.1 find_library():找系统库的标配

这个命令专门用来查找系统预装的共享库。比如 liblog、libandroid、libEGL 这些。

基本用法很简单:

find_library(
    LOG_LIB          # 变量名,用来存结果
    log              # 库名,不带 lib 前缀和 .so 后缀
    PATHS /usr/lib   # 可选,指定搜索路径
)

找到之后,直接链接:

target_link_libraries(myapp ${LOG_LIB})
我的习惯:变量名用大写加下划线,一眼就能看出是 CMake 变量。比如 EGL_LIBGLESv2_LIB

为什么不用直接写 -llog?因为不同 NDK 版本、不同架构下,库的路径可能不一样。find_library() 会自动处理这些差异。我在项目中遇到过,某次 NDK 升级后 libGLESv2.so 的路径变了,幸好用了 find_library(),只改了一行代码就搞定了。

9.2 find_package():找第三方库的利器

这是最强大的查找命令。它不仅能找到库文件,还能把头文件路径、编译选项、依赖关系一并搞定。

find_package() 有两种模式:

模式 说明 示例
模块模式 查找 Find<Name>.cmake 脚本 find_package(ZLIB)
配置模式 查找 <Name>Config.cmake 脚本 find_package(OpenCV REQUIRED)

我个人更推荐配置模式,因为库的作者自己提供的配置信息最准确。比如 OpenCV、Boost、PCL 这些大库,都自带 Config.cmake。

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(myapp ${OpenCV_LIBS})
关键参数:
  • REQUIRED:找不到就报错,别偷偷摸摸地继续
  • QUIET:安静模式,找不到也不提示
  • COMPONENTS:只找特定组件,比如 find_package(Qt5 COMPONENTS Core Widgets)

你想想看,如果不用 find_package(),你得手动设置多少个变量?头文件路径、库路径、链接库名、编译宏……而且每个平台都不一样。find_package() 把这些脏活累活全包了。

9.3 find_path() 和 find_file():找头文件和普通文件

这两个命令是兄弟俩。find_path() 找目录,find_file() 找文件。

什么时候用 find_path()?当你知道头文件名,但不知道它在哪个目录时:

find_path(
    OPENSSL_INCLUDE_DIR
    openssl/ssl.h
    PATHS /usr/include /usr/local/include
)

find_file() 则更通用,可以找任何类型的文件:

find_file(
    CONFIG_JSON
    "config.json"
    PATHS ${CMAKE_CURRENT_SOURCE_DIR}/configs
)
我曾经踩过的坑:find_path() 返回的是包含目标文件的目录路径,不是文件本身的路径。比如上面找 openssl/ssl.h,返回的是 /usr/include,而不是 /usr/include/openssl/ssl.h。很多新手在这里搞混。

这两个命令的搜索顺序是:先搜 PATHS 指定的路径,再搜系统默认路径,最后搜 CMAKE_PREFIX_PATH。如果你想让 CMake 优先搜某个目录,记得把路径加到 PATHS 里。

9.4 pkg_check_modules():用 pkg-config 查库

这是 CMake 里的一个模块,需要先包含才能用:

include(FindPkgConfig)
pkg_check_modules(SDL2 REQUIRED sdl2)

pkg-config 是 Linux 世界里的标准库查找工具。很多库都提供了 .pc 文件,里面记录了编译和链接需要的所有信息。

找到之后,CMake 会自动生成这些变量:

  • SDL2_INCLUDE_DIRS:头文件路径
  • SDL2_LIBRARY_DIRS:库文件路径
  • SDL2_LIBRARIES:库名列表
  • SDL2_CFLAGS_OTHER:其他编译选项

用法也很直接:

include_directories(${SDL2_INCLUDE_DIRS})
link_directories(${SDL2_LIBRARY_DIRS})
target_link_libraries(myapp ${SDL2_LIBRARIES})
什么时候用 pkg_check_modules()?当库没有提供 CMake Config 文件,但提供了 .pc 文件时。比如很多 Linux 系统库、GTK、SDL 等。在 Android NDK 里,pkg-config 默认不可用,需要自己交叉编译 pkg-config 或者用 PKG_CONFIG_PATH 环境变量指定路径。

9.5 五种查找命令的对比

为了让你一目了然,我整理了一张对比表:

命令 查找目标 典型场景 输出变量
find_library() 共享库文件 系统库如 liblog、libandroid 库文件路径
find_package() 完整的包信息 OpenCV、Boost、Qt 多个变量(头文件、库、宏等)
find_path() 头文件所在目录 自定义头文件路径 目录路径
find_file() 任意文件 配置文件、资源文件 文件路径
pkg_check_modules() pkg-config 管理的库 GTK、SDL、ffmpeg 多个变量

9.6 实战:一个完整的查找示例

假设我们要编译一个 Android 应用,需要用到 OpenSSL 和 libpng。来看看完整的 CMakeLists.txt 怎么写:

cmake_minimum_required(VERSION 3.10)
project(MySecureApp)

# 查找系统库
find_library(LOG_LIB log)
find_library(ANDROID_LIB android)

# 查找 OpenSSL(假设它提供了 Config 文件)
find_package(OpenSSL REQUIRED)

# 查找 libpng(用 pkg-config)
include(FindPkgConfig)
pkg_check_modules(PNG REQUIRED libpng)

# 查找自定义头文件
find_path(MY_HEADERS myapp/config.h PATHS ${CMAKE_CURRENT_SOURCE_DIR}/include)

# 添加可执行文件
add_executable(myapp main.c)

# 链接所有库
target_link_libraries(myapp
    ${LOG_LIB}
    ${ANDROID_LIB}
    ${OPENSSL_LIBRARIES}
    ${PNG_LIBRARIES}
)

# 设置头文件路径
target_include_directories(myapp PRIVATE
    ${OPENSSL_INCLUDE_DIR}
    ${PNG_INCLUDE_DIRS}
    ${MY_HEADERS}
)

这个例子几乎涵盖了所有查找命令。你想想看,如果不用这些命令,你得手动写多少硬编码路径?而且换个 NDK 版本可能就挂了。

9.7 查找命令的核心逻辑

为了让你更直观地理解,我画了一张流程图:

CMake 查找依赖库 选择查找命令 系统库 / 第三方包 / 头文件 / 任意文件 系统库 第三方包 头文件/文件 find_library() 有 Config.cmake? 有 / 没有 find_package() pkg_check_modules() 找目录还是文件? 目录 / 文件 find_path() find_file() 得到库路径 / 头文件路径 / 编译选项 链接到目标

这张图展示了 CMake 查找依赖库的完整决策流程。核心思想是:先判断你要找什么,再选对应的命令。别一上来就用 find_package(),它虽然强大,但有时候杀鸡用牛刀。

9.8 避坑指南

最后,分享几个我这些年踩过的坑:

  • 路径缓存问题:CMake 会把查找结果缓存到 CMakeCache.txt 里。如果你移动了库的位置,记得删掉缓存重新 cmake。我曾经因为这个折腾了一下午。
  • 大小写敏感:Linux 下文件名大小写敏感,Windows 不敏感。写 PATHS 时尽量用小写,避免跨平台问题。
  • REQUIRED 参数:我建议所有 find_package() 都加上 REQUIRED。找不到就报错,别让编译继续,否则链接时会出更诡异的错误。
  • NDK 下的 pkg-config:Android NDK 默认不带 pkg-config。你需要自己编译一个,或者用 PKG_CONFIG_PATH 指向 .pc 文件。我一般用 find_program() 先找 pkg-config 是否存在。
核心原则:能用查找命令就别写死路径。你的 CMakeLists.txt 应该能在任何机器上直接编译,而不是只在你自己的电脑上能跑。

好了,这一章的内容就到这里。五种查找命令各有千秋,掌握它们,你的 NDK 开发效率至少提升一倍。下一章我们聊聊如何管理多个库的版本和依赖关系,那又是一个大坑。


公众号:蓝海资料掘金营,微信deep3321