CMake 查找外部依赖
本章将介绍如何使用 find_package 命令查找和配置外部依赖包。
find_package 基础
基本语法
find_package(PackageName [version] [EXACT] [QUIET] [REQUIRED]
[COMPONENTS components...])
参数说明
| 参数 | 说明 |
|---|---|
| version | 要求的最低版本号 |
| EXACT | 要求精确版本匹配(完全等于) |
| QUIET | 静默模式,不显示查找状态信息 |
| REQUIRED | 必须找到该包,否则报错并终止配置 |
| COMPONENTS | 指定需要查找的组件(可多个) |
简单示例
# 查找 Boost 库,要求版本不低于 1.70
find_package(Boost 1.70 REQUIRED)
# 静默模式查找,不显示信息
find_package(ZLIB QUIET)
# 必须找到,否则报错
find_package(OpenCV 4.0 REQUIRED)
查找模式:Module 模式与 Config 模式
CMake 的 find_package 支持两种查找模式:Module(模块)模式和 Config(配置)模式。
Module 模式
Module 模式使用 CMake 内置的 Find 模块文件,即 Find<PackageName>.cmake。这些模块文件由 CMake 社区编写,通常用于查找流行的开源库。
特点:
- 查找路径:
CMAKE_MODULE_PATH指定的目录 - 变量命名:通常为
<PackageName>_INCLUDE_DIRS和<PackageName>_LIBS
# 设置模块路径(可选)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# 使用 Find 模块查找 OpenSSL
find_package(OpenSSL REQUIRED)
# 使用找到的库
target_include_directories(myapp PRIVATE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(myapp ${OPENSSL_LIBRARIES})
常用 Find 模块:
find_package(OpenCV REQUIRED) # 计算机视觉库
find_package(OpenSSL REQUIRED) # SSL/TLS 加密库
find_package(ZLIB REQUIRED) # 压缩库
find_package(Threads REQUIRED) # 线程库(POSIX 或 Win32)
Config 模式
Config 模式使用包供应商提供的配置文件,即 <PackageName>Config.cmake 或 <PackageName>-config.cmake。这是现代 CMake 包的标准方式。
特点:
- 查找路径:
CMAKE_PREFIX_PATH、<Package>_ROOT、系统路径等 - 提供现代目标:支持
target_link_libraries的 INTERFACE 目标
# 使用 Config 模式查找 fmt 库
find_package(fmt REQUIRED)
target_link_libraries(myapp PRIVATE fmt::fmt)
# 使用 Config 模式查找 spdlog
find_package(spdlog REQUIRED)
target_link_libraries(myapp PRIVATE spdlog::spdlog)
Config 模式自动查找的路径:
# CMake 会在以下位置查找 Config 文件:
# 1. <PackageName>_ROOT 环境变量
# 2. CMAKE_PREFIX_PATH
# 3. CMAKE_INSTALL_PREFIX
# 4. 系统默认路径(如 /usr/local 等)
模式选择
# CMake 会自动按以下顺序尝试:
# 1. 先在 Module 路径中查找 Find<Package>.cmake
# 2. 如果没找到,尝试 Config 模式查找 <Package>Config.cmake
# 可以显式指定模式
find_package(PackageName MODULE) # 强制使用 Module 模式
find_package(PackageName CONFIG) # 强制使用 Config 模式
使用找到的包
传统变量方式(不推荐)
这是早期 CMake 的方式,兼容性较好但不推荐新项目使用。
find_package(OpenCV REQUIRED)
# 通过变量使用
target_include_directories(myapp PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(myapp ${OpenCV_LIBS})
常见变量:
<Package>_INCLUDE_DIRS或<Package>_INCLUDE_PATH:头文件目录<Package>_LIBRARIES或<Package>_LIBS:库文件<Package>_FOUND:是否找到<Package>_VERSION:版本号
现代目标方式(推荐)
现代 CMake 推荐使用导入的目标(IMPORTED targets),这些是 CMake 内部定义的目标,包含了所有必要的包含目录和链接库信息。
# 使用 Threads 库(所有平台都有)
find_package(Threads REQUIRED)
target_link_libraries(myapp PRIVATE Threads::Threads)
# 使用 fmt 库
find_package(fmt REQUIRED)
target_link_libraries(myapp PRIVATE fmt::fmt)
# 使用 spdlog 库
find_package(spdlog REQUIRED)
target_link_libraries(myapp PRIVATE spdlog::spdlog)
优点:
- 传递式依赖:链接目标会自动传递依赖
- 包含目录自动设置
- 编译器标志自动配置
- 更清晰的依赖关系
必需包与可选包
必需包(REQUIRED)
如果包是项目必需的,使用 REQUIRED 参数。如果找不到,CMake 会报错并终止。
# 必须找到 Boost,否则配置失败
find_package(Boost 1.70 REQUIRED)
# 同时需要多个组件
find_package(Boost REQUIRED COMPONENTS filesystem system)
可选包(不带 REQUIRED)
如果包是可选的,可以不带 REQUIRED 参数,然后通过 <Package>_FOUND 变量判断是否找到。
# 可选包,不会报错
find_package(OpenCV)
if(OpenCV_FOUND)
message(STATUS "Found OpenCV: ${OpenCV_VERSION}")
target_link_libraries(myapp PRIVATE ${OpenCV_LIBS})
target_compile_definitions(myapp PRIVATE HAVE_OPENCV)
else()
message(STATUS "OpenCV not found, will build without it")
endif()
与 COMPONENTS 结合使用
# Boost 是必需的,filesystem 和 system 组件也是必需的
find_package(Boost REQUIRED COMPONENTS filesystem system)
# 可选包的组件
find_package(OpenCV)
if(OpenCV_FOUND)
find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui)
endif()
版本要求
最低版本
# 要求版本不低于 1.70
find_package(Boost 1.70 REQUIRED)
精确版本
# 要求精确版本 1.70.0
find_package(Boost 1.70.0 EXACT REQUIRED)
版本范围
# CMake 3.19+ 支持版本范围
find_package(Boost REQUIRED)
# 或者
find_package(Boost 1.70...1.80 REQUIRED)
版本兼容性变量
# 设置版本兼容策略
# compatible:兼容版本(默认)
# exact:精确匹配
# new:只接受新版本
set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
set(CMAKE_FIND_PACKAGE_SORT_DIRECTION ASC)
包组件(Components)
使用组件
很多库支持按组件查找,只包含需要的功能。
# OpenCV 的组件
find_package(OpenCV REQUIRED COMPONENTS core imgproc video)
# Boost 的组件
find_package(Boost REQUIRED COMPONENTS filesystem system thread)
# Qt6 的组件
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui Network)
# MySQL 的组件
find_package(MySQL REQUIRED COMPONENTS Server Client)
检查组件是否找到
find_package(Boost REQUIRED COMPONENTS filesystem system)
# 检查特定组件
if(Boost_FOUND)
message(STATUS "Boost version: ${Boost_VERSION}")
message(STATUS "Found components: ${Boost_FIND_COMPONENTS}")
endif()
使用组件目标
# 根据找到的组件链接对应的目标
find_package(Boost REQUIRED COMPONENTS filesystem system)
target_link_libraries(myapp PRIVATE
Boost::filesystem
Boost::system
)
检查查找结果
基本检查
find_package(SomePackage)
# 检查是否找到
if(SomePackage_FOUND)
message(STATUS "Found SomePackage: ${SomePackage_VERSION}")
else()
message(STATUS "SomePackage not found")
endif()
带 REQUIRED 的检查
# 使用 REQUIRED 时,如果没找到 CMake 会自动报错
# 但我们仍可以检查版本等
find_package(OpenCV 4.0 REQUIRED)
if(OpenCV_FOUND)
message(STATUS "OpenCV version: ${OpenCV_VERSION}")
message(STATUS "OpenCV include dirs: ${OpenCV_INCLUDE_DIRS}")
message(STATUS "OpenCV libraries: ${OpenCV_LIBS}")
endif()
条件构建
# 根据包是否找到选择不同实现
find_package(OpenCV)
if(OpenCV_FOUND)
# 使用 OpenCV 处理图像
target_sources(myapp PRIVATE image_processor.cpp)
else()
# 使用自定义实现
target_sources(myapp PRIVATE simple_processor.cpp)
target_compile_definitions(myapp PRIVATE NO_OPENCV)
endif()
指定查找路径
使用 CMAKE_PREFIX_PATH
# 设置自定义查找路径
set(CMAKE_PREFIX_PATH "/opt/custom/libs;$ENV{CUSTOM_LIBS}")
find_package(MyLib REQUIRED)
使用环境变量
# Linux/Mac
export CMAKE_PREFIX_PATH=/opt/custom/libs:$CMAKE_PREFIX_PATH
# Windows
set CMAKE_PREFIX_PATH=C:\custom\libs
使用 Package_ROOT 变量
# 针对特定包设置查找路径
set(MyLib_ROOT "/path/to/mylib")
find_package(MyLib REQUIRED)
查找路径优先级
<PackageName>_ROOT变量CMAKE_PREFIX_PATH变量- 系统环境变量(如 Windows 的 PATH)
- CMake 安装路径(
CMAKE_INSTALL_PREFIX) - 系统默认路径
常见包示例
Boost
# 查找 Boost,要求版本 1.70+,需要 filesystem 和 system
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
target_link_libraries(myapp PRIVATE
Boost::filesystem
Boost::system
)
# 使用 Boost 的其他组件
find_package(Boost COMPONENTS regex thread)
target_link_libraries(myapp PRIVATE
Boost::regex
Boost::thread
)
OpenCV
# 查找 OpenCV(自动使用 Config 或 Find 模块)
find_package(OpenCV REQUIRED)
# 使用传统变量方式
target_include_directories(myapp PRIVATE ${OpenCV_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${OpenCV_LIBS})
# 或者检查是否有特定模块
if(OpenCV_FOUND AND DEFINED OpenCV_VERSION)
message(STATUS "OpenCV version: ${OpenCV_VERSION}")
endif()
Qt6
# 查找 Qt6,需要 Core、Gui、Widgets 组件
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
target_link_libraries(myapp PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
# Qt5 兼容处理
if(NOT Qt6_FOUND)
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets)
target_link_libraries(myapp PRIVATE
Qt5::Core
Qt5::Gui
Qt5::Widgets
)
endif()
fmt(现代 C++ 格式化库)
# 使用 fmt 库
find_package(fmt REQUIRED)
target_link_libraries(myapp PRIVATE fmt::fmt)
# 使用头文件-only 模式
find_package(fmt REQUIRED CONFIG)
set(FMT_HEADER_ONLY ON)
spdlog(高性能日志库)
# 查找 spdlog
find_package(spdlog REQUIRED)
target_link_libraries(myapp PRIVATE spdlog::spdlog)
# 静态链接
find_package(spdlog REQUIRED CONFIG)
set(SPDLOG_BUILD_STATIC ON)
target_link_libraries(myapp PRIVATE spdlog::spdlog)
SDL2
# 查找 SDL2
find_package(SDL2 REQUIRED)
target_include_directories(myapp PRIVATE ${SDL2_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE ${SDL2_LIBRARIES})
# 如果 SDL2 提供现代目标
if(TARGET SDL2::SDL2)
target_link_libraries(myapp PRIVATE SDL2::SDL2)
endif()
编写 Find 模块
当使用的库没有提供 Config 文件时,可以编写自己的 Find 模块。
Find 模块基本结构
# FindMyLibrary.cmake
# 设置找到状态
find_library(MYLIBRARY_LIBRARY
NAMES mylibrary
PATHS /usr/local/lib /opt/mylib/lib
)
find_path(MYLIBRARY_INCLUDE_DIR
NAMES mylibrary.h
PATHS /usr/local/include /opt/mylib/include
)
# 处理版本(如果有)
if(MYLIBRARY_INCLUDE_DIR AND EXISTS "${MYLIBRARY_INCLUDE_DIR}/mylibrary/version.h")
set(MYLIBRARY_VERSION "1.0.0") # 可从 version.h 解析
endif()
# 找到库后设置导出目标
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLibrary
REQUIRED_VARS MYLIBRARY_LIBRARY MYLIBRARY_INCLUDE_DIR
VERSION_VAR MYLIBRARY_VERSION
)
# 如果找到,定义导入目标
if(MYLIBRARY_FOUND AND NOT TARGET MyLibrary::MyLibrary)
add_library(MyLibrary::MyLibrary SHARED IMPORTED)
set_target_properties(MyLibrary::MyLibrary PROPERTIES
IMPORTED_LOCATION "${MYLIBRARY_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MYLIBRARY_INCLUDE_DIR}"
)
endif()
使用自定义 Find 模块
# 设置模块路径
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# 然后正常使用 find_package
find_package(MyLibrary REQUIRED)
target_link_libraries(myapp PRIVATE MyLibrary::MyLibrary)
综合示例
cmake_minimum_required(VERSION 3.16)
project(MyProject)
# 设置模块路径
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# =====================
# 查找必需依赖
# =====================
# Threads(所有平台都需要)
find_package(Threads REQUIRED)
# Boost(需要 filesystem 和 system)
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)
# =====================
# 查找可选依赖
# =====================
# OpenCV(可选)
find_package(OpenCV)
set(USE_OPENCV OFF)
if(OpenCV_FOUND)
set(USE_OPENCV ON)
message(STATUS "Found OpenCV: ${OpenCV_VERSION}")
endif()
# fmt(推荐用于格式化)
find_package(fmt)
set(USE_FMT OFF)
if(fmt_FOUND)
set(USE_FMT ON)
message(STATUS "Found fmt: ${fmt_VERSION}")
endif()
# =====================
# 添加可执行目标
# =====================
add_executable(myapp main.cpp)
# 链接必需依赖
target_link_libraries(myapp PRIVATE
Threads::Threads
Boost::filesystem
Boost::system
)
# 链接可选依赖(如果找到)
if(USE_OPENCV)
target_link_libraries(myapp PRIVATE ${OpenCV_LIBS})
target_compile_definitions(myapp PRIVATE HAVE_OPENCV)
endif()
if(USE_FMT)
target_link_libraries(myapp PRIVATE fmt::fmt)
target_compile_definitions(myapp PRIVATE HAVE_FMT)
endif()
# =====================
# 条件编译
# =====================
if(USE_OPENCV)
target_sources(myapp PRIVATE image_processor.cpp)
else()
target_sources(myapp PRIVATE simple_processor.cpp)
endif()
小结
本章我们学习了:
- find_package 基础:
find_package的基本语法和参数 - 查找模式:Module 模式(Find 模块)和 Config 模式(Config 文件)的区别
- 使用包:传统变量方式和现代目标方式(推荐)
- 必需与可选:REQUIRED 参数和
<Package>_FOUND检查 - 版本要求:最低版本、精确版本和版本范围
- 包组件:通过 COMPONENTS 按需查找
- 常见包:Boost、OpenCV、Qt、fmt、spdlog、SDL2 的使用方法
- Find 模块:编写自定义 Find 模块来处理没有 Config 文件的库
练习
- 使用
find_package查找系统 Threads 库并链接到你的程序 - 查找 Boost 库,使用其 filesystem 和 system 组件
- 编写一个 CMakeLists.txt,根据 OpenCV 是否找到选择不同的图像处理实现
- 尝试查找一个你熟悉的库(如 nlohmann_json),使用现代目标方式链接