CMake中find_package指令的实现
CMake的find_package指令用于搜索第三方依赖包,支持模块模式(依赖Find.cmake文件)和配置模式(依赖库自带Config.cmake)。配置模式通过导入目标自动管理头文件和链接,是推荐方式;模块模式适用于未提供CMake配置的旧库。常用参数包括REQUIRED、QUIET、COMPONENTS等,搜索路径可自定义。
1.简介
说到 CMake 中的查找模块(find module),它本质上是一系列用于搜索第三方依赖包(包括库或可执行文件)的工具。和普通模块不同,我们通常不会用 include 去加载它,而是通过 find_package 这条命令来触发搜索流程。

基本语法看起来有点长,但日常使用往往只需要记住几个关键参数:
find_package([version] [EXACT] [QUIET] [MODULE] [REQUIRED] [[COMPONENTS] [components...]] [OPTIONAL_COMPONENTS components...] [CONFIG|NO_MODULE] [HINTS path1 [path2 ... ]] [PATHS path1 [path2 ... ]] [NO_DEFAULT_PATH] [NO_PACKAGE_ROOT_PATH] [NO_CMAKE_PATH] [NO_CMAKE_ENVIRONMENT_PATH] [NO_SYSTEM_ENVIRONMENT_PATH] [NO_CMAKE_PACKAGE_REGISTRY] [NO_CMAKE_BUILDS_PATH] [NO_CMAKE_SYSTEM_PATH] [CMAKE_FIND_ROOT_PATH_BOTH|ONLY_CMAKE_FIND_ROOT_PATH|NO_CMAKE_FIND_ROOT_PATH])
不过实际项目中更常见的简化写法是这样的:
find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem) find_package(OpenCV REQUIRED)
2.搜索模式
find_package 查找包的方式有两种,理解它们的区别是正确使用的关键。
模块模式(Module Mode)
- 借助 CMake 内置的模块文件(位于
Modules/Find)来完成搜索。它会先沿着.cmake CMAKE_MODULE_PATH变量指定的路径列表寻找,找不到再回到 CMake 安装目录的预制模块中查找。如果仍然没找到对应的模块,命令会自动切换到配置模式继续处理。 - 主要适用于那些没有提供 CMake 配置文件的旧库(比如 OpenGL、Boost 的早期组件)。
- 模块文件通常由用户或 CMake 官方编写,靠手动逻辑去定位头文件目录(
find_path)和库文件(find_library),然后定义、_FOUND 、_INCLUDE_DIRS 等变量。_LIBRARIES
配置模式(Config Mode)
- 直接查找库自带的 CMake 配置文件,比如
或Config.cmake ,以及版本文件-config.cmake 。ConfigVersion.cmake - 这是现代库的标配(例如 OpenCV、Qt、Eigen),配置文件由库的编译安装流程自动生成,内部已经定义好了导入目标(如
),头文件路径、库文件路径、编译选项等全部封装好了,不需要我们手动去设置变量。::
模式选择规则:默认情况下,CMake 会优先尝试配置模式,失败后再回退到模块模式。当然也可以通过参数强制指定:
CONFIG或NO_MODULE:强制使用配置模式MODULE:强制使用模块模式
例如:
find_package(PackageName MODULE) # 强制使用模块模式
3.常用参数
| 参数 | 作用 |
|---|---|
| REQUIRED | 表示该包是编译必需的,找不到就会直接报错终止配置。 |
| QUIET | 静默模式,找不到时不显示警告(默认会打印警告信息)。 |
| EXACT | 要求版本号严格匹配(比如必须是 3.14.1)。 |
| COMPONENTS | 指定需要哪些子组件(例如 Boost 的 system、filesystem)。 |
| HINTS | 手动提示一些可能的搜索路径,优先级高于默认路径。 |
| PATHS | 强制指定搜索路径,优先级最高。 |
| NO_DEFAULT_PATH | 完全忽略默认路径,只使用 HINTS 和 PATHS 提供的路径。 |
4.工作流程
整个查找过程可以分为几个步骤,理解之后就能更好地调试找不到包的问题了。
第一步:确定搜索路径
- 系统默认路径,比如
/usr/lib/cmake、Windows 下的C:/Program Files/ CMAKE_PREFIX_PATH环境变量指定的路径HINTS和PATHS参数中明确给出的路径
第二步:查找配置文件
- 配置模式下:寻找
或Config.cmake -config.cmake - 模块模式下:寻找 CMake 内置的
Find模块.cmake
当然也可以自定义搜索路径,比如下面这个例子:
find_package(MyLib REQUIRED
HINTS ${CMAKE_SOURCE_DIR}/../mylib/install # 优先搜索这个位置
PATHS /opt/mylib /usr/local/mylib # 备选路径
)
第三步:验证版本(如果指定了版本要求)
- 检查库的实际版本是否满足要求(比如
>=3.10或EXACT 3.14.1)。
第四步:导入目标与设置结果变量
搜索成功后,CMake 会定义一系列变量和导入目标。其中,find_package_handle_standard_args 这个命令负责设置关键的状态变量:
:布尔值,表示是否找到。_FOUND 或_INCLUDE_DIRS :头文件路径。_INCLUDES 或_LIBRARIES :库文件路径。_LIBS :版本号。_VERSION
对于配置模式,更推荐直接使用导入目标。比如:
find_package(OpenCV REQUIRED)
target_link_libraries(myapp PRIVATE ${OpenCV_LIBS}) # 模块模式
# 或者用导入目标(配置模式更常见)
target_link_libraries(myapp PRIVATE OpenCV::opencv_core)
5.内置模块示例:FindBoost.cmake
拿 Boost 库来说,模块模式的典型用法很能说明问题。
1. 调用 find_package
find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem)
2. 模块文件背后的动作
FindBoost.cmake 会帮助我们做以下几件事:
- 搜索 Boost 的头文件路径(比如
/usr/include/boost)。 - 找到指定组件的库文件(例如
libboost_system.so、libboost_filesystem.so)。 - 设置以下变量供我们使用:
Boost_FOUND # 是否找到了所有必需的组件 Boost_INCLUDE_DIRS # 头文件路径 Boost_LIBRARIES # 库文件列表(如 boost_system;boost_filesystem) Boost_VERSION # 版本号(如 1.70.0)
3. 在项目中如何使用
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${Boost_LIBRARIES})
# 如果模块支持导入目标,也可以这样写(更现代)
# target_link_libraries(myapp PRIVATE Boost::system Boost::filesystem)
endif()
6.自定义模块文件(Find.cmake)
如果依赖的库没有现成的 Find,我们可以自己动手写一个。下面是一个简化的 FindMyLib.cmake 示例:
# 1. 定义缓存变量,允许用户在 CMake 界面中手动指定路径
set(MYLIB_ROOT "" CACHE PATH "MyLib installation root")
# 2. 查找头文件
find_path(MYLIB_INCLUDE_DIR
NAMES mylib.h
HINTS ${MYLIB_ROOT}/include
PATHS /usr/local/include /opt/mylib/include
)
# 3. 查找库文件(这里以静态库为例)
find_library(MYLIB_LIBRARY
NAMES mylib mylib_static
HINTS ${MYLIB_ROOT}/lib
PATHS /usr/local/lib /opt/mylib/lib
)
# 4. 从文件中提取版本号(示例,从头文件中正则匹配)
if(MYLIB_INCLUDE_DIR)
file(STRINGS "${MYLIB_INCLUDE_DIR}/mylib.h" MYLIB_VERSION_LINE
REGEX "#define MYLIB_VERSION "[0-9.]+"")
string(REGEX REPLACE "#define MYLIB_VERSION "([0-9.]+)"" "\1"
MYLIB_VERSION "${MYLIB_VERSION_LINE}")
endif()
# 5. 调用标准结果变量处理函数
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib
REQUIRED_VARS MYLIB_LIBRARY MYLIB_INCLUDE_DIR
VERSION_VAR MYLIB_VERSION
)
# 6. 可选:创建导入目标(现代 CMake 推荐的做法)
if(MYLIB_FOUND)
add_library(MyLib::MyLib UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "${MYLIB_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}"
)
endif()
使用自定义模块时,只需两步:
# 先把模块路径加入 CMAKE_MODULE_PATH
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules")
# 然后正常调用 find_package
find_package(MyLib 2.0 REQUIRED)
# 链接时用导入目标(或直接用变量)
target_link_libraries(myapp PRIVATE MyLib::MyLib)
7.模块模式 vs 配置模式
| 特性 | 模块模式 | 配置模式 |
|---|---|---|
| 依赖文件 | CMake 内置或用户自定义的 Find<>.cmake | 库自身提供的 <>.cmake 或 <>.Config.cmake |
| 维护者 | CMake 社区或用户自己 | 库的开发者 |
| 变量命名 | 不统一(比如 Boost 用 Boost_LIBRARIES,OpenCV 用 OpenCV_LIBS) | 统一,通过导入目标来管理 |
| 推荐场景 | 旧库、没有 CMake 支持的库 | 现代库(如 Qt、Eigen) |
| 集成度 | 相对较低,需要手动处理变量 | 高,导入目标自动封装一切 |
8.总结
关于 find_package,有几个核心建议值得记住:
- 优先考虑配置模式。现代库都会提供自己的 CMake 配置文件(比如
Qt5Config.cmake),通过导入目标(如Qt5::Core)就能自动搞定头文件和链接依赖,避免变量满天飞。 - 模块模式有其历史使命。它由第三方(CMake 社区或用户)维护,难免存在版本滞后或组件缺失的问题。但对于那些没有 CMake 支持的旧库,它仍然是救命稻草。
- 自定义模块时要规范化:用
find_package_handle_standard_args统一结果变量;尽量创建IMPORTED导入目标以兼容现代 CMake 风格;通过CACHE变量允许用户手工指定路径(例如MYLIB_ROOT)。
模块模式是 CMake 兼容旧库的重要机制,即使在配置模式大行其道的今天,它仍然不可或缺。实际开发中,建议优先使用配置模式,只在处理那些“老旧”依赖时才编写自定义模块。

































