第九章 查找依赖库: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})
EGL_LIB、GLESv2_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
)
这两个命令的搜索顺序是:先搜 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_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_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 是否存在。
好了,这一章的内容就到这里。五种查找命令各有千秋,掌握它们,你的 NDK 开发效率至少提升一倍。下一章我们聊聊如何管理多个库的版本和依赖关系,那又是一个大坑。
公众号:蓝海资料掘金营,微信deep3321