跳到主要内容

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)

优点:

  1. 传递式依赖:链接目标会自动传递依赖
  2. 包含目录自动设置
  3. 编译器标志自动配置
  4. 更清晰的依赖关系

必需包与可选包

必需包(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)

查找路径优先级

  1. <PackageName>_ROOT 变量
  2. CMAKE_PREFIX_PATH 变量
  3. 系统环境变量(如 Windows 的 PATH)
  4. CMake 安装路径(CMAKE_INSTALL_PREFIX
  5. 系统默认路径

常见包示例

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()

小结

本章我们学习了:

  1. find_package 基础find_package 的基本语法和参数
  2. 查找模式:Module 模式(Find 模块)和 Config 模式(Config 文件)的区别
  3. 使用包:传统变量方式和现代目标方式(推荐)
  4. 必需与可选:REQUIRED 参数和 <Package>_FOUND 检查
  5. 版本要求:最低版本、精确版本和版本范围
  6. 包组件:通过 COMPONENTS 按需查找
  7. 常见包:Boost、OpenCV、Qt、fmt、spdlog、SDL2 的使用方法
  8. Find 模块:编写自定义 Find 模块来处理没有 Config 文件的库

练习

  1. 使用 find_package 查找系统 Threads 库并链接到你的程序
  2. 查找 Boost 库,使用其 filesystem 和 system 组件
  3. 编写一个 CMakeLists.txt,根据 OpenCV 是否找到选择不同的图像处理实现
  4. 尝试查找一个你熟悉的库(如 nlohmann_json),使用现代目标方式链接

参考资料