强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 模块:

模块包名创建的目标
FindOpenSSLOpenSSLOpenSSL::SSL, OpenSSL::Crypto
FindZLIBZLIBZLIB::ZLIB
FindThreadsThreadsThreads::Threads
FindCURLCURLCURL::libcurl
FindSQLite3SQLite3SQLite::SQLite3
FindBoostBoostBoost::filesystem
FindPython3Python3Python3::Interpreter, Python3::Module
FindGitGitGit::Git
FindJPEGJPEGJPEG::JPEG
FindPNGPNGPNG::PNG
FindLuaLuaLua::Lua
FindBISONBISONBISON::BISON
FindFLEXFLEXFLEX::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 章 — 命令与控制流 →