本文是对简书 CMake命令之find_package介绍、CMake中find_package命令的搜索模式之模块模式(Module mode)、CMake中find_package命令的搜索模式之配置模式(Config mode)、pkg-config用法详解 四篇文章的整合,并根据自己的环境修改。
搜索模式
find_package 用于查找包(通常是使用三方库),并返回关于包的细节(使用包所依赖的头文件、库文件、编译选项、链接选项等)。
与 find_libaray 直接在指定搜索目录下搜索库不同,find_package 命令可以获取更多的信息,那么它的搜索方式也是与 find_libaray 不一样,它有两种不同的搜索方式,因此在介绍这个命令的细节之前,先简单介绍一下 find_package 命令的两种搜索模式:模块模式(Module mode)和配置模式(Config mode)。
我们将以两个例子分别展示两种搜索模式。本例中我会利用自己系统(Ubuntu 22.04)自带的 python 和自己编写的 mymath 库(代码在 pkg-config用法详解 中给出),尝试搜索这两个库。
模块模式(Module mode)
在该模式下,CMake 会搜索一个名为 Find<PackageName>.cmake 的文件,其中 <PackageName> 为待搜索包的名称。
搜索路径的顺序依次是:
- 从变量 CMAKE_MODULE_PATH 指定的路径中进行查找
- 从 CMake 安装路径中查找。CMake 会在其安装路径下提供很多
.cmake文件,例如/XXX/cmake/Modules/目录下(不同的系统安装目录可能不一致)
如果找到文件 Find<PackageName>.cmake,CMake 会读取并处理该文件,简而言之,它负责检查一些条件(如版本号是否满足等)是否满足,并在找到包后,返回给调用者一些变量,用以获取包的详细信息。。
一般来说,Find<PackageName>.cmake 文件不是随包本身一起提供的,更多的是外部针对已有包的重新包装,例如操作系统、CMake 程序、甚至是调用 find_package 命令的工程针对已有的包提供针对该包的 .cmake 文件。
模块模式查找 Python 库
模块模式的 CMakeLists.txt 内容如下
1 | # CMakeLists.txt |
在终端执行 cmake 命令后,输出如下:
1 | ~/Code/cmake/python/build ❯ cmake .. |
模块模式查找自己编写的 mymath 库
我们知道在 Module 模式下,当调用 find_package 命令查找 <PackageName> 包的时候,实际上会去查找一个名为 Find<PackageName>.cmake 的文件,这个文件的主要任务就是确定一个包是否可用,查找的结果会反映在变量 <PackageName>_FOUND 上供 find_package 的调用者使用。当找到可用的包,同时也会提供使用这个包所需要的变量、宏和导入目标(例如库文件)。
而我们自己写的 mymath 库目前是没有 Findmymath.cmake 文件的,需要自己写一个。
标准变量名称
Find<PackageName>.cmake 文件承担了定义 <PackageName> 包相关的变量的作用,这些变量称作"标准变量"。一旦 find_package 调用成功,这些变量将返回给调用者使用,为了保证不同的包之间返回的变量不冲突,对编写的 Find<PackageName>.cmake 返回的标准变量名称有如下约束:所有的变量都是以 PackageName_ 开头,PackageName 就是文件 Find<PackageName>.cmake 中的 <PackageName>,必须完全一致,大小写敏感。
PackageName_INCLUDE_DIRS:使用包需要包含的头文件。PackageName_LIBRARIES:使用包所需要的库文件,是全路径或者链接器能在库搜索目录下找到的库文件名称。PackageName_DEFINITIONS:使用包所需要的编译选项。PackageName_LIBRARY:库的路径,只有当包提供的是单个库的时候才能使用这形式。PackageName_INCLUDE_DIR:使用包所需要包含的头文件目录,只能在单个库的使用,使用者需要将该路径加入到搜索路径中。
.cmake 文件编写步骤说明
接下来我们来写一个 .cmake 文件,假设我们的包名为 mymath,该包提供一个 libmymath.a 的库,其中包含一个 add 接口,简单计算两个整数的和并打印出结果。在编写 Findmymath.cmake 之前,我们先来看下 .cmake 文件的格式。
- 文件开头是 license 信息
1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
- 接着是一个
CMake支持的多行注释(单行注释以#开头;多行注释是指:以#[开头,紧接着跟 0 个或多个=,之后是[,接下来就是注释内容,注释可以跨越多行,然后以]、0 个或多个=、]组成结束,开头的=个数要和结尾的=个数相等),.cmake要求注释以.rst开头:
1 | #[=======================================================================[.rst: |
-
接下来在注释中声明
find_package的标准变量及相关说明- 首先是包的名字,分为两行,第一行是包名字,第二行是包名字下方的下划线
---,与包名字长度相等(这里以 python 的FindPython3.cmake进行说明)。
1
2FindPython3
------------ 接着是对包的一个简要描述,这个没有特殊要求
- 接下来分为几个部分,主要是对几类变量的声明(导入变量、结果变量、缓存变量),格式都是一致的:首先是变量类型的说明,并在其下方以等长
^^^标识,接着是一段对变量内容的简要描述,再接下来就是我们要定义的标准变量了。变量以``标准变量``的格式定义,每个变量下可以对变量做一个简短的说明(这里以 python 的FindPython3.cmake进行说明)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Imported Targets
^^^^^^^^^^^^^^^^
This module defines the following :ref:`Imported Targets <Imported Targets>`:
.. versionchanged:: 3.14
:ref:`Imported Targets <Imported Targets>` are only created when
:prop_gbl:`CMAKE_ROLE` is ``PROJECT``.
``Python3::Interpreter``
Python 3 interpreter. Target defined if component ``Interpreter`` is found.
``Python3::Compiler``
Python 3 compiler. Target defined if component ``Compiler`` is found.
``Python3::Module``
.. versionadded:: 3.15 - 首先是包的名字,分为两行,第一行是包名字,第二行是包名字下方的下划线
-
注释部分到此结束,接下来是对库进行真正查找,并把注释部分申明的变量进行赋值的过程。
- 先尝试使用
pkg-config来找到真正的库,pkg-config是系统提供的命令用于找系统中是否存在相关的库,在CMake中使用如下两条,CMake会从<PackageName>.pc文件中读取对应的变量(这里以我们自己的mymath库进行说明)。
1
2find_package(PkgConfig)
pkg_check_modules(PC_mymath QUIET mymath)- 如果能找到库,那么变量
PC_mymath_FOUND存在,并且可以得到mymath相关的头文件和库目录,并且是存储在以PC_mymath_XXX开头的变量中,例如
C_mymath_INCLUDE_DIRS(头文件目录)、PC_mymath_LIBRARY_DIRS(库文件目录)、PC_mymath_LIBRARIES(库名称)等等(具体有哪些变量可以参考man pkg-config或者在 CMakeCache.txt 中过滤 PC_mymath 查看)。 - 先尝试使用
-
上一步利用了
pkg-config获得的变量还不是最终要给find_package返回的变量,我们要对返回的变量做正确的赋值,并最终调用include(FindPackageHandleStandardArgs)和find_package_handle_standard_args将变量返回给find_package调用处。
1 | include(FindPackageHandleStandardArgs) |
.cmake 文件编写
假定我们自己的库 mymath 已经写好了 .pc 文件(参考 pkg-config用法详解),并能够通过 pkg-config 方式找到它(可以通过 pkg-onfig --list-all 查看到 mymath 库)。
接下来我们来编写库 mymath 的 Findmymath.cmake 文件,参照前面对 .cmake 文件说明,内容如下,示例至提供了库目录、库文件、头文件等少量变量信息:
1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
测试代码
最后我们编写一个 test.cpp 文件来测试下是否能找到 mymath 库。
1 |
|
此外,还需要在 test.cpp 的同级目录下编写一个 CMakeLists.txt 来使用 find_package 来找到并使用 mymath 库。
1 | cmake_minimum_required(VERSION 3.22.1) |
在 build 目录下执行 cmake .. 命令,此处为了演示,直接指定 CMAKE_MODULE_PATH 的值为当前的 .cmake 所在路径,这样 find_package 命令会直接找到我们编写的 .cmake 文件并读取其中的内容,编译并运行最终程序(只摘取了我们关注的显示信息):
1 | ~/Code/cmake/myproj/build ❯ pwd |
配置模式(Config mode)
该模式下,CMake 会搜索 <lowercasePackageName>-config.cmake 文件或 <PackageName>Config.cmake 文件。如果 find_package 命令中指定了具体的版本,也会搜索 <lowercasePackageName>-config-version.cmake 或 <PackageName>ConfigVersion.cmake 文件,因此配置模式下通常会提供配置文件和版本文件(注意形式上要保持一致),并且作为包的一部分一起提供给使用者。
同样的,当 find_package 调用返回时,一系列跟包相关的变量也会提供给调用者。例如 <PackageName>_FOUND 标识包是否找到、<PackageName>_DIR 变量用于指示包配置文件所在的位置。实际上,返回的变量并没有特别的限制,但是还是建议遵循模块模式的标准变量名称的命名规则。
该模式下对 .cmake 文件的搜索路径的顺序比较复杂,具体见本文的配置模式的查找细节说明小节。
配置模式查找 Python 库
由于 Python 库本身未提供 python3Config.cmake,我们简单的编写一个。但是在这之前,需要先安装一下 Python 的头文件:
1 | sudo apt install python3-dev |
创建 python3Config.cmake ,内容如下:
1 | # python3Config.cmake |
我们来新建一个测试文件 test.cpp:
1 | // test.cpp |
同级目录下新建 CMakeLists.txt 内容如下:
1 | # CMakeLists.txt |
配置模式查找自己编写的 mymath 库
一个配置文件方式提供的包由包配置文件(必须包含,名为 <lowercasePackageName>-config.cmake 文件或 <PackageName>Config.cmake)和包版本文件(可选,名为 <lowercasePackageName>-config-version.cmake 或 <PackageName>ConfigVersion.cmake)组成。配置文件和版本文件的命名要配对出现,也就是:
1 | <lowercasePackageName>-config.cmake |
或者是:
1 | <lowercasePackageName>Config.cmake |
仍然是以我们自己编写的 mymath 库为例,假设 mymath 库提供了如下的文件:
1 | ├── CMakeLists.txt |
配置文件 mymathConfig.cmake
mymathConfig.cmake 文件有两种方式可以生成:
-
在 CMake 中
include(CMakePackageConfigHelpers),用CMakePackageConfigHelpers提供的configure_package_config_file()来生成。 -
直接通过
set设置对应的变量,本文采用这种方式提供,如下:
1 | # mymathConfig.cmake |
可以参考 配置模式的查找细节说明 小节来查看 .cmake 文件的搜索路径,我们的例子将在 find_package 中通过 PATHS 选项来指定。
版本文件 mymathConfigVersion.cmake
find_package 找到一个配置文件后,会尝试去查找版本文件。版本文件的主要作用是用来验证包的版本是否与 find_package 命令中指定的版本信息匹配。如果匹配的话,就会使用配置文件中的内容,否则会忽略配置文件中的内容。
和配置文件一样,版本文件也有两种方式生成:
- 在 CMake 中
include(CMakePackageConfigHelpers),用CMakePackageConfigHelpers提供的write_basic_package_version_file()来生成。我们的例子采用自动生成的方式,在mymath库的CMakeLists.txt中添加:
1 | include(CMakePackageConfigHelpers) |
- 自己手动写版本文件的校验规则。
生成自己的库
mymath 库的 CMakeLists.txt 内容如下:
1 | cmake_minimum_required(VERSION 3.18) |
在 build 目录中执行 cmake .. 和 make 生成 libmymath.a 库。
测试代码
最后我们编写一个 test.cpp 文件来测试下是否能找到 mymath 库。
1 |
|
1 | # 顶层的CMakeLists.txt:/XXX/CMakeLists.txt |
测试结果如下:
1 | ~/Code/cmake/myproj/build ❯ pwd |
配置模式的查找细节说明
这一部分可在官方文档上找到 find_package——config-mode-search-procedure。
配置模式的查找目录
CMake 会从如下从几个目录中取搜索配置文件,下面列出了将会搜索的目录,每一个目录后面通过字母来标记不同的操作系统(W 表示 Windows,U 表示 UNIX,A 表示 Apple),目录中的 <prefix> 是目录的前缀,将在下一节介绍是怎么生成的。
1 | <prefix>/ (W) |
CMake 3.25 版本里还加入了 <prefix>/<name>*/(cmake|CMake)/<name>*/ (W)
在支持 macOS 的 FRAMEWORK 和 BUNDLE 系统中,会搜索如下框架和应用程序包目录是否包含配置文件:
1 | <prefix>/<name>.framework/Resources/ (A) |
上面列举的目录中,<name> 是大小写不敏感的,并且会跟 <PackageName> 或者由 NAMES 选项指定的名字进行匹配。
CMAKE_LIBRARY_ARCHITECTURE 变量指定的时候,也会搜索 lib/<arch> 相关的路径,会按照如下顺序搜索:
- 如果
FIND_LIBRARY_USE_LIB64_PATHS属性被设置为true,那么在 64 位系统上,带lib64的路径会被搜索 - 如果
FIND_LIBRARY_USE_LIB32_PATHS属性被设置为true,那么在 32 位系统上,带lib32的路径会被搜索 - 如果
FIND_LIBRARY_USE_LIBX32_PATHS属性被设置为true,那么在使用 X32 ABI 的平台上,带libx32的路径会被搜索 - lib路径总是会被搜索的
- 可以通过
PATH_SUFFIXES变量指定搜索路径的后缀,会在上述的每一个路径中都添加后缀路径进行查找。
配置模式查找目录的前缀 <prefix> 如何生成
如果指定了 NO_DEFAULT_PATH 选项的话,则启用所有 NO_* 选项,<prefix> 的构造规则如下:
- 查找名为
<PackageName>_ROOT的 CMake 变量和名为<PackageName>_ROOT的环境变量,<PackageName>是待查找的包名。包的根变量是通过栈维护的,因此如果在当前查找模块中调用find_package,来自父查找模块的根路径也会被搜索。当然可以通过NO_PACKAGE_ROOT_PATH选项或者将CMAKE_FIND_USE_PACKAGE_ROOT_PATH设置为FALSE来跳过。(备注,该查找过程是 CMake 3.12 版本新增) - 通过选项
-DVAR=value传递进来的路径,多个路径需要以分号隔开。包含CMAKE_PFEFIX_PATH、CMAKE_FRAMEWORK_PATH、CMAKE_APPBUNDLE_PATH三个变量;例如cmake -DCMAKE_PREFIX_PATH=/tmp/test。可以通过NO_CMAKE_PATH选项或将CMAKE_FIND_USE_CMAKE_PATH设置为FALSE来跳过 - 特定的 CMake 环境变量,包含:
<PackageName>_DIR、CMAKE_PREFIX_PATH、CMAKE_FRAMEWORK_PATH、CMAKE_APPBUNDLE_PATH,可以通过NO_CMAKE_ENVIRONMENT_PATH选项或将CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH设置为FALSE来跳过 HINTS选项指定的路径- 标准系统环境变量,例如
PATH,注意,/bin和/sbin会自动添加到PATH指定的目录中 - CMake 的 User Package Registry 中存储的路径
- 当前系统的平台路径,典型的就是软件安装目录,例如 Linux 下的
/usr/local,包含如下几个变量:
CMAKE_SYSTEM_PREFIX_PATH、CMAKE_SYSTEM_FRAMEWORK_PATH、CMAKE_SYSTEM_APPBUNDLE_PATH。 - CMake 的 System Package Registry 中存储的路径
PATHS选项指定的路径
CMAKE_FIND_ROOT_PATH变量用于指定搜索的根路径。
在 find_package 命令调用之前设置 CMAKE_FIND_PACKAGE_RESOLVE_SYMLINKS 为 TRUE,这样如果查找到的路径是一个符号链接,会将符号链接对应的真实路径存起来。
配置模式下的版本配置文件
当指定 version 参数,配置模式将仅会查找能兼容指定版本的包,如果指定了 EXACT,则只会查找精确匹配指定版本的包。CMake 本身不会对版本号做任何转换,而是通过查找到包的版本校验文件(包自身提供的)<PackageName>ConfigVersion.cmake (或 <PackageName>-config-version.cmake),调用版本配置文件做校验,版本配置文件可以通过 CMakePackageConfigHelpers 模块来辅助创建。可以参考之前的例子。
当 find_package 命令中指定 version 参数后,会把 version 参数分解出来,赋值到 PACKAGE_FIND_XXX 中,供版本配置文件校验版本号使用,具体赋值的变量如下:
PACKAGE_FIND_NAME:包名PACKAGE_FIND_VERSION:全版本字符串PACKAGE_FIND_VERSION_MAJOR:主版本,默认值为0PACKAGE_FIND_VERSION_MINOR:次版本,默认为0PACKAGE_FIND_VERSION_PATCH:补丁版本,默认为0PACKAGE_FIND_VERSION_TWEAK:小版本,默认为0
当指定的版本是一个范围时,上述变量会存放范围中较小的那个版本号,这个主要是为了保证对没有实现版本范围的兼容,此外,也会赋值如下变量:
当版本配置文件完成版本校验后,会设置如下 PACKAGE_VERSION_XXX 变量供 find_package 使用,具体的变量如下:
PACKAGE_VERSION:全版本字符串PACKAGE_VERSION_EXACT:版本是否精确匹配,True表示精确匹配PACKAGE_VERSION_COMPATIBLE:版本是否兼容,True表示兼容PACKAGE_VERSION_UNSUITABLE:版本是否合适,True表示不合适
上面的 PACKAGE_VERSION_XXX 几个变量仅用于 find_package 命令检查配置文件是否提供了一个可接受的版本,一旦 find_package 命令返回后,这些变量就失效了。如果版本校验通过,那么如下 <PackageName>_VERSION_XXX 变量会被设置,供 find_package 调用者使用:
<PackageName>_VERSION:包的全版本字符串<PackageName>_VERSION_MAJOR:主版本<PackageName>_VERSION_MINOR:次版本<PackageName>_VERSION_PATCH:补丁版本<PackageName>_VERSION_TWEAK:小版本<PackageName>_VERSION_COUNT:点分版本组成的数量,范围0~4
命令格式
find_package 命令有两种格式,基本命令格式和完整命令格式。
基本命令
1 | find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE] |
几个重要的参数介绍:
- PackageName:待查找包的名称。此外它还决定两种搜索模下的
.cmake文件名称:例如模块模式下的名称为Find<PackageName>.cmake,而配置模式下为<lowercasePackageName>-config.cmake/<lowercasePackageName>-config-version.cmake - MODULE:该选项指定
find_package命令只使用模块模式搜索方式查找。未指定该选项时,find_package会优先使用模块模式搜索,仍未找到包时,会切换成配置模式搜索。 - version:待查找包的版本号要求,版本号为点分格式,由四个部分组成,每个部分都是一个数字,均为可选:
major[.minor[.patch[.tweak]]],例如1.1.1.1、1.0、等。同样也可以指定版本范围(CMake 3.19及之后才支持),格式为:versionMin...[<]versionMax,versionMin和versionMax均是major[.minor[.patch[.tweak]]]形式的版本号,默认情况下会包含这个指定区间两端的版本号,但如果指定了<,那么会排除掉versionMax,例如1.1.1.1...1.1.2.0、1.1.1.1...<1.1.2.0等。 - EXACT:该选项要求待查找包的版本必须与指定的版本精确匹配,因此如果指定的是一个版本范围,不能使用该参数。
- QUIET:禁止输出信息,正常情况当找到包时,CMake 会打印一些信息,指定该选项时会禁止掉这些打印。例外是当同时指定
QUIET时,如果找不到包,仍然会输出错误信息并终止执行过程。 - REQUIRED:当未找到满足条件的包(例如版本号不匹配,或指定组件未找到等),会终止 CMake 的执行过程,并输出一条错误信息。如果未指定该选项,即使未找到满足条件的包,CMake 的执行过程也会继续。
- COMPONENTS:指定要查找的组件。通常一个包可能包含多个组件(可以理解为多个库,例如把 C++ 的 std 看成一个包的概念,那么 vector 就是 std 下的其中一个组件),我们的工程可能会依赖包下的具体某个组件,因此可以通过这个选项来检测这些组件是否存在。通常的约定是,该选项后的组件应该都找到时才认为包找到,否则认为未找到满足条件的包。这个约束会依赖包的
.cmake来实现,通过find_package命令传入的COMPONENTS 可以通过<PackName>_FIND_COMPONENTS这个变量来获得。举个简单的例子:
1 | # mymathConfig.cmake,假定它位于./mymath/mymath目录下 |
1 | # 顶层目录的CMakeLists.txt |
- OPTIONAL_COMPONENTS:与 COMPONENTS 的区别是,不强制要求这些组件必须存在。不影响 CMake 的执行。
完整命令
1 | find_package(<PackageName> [version] [EXACT] [QUIET] |
这里介绍一下与基本命令有差异的地方:
- CONFIG|NO_MODULE:这两个选项二选一即可,表示强制
find_package命令使用配置模式搜索,忽略模块模式搜索。 - NAMES:默认情况下
find_package命令会查找名为<PackageName>的包。如果 NAMES 选项后指定了名称,则会使用这些名字来查找包而忽略<PackageName>参数。 - PATHS/HINTS:配置模式下指定
.cmake文件的搜索路径。 - NO_XXX_PATH:配置模式下忽略指定的路径,具体的含义可以参考4.1.2节。
命令返回的结果
<PackageName>_FOUND 变量用来表示包是否找到,True 表示包找到了,False 表示未找到满足条件的包。如果包被找到,那么还会提供其他与这个包相关的变量供调用者使用,例如包的头文件、库文件等。这些变量都是以<PackageName>_ 开头的,具体的命名格式请参考 Cmake 中 find_package 命令的搜索模式之模块模式(Module mode)的 四、对标准变量名称的更多说明章节。
搜索模式和命令之间的关系
搜索模式有两种:模块模式和配置模式。命令有两种形式:基本命令和完整命令。他们之间的关系是:
基本命令: 首先使用模块模式,如果没找到包,则会切换到配置模式。可以通过将 CMAKE_FIND_PACKAGE_PREFER_CONFIG 变量设置为 true 来改变顺序,这样会优先使用配置模式,如果没找到再切换到模块模式。此外,基本命令可以通过 MODULE 选项来强制指定只使用模块模式,也可以指定 NO_MODULE|CONFIG 来表示只使用配置模式搜索。
完整命令:只支持配置模式搜索。

