10. 查找依赖包:find_package(),Config模式与Module模式
依赖管理,说白了就是「你的项目需要别人写好的库」。在 CMake 里,find_package() 就是干这个活的。我刚开始用 CMake 时,总觉得这命令挺玄乎——明明指定了包名,它怎么就知道去哪找?后来踩了几次坑,才搞明白它背后其实就两种模式:Config 模式和 Module 模式。
10.1 find_package() 的基本用法
先看一个最简单的例子:
find_package(OpenCV REQUIRED)
target_link_libraries(my_app PRIVATE ${OpenCV_LIBS})
这里 REQUIRED 表示「找不到就报错」。如果不加,找不到时只会给个警告,然后跳过后续代码。我个人习惯是,核心依赖必须加 REQUIRED,可选功能才不加。
为什么会这样?因为 CMake 需要知道这个包到底存不存在。你想想看,如果 OpenCV 没装,你后面还链接它的库,那编译肯定崩。
10.2 Config 模式 vs Module 模式
CMake 找包时,会按这个顺序尝试:
- 先找
<PackageName>Config.cmake或<lowercase-package-name>-config.cmake(Config 模式) - 如果没找到,再找
Find<PackageName>.cmake(Module 模式)
说白了,Config 模式是「包自己提供的查找脚本」,Module 模式是「CMake 或用户提供的查找脚本」。
核心区别:
- Config 模式:包作者写好了
XXXConfig.cmake,安装时一并装到系统里。比如 OpenCV、Qt、PCL 这些大库都自带。 - Module 模式:CMake 自带了一些
FindXXX.cmake(比如 FindZLIB、FindPNG),或者你自己写一个FindXXX.cmake放到项目里。
我在项目中遇到过这样的情况:一个第三方库只提供了 .h 和 .lib,没有 Config.cmake。这时候就得自己写 FindXXX.cmake,走 Module 模式。
10.3 编写一个简单的 FindXXX.cmake
假设我们有一个叫 MyUtils 的库,头文件在 /usr/local/include,库文件在 /usr/local/lib。我们来写一个 FindMyUtils.cmake:
# FindMyUtils.cmake
# 查找 MyUtils 库的头文件和库文件
# 先找头文件
find_path(MyUtils_INCLUDE_DIR
NAMES myutils.h
PATHS /usr/local/include
/opt/local/include
)
# 再找库文件
find_library(MyUtils_LIBRARY
NAMES myutils libmyutils
PATHS /usr/local/lib
/opt/local/lib
)
# 检查是否都找到了
if(MyUtils_INCLUDE_DIR AND MyUtils_LIBRARY)
set(MyUtils_FOUND TRUE)
set(MyUtils_INCLUDE_DIRS ${MyUtils_INCLUDE_DIR})
set(MyUtils_LIBRARIES ${MyUtils_LIBRARY})
# 给调用方提供导入目标(现代 CMake 推荐做法)
if(NOT TARGET MyUtils::MyUtils)
add_library(MyUtils::MyUtils UNKNOWN IMPORTED)
set_target_properties(MyUtils::MyUtils PROPERTIES
IMPORTED_LOCATION "${MyUtils_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MyUtils_INCLUDE_DIR}"
)
endif()
else()
set(MyUtils_FOUND FALSE)
endif()
# 标准输出
mark_as_advanced(MyUtils_INCLUDE_DIR MyUtils_LIBRARY)
嗯,这里要注意几个点:
find_path和find_library是 CMake 内置的查找命令,它们会缓存结果,第二次运行会快很多。- 我习惯同时设置
MyUtils_INCLUDE_DIRS和MyUtils_LIBRARIES,这样兼容老项目用变量链接的方式。 - 导入目标
MyUtils::MyUtils是现代 CMake 的推荐做法,调用方可以直接target_link_libraries,不用手动设置包含目录。
小技巧: 如果你想让用户能通过 MyUtils_ROOT 环境变量指定查找路径,可以在 find_path 和 find_library 的 PATHS 里加上 $ENV{MyUtils_ROOT}。这样用户不用改 CMakeLists.txt,直接设环境变量就行。
10.4 两种模式的查找路径对比
| 模式 | 查找的文件 | 典型路径 | 谁提供 |
|---|---|---|---|
| Config 模式 | XXXConfig.cmake |
/usr/lib/cmake/XXX/ |
库作者 |
| Module 模式 | FindXXX.cmake |
CMake 安装目录的 Modules/ 或项目内 |
CMake 或用户 |
我曾经踩过一个坑:某个库同时提供了 Config.cmake 和 FindXXX.cmake,但 FindXXX.cmake 是旧版的,变量名不一样。CMake 默认优先走 Config 模式,所以没问题。但如果我手贱把 FindXXX.cmake 放到了项目里,CMake 会优先用项目里的 FindXXX.cmake(因为 CMAKE_MODULE_PATH 优先级更高),结果就用了旧版查找逻辑。嗯,从那以后我养成了习惯:明确指定用哪种模式。
10.5 强制指定模式
你可以用 MODULE 或 CONFIG 关键字强制 CMake 使用特定模式:
# 强制走 Module 模式
find_package(MyUtils MODULE REQUIRED)
# 强制走 Config 模式
find_package(MyUtils CONFIG REQUIRED)
我个人建议:如果库本身提供了 Config.cmake,优先用 Config 模式。因为库作者最清楚自己的库该怎么配置。只有那些没有 Config 的库,才自己写 FindXXX.cmake。
注意事项: 如果你写 FindXXX.cmake,一定要设置 XXX_FOUND 变量。CMake 就是靠这个变量判断包是否找到的。忘了设?那 if(XXX_FOUND) 永远不成立,调用方会一脸懵。
10.6 知识体系图
下面这张图帮你理清 find_package() 的完整流程:
10.7 实战建议
写 FindXXX.cmake 时,我总结了几个经验:
- 变量命名要规范:
XXX_INCLUDE_DIR、XXX_LIBRARY、XXX_FOUND是约定俗成的,别自己发明新名字。 - 提供导入目标:现代 CMake 项目更倾向于用
target_link_libraries而不是直接操作变量。所以尽量创建XXX::XXX这样的导入目标。 - 考虑跨平台:Windows 上库文件可能叫
.lib,Linux 上叫.so或.a。用find_library时,CMake 会自动处理这些差异。 - 缓存变量:用
mark_as_advanced把查找路径标记为高级变量,这样普通用户不会在 CMake GUI 里被一堆内部变量吓到。
调试小技巧: 如果 find_package 找不到包,可以加 --debug-find 参数运行 CMake:cmake --debug-find ..。它会打印出每一步查找了哪些路径,非常直观。我曾经靠这个参数发现 CMake 在找 .cmake 文件时,路径里多了一个斜杠,导致路径拼接错误。
好了,关于 find_package() 的两种模式就聊到这。记住:Config 模式是「库主动告诉你它在哪」,Module 模式是「你去帮 CMake 找到它」。写 FindXXX.cmake 时,保持简单、规范,你的队友会感谢你的。
公众号:蓝海资料掘金营,微信 deep3321