CMake 从入门到精通:完整教程 / 第 7 章:查找模块详解
第 7 章:查找模块详解
7.1 find_package 工作原理
当执行 find_package(Foo) 时,CMake 按照以下顺序查找:
find_package(Foo)
│
├── Module 模式(先尝试)
│ └── 查找 FindFoo.cmake
│ ├── CMAKE_MODULE_PATH 中的路径
│ └── CMake 内置模块目录
│
└── Config 模式(后尝试)
└── 查找 FooConfig.cmake 或 foo-config.cmake
├── CMAKE_PREFIX_PATH
├── Foo_DIR
├── 注册表(Windows)
└── 标准系统路径
7.1.1 Module 模式(Find 模块)
# 使用 Module 模式
find_package(OpenSSL MODULE REQUIRED)
# CMake 查找 FindOpenSSL.cmake
# 该文件由 CMake 内置提供
Module 模式查找的文件:Find<PackageName>.cmake
7.1.2 Config 模式
# 使用 Config 模式
find_package(OpenSSL CONFIG REQUIRED)
# CMake 查找 openssl-config.cmake 或 OpenSSLConfig.cmake
# 由包自身安装时提供
Config 模式查找的文件:
<PackageName>Config.cmake<lowercase-package-name>-config.cmake
7.1.3 模式对比
| 方面 | Module 模式 | Config 模式 |
|---|---|---|
| 查找文件 | Find<Package>.cmake | <Package>Config.cmake |
| 提供者 | CMake 内置 / 用户编写 | 包自身提供 |
| 变量风格 | <PACKAGE>_<VAR> | 取决于包实现 |
| 导入目标 | 可能创建 | 通常创建 |
| 现代推荐 | 作为回退方案 | ✅ 优先使用 |
7.2 CMake 内置 Find 模块
CMake 提供了大量内置的 Find 模块:
| 模块 | 包名 | 创建的目标 |
|---|---|---|
| FindOpenSSL | OpenSSL | OpenSSL::SSL, OpenSSL::Crypto |
| FindZLIB | ZLIB | ZLIB::ZLIB |
| FindThreads | Threads | Threads::Threads |
| FindCURL | CURL | CURL::libcurl |
| FindSQLite3 | SQLite3 | SQLite::SQLite3 |
| FindBoost | Boost | Boost::filesystem 等 |
| FindPython3 | Python3 | Python3::Interpreter, Python3::Module |
| FindGit | Git | Git::Git |
| FindJPEG | JPEG | JPEG::JPEG |
| FindPNG | PNG | PNG::PNG |
| FindLua | Lua | Lua::Lua |
| FindBISON | BISON | BISON::BISON |
| FindFLEX | FLEX | FLEX::Flex |
7.2.1 使用内置 Find 模块
# Threads — 跨平台线程支持
find_package(Threads REQUIRED)
target_link_libraries(myapp PRIVATE Threads::Threads)
# ZLIB 压缩库
find_package(ZLIB REQUIRED)
target_link_libraries(myapp PRIVATE ZLIB::ZLIB)
# CURL
find_package(CURL REQUIRED)
target_link_libraries(myapp PRIVATE CURL::libcurl)
# Python3
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
target_link_libraries(myapp PRIVATE Python3::Python)
7.2.2 Find 模块设置的变量
每个 Find 模块都遵循命名约定:
find_package(ZLIB)
# 设置的变量
message("${ZLIB_FOUND}") # TRUE/FALSE
message("${ZLIB_VERSION}") # 1.2.13
message("${ZLIB_VERSION_STRING}") # 1.2.13
message("${ZLIB_INCLUDE_DIRS}") # /usr/include
message("${ZLIB_LIBRARIES}") # /usr/lib/libz.so
message("${ZLIB_DEFINITIONS}") # 编译定义
7.2.3 设置查找路径
# 通用路径
list(APPEND CMAKE_PREFIX_PATH "/opt/dependencies")
# 通过环境变量
# export CMAKE_PREFIX_PATH="/opt/dependencies:$CMAKE_PREFIX_PATH"
# 专用路径变量
# cmake -DOPENSSL_ROOT_DIR=/opt/openssl
# cmake -DZLIB_ROOT=/opt/zlib
# cmake -DBOOST_ROOT=/opt/boost
# cmake -DCURL_ROOT=/opt/curl
7.3 Config 模式详解
7.3.1 Config 文件结构
一个标准的 FooConfig.cmake:
# FooConfig.cmake
# 保护:避免重复包含
if(TARGET Foo::Foo)
return()
endif()
# 包含版本文件
include(CMakeFindDependencyMacro)
# 声明自身依赖
find_dependency(Threads)
find_dependency(ZLIB)
# 包含目标定义文件
include("${CMAKE_CURRENT_LIST_DIR}/FooTargets.cmake")
# 设置版本
set(Foo_VERSION "@PROJECT_VERSION@")
set(Foo_FOUND TRUE)
# 检查兼容性
check_required_components(Foo)
7.3.2 版本文件
# FooConfigVersion.cmake
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/FooConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion # 主版本兼容
)
| 兼容性选项 | 含义 |
|---|---|
AnyNewerVersion | 任何更新版本都兼容 |
SameMajorVersion | 同主版本兼容(推荐) |
SameMinorVersion | 同次版本兼容 |
ExactVersion | 精确版本匹配 |
7.3.3 安装和导出
# 创建导出集
install(TARGETS mylib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
# 安装头文件
install(DIRECTORY include/ DESTINATION include)
# 安装目标文件
install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
NAMESPACE MyLib::
DESTINATION lib/cmake/MyLib
)
# 生成配置文件
include(CMakePackageConfigHelpers)
configure_package_config_file(
cmake/MyLibConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
INSTALL_DESTINATION lib/cmake/MyLib
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
DESTINATION lib/cmake/MyLib
)
7.4 版本约束
7.4.1 基本版本要求
# 最低版本
find_package(Boost 1.70 REQUIRED)
# 精确版本
find_package(Boost 1.70.0 EXACT REQUIRED)
# 范围版本(CMake 3.19+)
find_package(Boost 1.70...1.85 REQUIRED)
7.4.2 版本比较
find_package(OpenSSL 1.1)
if(OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0")
message("OpenSSL 3.x+ detected")
target_compile_definitions(myapp PRIVATE USE_OPENSSL3_API)
endif()
版本比较操作符:
| 操作符 | 含义 |
|---|---|
VERSION_LESS | < |
VERSION_GREATER | > |
VERSION_EQUAL | == |
VERSION_LESS_EQUAL | <= |
VERSION_GREATER_EQUAL | >= |
7.5 组件(Components)
7.5.1 查找组件
# Boost 组件
find_package(Boost REQUIRED COMPONENTS
filesystem
system
thread
program_options
)
target_link_libraries(myapp PRIVATE
Boost::filesystem
Boost::system
Boost::thread
Boost::program_options
)
# Qt6 组件
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Network)
target_link_libraries(myapp PRIVATE
Qt6::Core
Qt6::Widgets
Qt6::Network
)
7.5.2 可选组件
# 可选组件
find_package(Boost QUIET COMPONENTS filesystem)
if(Boost_FOUND AND TARGET Boost::filesystem)
target_link_libraries(myapp PRIVATE Boost::filesystem)
target_compile_definitions(myapp PRIVATE HAS_BOOST_FILESYSTEM)
endif()
7.5.3 检查组件
find_package(Boost COMPONENTS filesystem system)
# 检查特定组件
if(Boost_FILESYSTEM_FOUND)
message("Boost.Filesystem found: ${Boost_FILESYSTEM_LIBRARY}")
endif()
if(Boost_SYSTEM_FOUND)
message("Boost.System found: ${Boost_SYSTEM_LIBRARY}")
endif()
7.6 自定义 Find 模块
7.6.1 Find 模块模板
# cmake/FindMyLib.cmake
# 1. 头文件保护
if(TARGET MyLib::MyLib)
return()
endif()
# 2. 查找头文件
find_path(MyLib_INCLUDE_DIR
NAMES mylib/mylib.h
PATHS
${MyLib_ROOT}/include
$ENV{MyLib_ROOT}/include
PATH_SUFFIXES include
)
# 3. 查找库文件
find_library(MyLib_LIBRARY
NAMES mylib
PATHS
${MyLib_ROOT}/lib
$ENV{MyLib_ROOT}/lib
PATH_SUFFIXES lib lib64
)
# 4. 查找版本
if(MyLib_INCLUDE_DIR AND EXISTS "${MyLib_INCLUDE_DIR}/mylib/version.h")
file(STRINGS "${MyLib_INCLUDE_DIR}/mylib/version.h" version_line
REGEX "^#define[ \t]+MYLIB_VERSION[ \t]+\"[^\"]+\"")
string(REGEX REPLACE "^#define[ \t]+MYLIB_VERSION[ \t]+\"([^\"]+)\".*" "\\1"
MyLib_VERSION "${version_line}")
unset(version_line)
endif()
# 5. 处理参数
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib
REQUIRED_VARS MyLib_LIBRARY MyLib_INCLUDE_DIR
VERSION_VAR MyLib_VERSION
)
# 6. 创建导入目标
if(MyLib_FOUND)
set(MyLib_INCLUDE_DIRS ${MyLib_INCLUDE_DIR})
set(MyLib_LIBRARIES ${MyLib_LIBRARY})
if(NOT TARGET MyLib::MyLib)
add_library(MyLib::MyLib UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "${MyLib_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MyLib_INCLUDE_DIR}"
)
endif()
mark_as_advanced(MyLib_INCLUDE_DIR MyLib_LIBRARY)
endif()
7.6.2 使用自定义 Find 模块
# 将 Find 模块目录加入搜索路径
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# 使用自定义模块
find_package(MyLib REQUIRED)
target_link_libraries(myapp PRIVATE MyLib::MyLib)
7.6.3 Find 模块标准模板
# 完整的 Find 模块模板
# cmake/Find<Package>.cmake
#[=======================================================================[.rst:
Find<Package>
---------
查找 <Package> 库。
导入目标
^^^^^^^^
``<Package>::<Package>`` - 主库目标。
结果变量
^^^^^^^^
``<Package>_FOUND`` - 是否找到
``<Package>_VERSION`` - 版本号
``<Package>_INCLUDE_DIRS`` - 包含目录
``<Package>_LIBRARIES`` - 库文件列表
缓存变量
^^^^^^^^
``<Package>_INCLUDE_DIR`` - 头文件目录
``<Package>_LIBRARY`` - 库文件路径
用法示例
^^^^^^^^
.. code-block:: cmake
find_package(<Package> REQUIRED)
target_link_libraries(myapp PRIVATE <Package>::<Package>)
#]=======================================================================]
include(FindPackageHandleStandardArgs)
# ... 查找逻辑 ...
7.7 编写 Config 模式的配置
7.7.1 模板文件
# cmake/MyLibConfig.cmake.in
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# 声明依赖
find_dependency(Threads)
if(@USE_OPENSSL@)
find_dependency(OpenSSL)
endif()
# 包含目标
include("${CMAKE_CURRENT_LIST_DIR}/MyLibTargets.cmake")
# 检查组件
check_required_components(MyLib)
7.7.2 生成和安装
include(CMakePackageConfigHelpers)
# 生成配置文件
configure_package_config_file(
cmake/MyLibConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
INSTALL_DESTINATION lib/cmake/MyLib
NO_SET_AND_CHECK_MACRO
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# 生成版本文件
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
# 安装
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake"
DESTINATION lib/cmake/MyLib
)
7.8 业务场景
场景:支持多种查找方式
# 尝试 Config 模式(优先)
find_package(MyLib CONFIG QUIET)
if(NOT MyLib_FOUND)
# 回退到 Module 模式
find_package(MyLib MODULE QUIET)
endif()
if(NOT MyLib_FOUND)
# 都找不到,使用 FetchContent
include(FetchContent)
FetchContent_Declare(MyLib
GIT_REPOSITORY https://github.com/example/mylib.git
GIT_TAG v2.0.0
)
FetchContent_MakeAvailable(MyLib)
endif()
target_link_libraries(myapp PRIVATE MyLib::MyLib)
场景:版本约束和降级策略
# 优先查找 3.x
find_package(MyLib 3.0 QUIET)
if(NOT MyLib_FOUND)
# 降级到 2.x
find_package(MyLib 2.0 REQUIRED)
message(WARNING "使用 MyLib 2.x,部分功能不可用")
target_compile_definitions(myapp PRIVATE MYLIB_V2_COMPAT)
endif()
7.9 调试 find_package
7.9.1 调试输出
# 查看查找过程
cmake -S . -B build --debug-find-pkg=MyLib
# 查看所有查找
cmake -S . -B build --debug-find
# 设置日志级别
cmake -S . -B build --log-level=VERBOSE
7.9.2 检查缓存
# 查看缓存中的查找结果
cmake -S . -B build -L | grep -i mylib
# MyLib_DIR:PATH=/opt/mylib/lib/cmake/MyLib
# MyLib_INCLUDE_DIR:PATH=/opt/mylib/include
# MyLib_LIBRARY:FILEPATH=/opt/mylib/lib/libmylib.so
7.9.3 常见问题排查
# 问题:find_package 找不到库
# 解决方案 1:设置 CMAKE_PREFIX_PATH
# cmake -DCMAKE_PREFIX_PATH=/opt/deps
# 解决方案 2:设置专用路径
# cmake -DMyLib_ROOT=/opt/mylib
# 解决方案 3:设置 Config 文件目录
# cmake -DMyLib_DIR=/opt/mylib/lib/cmake/MyLib
# 问题:找到了错误的版本
# 解决:使用 EXACT 关键字
find_package(MyLib 2.0 EXACT REQUIRED)
7.10 注意事项
| 问题 | 说明 |
|---|---|
| Module vs Config 优先级 | Module 默认先查找,可用 CONFIG/MODULE 显式指定 |
| 包名大小写 | find_package 大小写敏感 |
| 版本兼容性 | 注意包的版本格式可能与 CMake 不一致 |
| 组件不是所有包都支持 | 具体组件名参考包的文档 |
| 导入目标 vs 变量 | 优先使用导入目标(Foo::Foo) |
7.11 扩展阅读
上一章:第 6 章 — 库的构建与使用 | 下一章:第 8 章 — 命令与控制流 →