第十八章:查找包——find_package命令、Config模式、Module模式、编写Find模块、查找库的路径

说到CMake里最常用的命令,find_package绝对排前三。我刚开始用CMake那会儿,总觉得这命令有点玄学——明明库装好了,它就是找不到。后来踩了不少坑,才真正搞明白它背后的机制。

说白了,find_package就干一件事:帮你找到某个库的头文件和库文件路径。但CMake提供了两种完全不同的查找方式——Module模式Config模式。这两种模式的区别,是很多新手甚至老手都会搞混的地方。

18.1 find_package的两种模式

先看一个最简单的用法:

find_package(OpenCV REQUIRED)

你猜CMake会怎么找?它会先尝试Module模式,再尝试Config模式。具体流程是这样的:

  1. 先找 FindOpenCV.cmake 模块文件
  2. 如果没找到,再找 OpenCVConfig.cmakeopencv-config.cmake 配置文件
  3. 两种都没找到,就报错(如果你加了REQUIRED)

嗯,这里要注意:Module模式是CMake自己提供的查找逻辑,Config模式是库作者提供的查找逻辑。两者的责任方完全不同。

核心区别一句话总结:

  • Module模式:CMake或你写的Find模块,负责「猜」库在哪
  • Config模式:库自己安装时生成了配置文件,直接告诉你「我在这」

18.2 Module模式详解

Module模式,说白了就是CMake执行一个叫FindXXX.cmake的脚本。这个脚本里写死了查找逻辑——去哪些路径找头文件、去哪些路径找库文件。

CMake官方提供了很多现成的Find模块,比如:

  • FindBoost.cmake
  • FindOpenSSL.cmake
  • FindPython3.cmake
  • FindThreads.cmake

这些模块放在CMake的安装目录下,你直接用就行。但问题是——不是所有库都有官方Find模块。比如一些小众库、公司内部库,就得自己写。

Module模式查找路径的优先级是这样的:

  1. CMAKE_MODULE_PATH 变量指定的路径
  2. CMake安装目录下的 Modules 目录

所以如果你想用自己的Find模块,只需要:

list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/Modules")
find_package(MyLib REQUIRED)

这样CMake就会先去你的项目目录下找 FindMyLib.cmake

我的习惯: 项目里所有第三方库的Find模块统一放在 cmake/Modules/ 目录下,方便管理。曾经有个项目,大家各写各的Find模块,散落在不同目录,维护起来简直噩梦。

18.3 Config模式详解

Config模式就优雅多了。库在安装时,会生成一个配置文件(比如 OpenCVConfig.cmake),里面直接定义了库的路径、版本、依赖关系等信息。

你想想看,这相当于库作者已经帮你把路铺好了。你只需要:

find_package(OpenCV CONFIG REQUIRED)

注意这里的 CONFIG 关键字——它强制CMake只使用Config模式,跳过Module模式。我个人建议:如果库支持Config模式,尽量用CONFIG关键字,避免CMake先尝试Module模式浪费时间。

Config模式的查找路径是这样的:

优先级 路径 说明
1 <PackageName>_DIR 变量 用户手动指定配置文件所在目录
2 CMAKE_PREFIX_PATH 最常用的方式,指定安装根目录
3 系统默认路径 /usr/lib/cmake、/usr/local/lib/cmake等

举个例子,如果你把库安装在 /opt/mylib 下:

cmake .. -DCMAKE_PREFIX_PATH=/opt/mylib

CMake就会去 /opt/mylib/lib/cmake/ 下找 MyLibConfig.cmake

我曾经踩过的坑: 有次升级了库版本,但忘了更新 CMAKE_PREFIX_PATH,结果CMake一直用旧版本的配置文件。排查了半天才发现是路径缓存的问题。所以建议每次重新配置时,清理一下CMake缓存。

18.4 编写Find模块

好,到了动手环节。怎么自己写一个Find模块?我直接给个模板:

# FindMyLib.cmake
# 查找 MyLib 库

# 先检查是否已经找到
if(MyLib_FOUND)
  return()
endif()

# 查找头文件
find_path(MyLib_INCLUDE_DIR
  NAMES mylib.h
  PATHS /usr/include /usr/local/include /opt/mylib/include
)

# 查找库文件
find_library(MyLib_LIBRARY
  NAMES mylib libmylib
  PATHS /usr/lib /usr/local/lib /opt/mylib/lib
)

# 处理找到/没找到的情况
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib
  REQUIRED_VARS MyLib_INCLUDE_DIR MyLib_LIBRARY
)

# 设置导出变量
if(MyLib_FOUND)
  set(MyLib_INCLUDE_DIRS ${MyLib_INCLUDE_DIR})
  set(MyLib_LIBRARIES ${MyLib_LIBRARY})
  
  # 创建导入目标(现代CMake推荐方式)
  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()
endif()

这个模板里,有几个关键点:

  • find_pathfind_library 是核心查找命令
  • FindPackageHandleStandardArgs 提供了标准的FOUND变量处理
  • 创建 IMPORTED目标 是现代CMake的推荐做法,比直接设置变量更优雅

我建议: 尽量使用IMPORTED目标的方式。这样其他target通过 target_link_libraries 链接时,会自动继承头文件路径和编译选项,省心很多。

18.5 查找库的路径——那些你不知道的细节

查找路径这件事,看似简单,其实有不少门道。CMake查找库时,会按以下顺序搜索:

  1. CMAKE_LIBRARY_PATH 变量指定的路径
  2. CMAKE_PREFIX_PATH 变量指定的路径下的 lib/ 目录
  3. 系统默认路径(/usr/lib, /usr/local/lib 等)
  4. 环境变量 LIBRARY_PATHLD_LIBRARY_PATH

但有个坑——环境变量并不总是被CMake读取。我记得有一次,明明设置了 LD_LIBRARY_PATH,CMake就是找不到库。后来发现,CMake的 find_library 命令默认不读取 LD_LIBRARY_PATH,只读取 LIBRARY_PATH

为什么会这样?因为 LD_LIBRARY_PATH 是运行时用的,LIBRARY_PATH 才是编译时用的。这个区别,很多人搞混过。

所以我的建议是:不要依赖环境变量,显式设置 CMAKE_PREFIX_PATHCMAKE_LIBRARY_PATH。这样更可控,也更容易排查问题。

18.6 知识体系总览

为了让你更直观地理解整个查找流程,我画了张图:

find_package 查找流程 find_package(XXX) 有CONFIG关键字? 或找到Config文件? Config模式 找XXXConfig.cmake Module模式 找FindXXX.cmake 按优先级搜索路径 找到了吗? 设置变量和导入目标 报错(如果REQUIRED)

这张图把整个查找流程串起来了。你可以看到,Config模式和Module模式是两条并行的路径,最终都会走到「按优先级搜索路径」这一步。区别只在于:Module模式靠Find脚本去猜,Config模式靠库自己提供的配置文件。

18.7 实战建议

最后,给你几个实战中的建议:

  • 优先用Config模式:如果库支持,加 CONFIG 关键字,省心
  • 统一管理Find模块:所有自定义Find模块放一个目录,通过 CMAKE_MODULE_PATH 引入
  • 创建IMPORTED目标:这是现代CMake的推荐做法,比设置变量更优雅
  • 显式指定路径:用 CMAKE_PREFIX_PATH 而不是依赖环境变量
  • 注意缓存问题:切换库版本时,记得清理CMake缓存

嗯,关于查找包的内容就这些了。记住一句话:find_package不是玄学,它只是按规则找文件而已。搞懂了规则,你就再也不会被它难住了。

公众号:蓝海资料掘金营,微信deep3321