在 CMake 中,find_package()
是一个核心函数,用于查找并加载外部依赖库的配置。它的主要作用是定位头文件、库文件,并设置相关变量,以便后续编译和链接。以下是详细解析:
1. 基本语法
find_package(<PackageName> [version] [REQUIRED] [COMPONENTS components...] [OPTIONAL_COMPONENTS components...] [NO_MODULE] [QUIET] [CONFIG])
<PackageName>
:要查找的包名(如Boost
、Eigen3
)。version
:可选,指定最低版本(如3.3
)。REQUIRED
:可选,如果找不到包,则报错并终止构建。COMPONENTS
:指定包的子组件(如 Boost 的system
、filesystem
)。NO_MODULE
:跳过模块模式,直接使用配置模式(Config Mode)。QUIET
:静默模式,不输出警告信息。
find_package()
中的 REQUIRED
是可选的。如果不指定 REQUIRED
,CMake 会尝试查找包,但即使找不到也不会报错,而是继续执行后续的 CMake 脚本。此时,你需要通过检查 <PackageName>_FOUND
变量来判断是否成功找到该包,并决定后续逻辑。
1.1 REQUIRED
关键字
1.1.1 没有REQUIRED
关键字的情况
find_package(<PackageName> [version] [COMPONENTS components...])
- 如果找到包,CMake 会设置
<PackageName>_FOUND=TRUE
和相关变量(如_INCLUDE_DIRS
、_LIBRARIES
)。 - 如果未找到包,
<PackageName>_FOUND=FALSE
,但不会中断构建。
1.1.2 典型场景
1.1.2.1 可选依赖库
某些库可能是可选的(例如,用于增强功能但不影响核心功能):
find_package(OpenCV) # 不强制要求 OpenCVif(OpenCV_FOUND)message(STATUS "OpenCV found, enabling advanced features")add_definitions(-DUSE_OPENCV)target_link_libraries(my_app PRIVATE ${OpenCV_LIBRARIES})
else()message(WARNING "OpenCV not found, some features will be disabled")
endif()
1.1.2.2 多版本兼容
尝试查找高版本库,失败时回退到低版本或默认路径:
find_package(Python 3.8) # 首选 Python 3.8
if(NOT Python_FOUND)find_package(Python 3.6) # 回退到 Python 3.6
endif()
1.1.2.3 平台特定依赖
某些库仅在特定平台需要(如 Linux 的 libudev
):
if(UNIX AND NOT APPLE)find_package(UDEV) # 仅在 Linux 下查找if(UDEV_FOUND)target_link_libraries(my_app PRIVATE udev)endif()
endif()
1.1.2.4 关键注意事项
1.1.2.4.1 必须检查 _FOUND
变量
如果不检查,后续使用未找到的库会导致编译或链接错误:
find_package(CURL)
# 错误:直接使用 ${CURL_LIBRARIES} 而不检查 CURL_FOUND
1.1.2.4.2 与 REQUIRED
的对比
行为 | REQUIRED 模式 | 非 REQUIRED 模式 |
---|---|---|
找不到包时 | 报错并终止 CMake 配置 | 继续执行,<PackageName>_FOUND=FALSE |
适用场景 | 核心依赖(如 ROS、Eigen) | 可选功能或平台特定依赖 |
代码复杂度 | 无需额外判断 | 需手动检查 _FOUND 变量 |
1.1.2.4.3 组件(COMPONENTS
)的非必需性
即使包支持组件,也可以不标记 REQUIRED
:
find_package(Boost COMPONENTS system)
if(Boost_FOUND AND Boost_SYSTEM_FOUND)target_link_libraries(my_app PRIVATE Boost::system)
endif()
1.1.3 实际案例
1.1.3.1 ROS 中的可选消息依赖
find_package(catkin COMPONENTSroscppsensor_msgs # 可选依赖
)if(catkin_FOUND AND sensor_msgs_FOUND)add_definitions(-DUSE_SENSOR_MSGS)
endif()
1.1.3.2 多图形后端支持
find_package(OpenGL)
find_package(Vulkan)if(OpenGL_FOUND)target_link_libraries(my_engine PRIVATE OpenGL::GL)
elseif(Vulkan_FOUND)target_link_libraries(my_engine PRIVATE Vulkan::Vulkan)
else()message(FATAL_ERROR "No supported graphics API found!")
endif()
1.2 COMPONENTS
:指定子组件
1.2.1 作用
- 用于声明需要查找的包的子模块或组件(例如 Boost 的
system
、filesystem
,或 ROS 的roscpp
、tf
)。 - 如果某个组件未找到,且标记了
REQUIRED
,CMake 会报错。
1.2.2 示例
find_package(Boost REQUIRED COMPONENTS system filesystem)
- 查找 Boost 库,并明确要求
system
和filesystem
两个组件。 - 成功后,变量
Boost_SYSTEM_FOUND
和Boost_FILESYSTEM_FOUND
会被设为TRUE
。
1.2.3 关键点
- 需要包本身支持组件化(如 Boost、Qt、ROS)。
- 每个组件可能有独立的变量(如
Boost_SYSTEM_LIBRARY
)。
1.3 NO_MODULE
:强制跳过模块模式
1.3.1 作用
- 强制 CMake 跳过传统的
Find<Package>.cmake
模块模式,直接使用包的现代配置模式(即查找<Package>Config.cmake
文件)。 - 适用于明确知道包提供了
Config.cmake
文件的情况(如 Eigen3、现代 Qt)。
1.3.2 示例
find_package(Eigen3 3.3 REQUIRED NO_MODULE)
- 跳过
FindEigen3.cmake
,直接查找Eigen3Config.cmake
。 - 避免因旧版模块文件与新版本库不兼容导致的问题。
1.3.3 关键点
- 通常用于现代 CMake 兼容的库(如 Eigen3、VTK)。
- 如果包没有提供
Config.cmake
文件,使用NO_MODULE
会导致查找失败。
1.3.4 COMPONENTS
与NO_MODULE
两者关系总结
特性 | COMPONENTS | NO_MODULE |
---|---|---|
用途 | 指定包的子组件 | 强制使用配置模式(跳过模块模式) |
依赖包的支持 | 包需支持组件化(如 Boost、Qt) | 包需提供 Config.cmake 文件 |
典型场景 | find_package(Boost COMPONENTS system) | find_package(Eigen3 NO_MODULE) |
是否互斥 | 可与 NO_MODULE 同时使用 | 可与 COMPONENTS 同时使用 |
1.3.5 同时使用的例子
find_package(Qt6 REQUIRED COMPONENTS Core Gui NO_MODULE)
- 强制使用
Qt6Config.cmake
(跳过FindQt6.cmake
),并指定需要Core
和Gui
组件。
1.3.6 为什么有些包需要同时用 COMPONENTS
和 NO_MODULE
?
- 例如 Qt6 既提供了
Config.cmake
文件,又将功能拆分为多个组件(Core、Gui、Widgets 等)。此时:find_package(Qt6 REQUIRED COMPONENTS Core NO_MODULE)
NO_MODULE
:确保使用Qt6Config.cmake
(现代方式)。COMPONENTS
:明确要求Core
组件。
1.3.7 如果包不支持组件,但用了 COMPONENTS
会怎样?
- CMake 会报错,例如:
错误信息类似:find_package(Eigen3 COMPONENTS Core) # Eigen3 无组件,会报错
Could not find a configuration file for package "Eigen3" that specifies component "Core".
1.3.8 如何知道一个包是否支持组件?
- 查看官方文档或包的
Config.cmake
文件。 - 例如 Boost 的组件列表见:Boost Libraries。
1.3.9 对比示例
案例 1:仅用 COMPONENTS
find_package(Boost REQUIRED COMPONENTS system)
- 查找 Boost 的
system
组件,使用默认的模块模式(FindBoost.cmake
)。
案例 2:仅用 NO_MODULE
find_package(Eigen3 NO_MODULE)
- 跳过
FindEigen3.cmake
,直接查找Eigen3Config.cmake
。
案例 3:同时使用
find_package(Qt5 COMPONENTS Widgets NO_MODULE)
- 强制使用
Qt5Config.cmake
,并指定Widgets
组件。
1.3.10 总结
COMPONENTS
:用于指定包的子模块,与包的功能拆分相关。NO_MODULE
:用于控制查找模式,与包的配置方式相关。- 两者可独立或组合使用,具体取决于包的支持情况。
2. 工作模式
find_package
有两种查找模式:
(1) 模块模式(Module Mode)
- 查找
<PackageName>Config.cmake
或Find<PackageName>.cmake
文件。 - 通常用于传统库(如
FindBoost.cmake
)。 - 优先级低于配置模式。
(2) 配置模式(Config Mode)
- 查找
<PackageName>Config.cmake
文件(通常由库的开发者提供)。 - 现代库(如
Eigen3
、Qt5
)优先使用此模式。 - 通过
NO_MODULE
强制启用。
3. 关键变量
成功调用 find_package
后,CMake 会设置以下变量(以 Eigen3
为例):
变量名 | 作用 | 示例值 |
---|---|---|
<PackageName>_FOUND | 是否找到包 | Eigen3_FOUND = TRUE |
<PackageName>_INCLUDE_DIR | 头文件目录 | Eigen3_INCLUDE_DIRS = /usr/include/eigen3 |
<PackageName>_LIBRARIES | 库文件路径 | Boost_LIBRARIES = /usr/lib/libboost_system.so |
<PackageName>_VERSION | 版本号 | Eigen3_VERSION = 3.4.0 |
4. 具体示例解析
(1) ROS 的 catkin
包
find_package(catkin REQUIRED COMPONENTSroscpp tf
)
- 作用:查找 ROS 的
catkin
构建系统,并加载roscpp
和tf
的依赖。 - 生成的变量:
catkin_INCLUDE_DIRS
:ROS 包的头文件路径。catkin_LIBRARIES
:ROS 包的库文件路径。
- 后续用法:
include_directories(${catkin_INCLUDE_DIRS}) target_link_libraries(my_node ${catkin_LIBRARIES})
(2) Boost 库
find_package(Boost REQUIRED COMPONENTSsystem filesystem
)
- 作用:查找 Boost 库,并指定需要
system
和filesystem
组件。 - 生成的变量:
Boost_INCLUDE_DIRS
:Boost 头文件路径(如/usr/include
)。Boost_LIBRARIES
:组件库路径(如boost_system
、boost_filesystem
)。
- 后续用法:
target_include_directories(my_app PRIVATE ${Boost_INCLUDE_DIRS}) target_link_libraries(my_app ${Boost_LIBRARIES})
(3) Eigen3 线性代数库
find_package(Eigen3 3.3 REQUIRED NO_MODULE)
- 作用:查找 Eigen3 库,要求版本 ≥ 3.3,并强制使用配置模式(
NO_MODULE
)。 - 生成的变量:
Eigen3_INCLUDE_DIRS
:Eigen 头文件路径(如/usr/include/eigen3
)。Eigen3_VERSION
:版本号。
- 后续用法:
target_include_directories(my_app PRIVATE ${Eigen3_INCLUDE_DIRS})
(4) PkgConfig 工具
find_package(PkgConfig REQUIRED)
- 作用:启用
pkg-config
支持(用于查找没有 CMake 配置文件的库)。 - 后续用法:
pkg_search_module(GLIB REQUIRED glib-2.0) include_directories(${GLIB_INCLUDE_DIRS}) target_link_libraries(my_app ${GLIB_LIBRARIES})
5. 常见问题
(1) 为什么有些包需要 NO_MODULE
?
- 例如
Eigen3
只有Eigen3Config.cmake
,没有FindEigen3.cmake
,因此需强制使用配置模式。
(2) REQUIRED
的作用是什么?
- 如果找不到包,CMake 会报错并停止构建。避免后续链接时出现未定义错误。
(3) 可以没有REQUIRED
关键字吗
-
是的,
5. 调试技巧
- 查看查找结果:
find_package(Foo) message(STATUS "Foo_FOUND = ${Foo_FOUND}, Foo_INCLUDE_DIRS = ${Foo_INCLUDE_DIRS}")
- 手动指定路径(用于调试):
set(Foo_DIR "/path/to/FooConfig.cmake") # 提示 CMake 查找路径
总结
- **省略
REQUIRED** 时,
find_package()变为“尝试查找”,需配合
_FOUND` 变量使用。 - 适用场景:可选功能、多版本回退、平台特定依赖。
- 优势:灵活控制构建流程,避免因非核心依赖缺失导致构建失败。
(4) 如何调试 find_package
失败?
- 检查路径是否在
CMAKE_PREFIX_PATH
中:message(STATUS "CMAKE_PREFIX_PATH = ${CMAKE_PREFIX_PATH}")
- 手动指定路径:
set(Eigen3_DIR "/path/to/eigen3/share/eigen3/cmake")
6. 总结
场景 | 示例命令 | 关键变量 |
---|---|---|
ROS 包依赖 | find_package(catkin REQUIRED COMPONENTS roscpp) | catkin_INCLUDE_DIRS |
Boost 组件 | find_package(Boost REQUIRED COMPONENTS system) | Boost_LIBRARIES |
强制配置模式 | find_package(Eigen3 NO_MODULE) | Eigen3_INCLUDE_DIRS |
使用 pkg-config | find_package(PkgConfig) | PKG_CONFIG_FOUND |
通过 find_package
,CMake 可以灵活地集成第三方库,而 ROS 的 catkin
进一步扩展了这一机制,使其支持 ROS 特有的依赖管理。