本文是对简书 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
project(python LANGUAGES NONE)

find_package(Python3 COMPONENTS Interpreter)
execute_process(
COMMAND
${Python3_EXECUTABLE} "-c" "print('Hello, world!')"
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)

message(STATUS "RESULT_VARIABLE is ${_status}")
message(STATUS "OUTPUT_VARIABLE is ${_hello_world}")

在终端执行 cmake 命令后,输出如下:

1
2
3
4
5
6
~/Code/cmake/python/build ❯ cmake ..
-- RESULT_VARIABLE is 0
-- OUTPUT_VARIABLE is Hello, world!
-- Configuring done
-- Generating done
-- Build files have been written to: /home/lushuangning/Code/cmake/python/build

模块模式查找自己编写的 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
2
# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
  • 接着是一个 CMake 支持的多行注释(单行注释以 # 开头;多行注释是指:以 #[ 开头,紧接着跟 0 个或多个 =,之后是 [,接下来就是注释内容,注释可以跨越多行,然后以 ]、0 个或多个 =] 组成结束,开头的 = 个数要和结尾的 = 个数相等),.cmake 要求注释以 .rst 开头:
1
2
3
4
5
#[=======================================================================[.rst:
...
中间的内容都是注释
...
]=======================================================================]
  • 接下来在注释中声明 find_package 的标准变量及相关说明

    • 首先是包的名字,分为两行,第一行是包名字,第二行是包名字下方的下划线 --- ,与包名字长度相等(这里以 python 的 FindPython3.cmake 进行说明)。
    1
    2
    FindPython3
    -----------
    • 接着是对包的一个简要描述,这个没有特殊要求
    • 接下来分为几个部分,主要是对几类变量的声明(导入变量、结果变量、缓存变量),格式都是一致的:首先是变量类型的说明,并在其下方以等长 ^^^ 标识,接着是一段对变量内容的简要描述,再接下来就是我们要定义的标准变量了。变量以 ``标准变量`` 的格式定义,每个变量下可以对变量做一个简短的说明(这里以 python 的 FindPython3.cmake 进行说明)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Imported 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
    2
    find_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
2
3
4
5
6
7
8
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(mymath
FOUND_VAR mymath_FOUND
REQUIRED_VARS
mymath_LIBRARY
mymath_INCLUDE_DIR
VERSION_VAR mymath_VERSION
)

.cmake 文件编写

假定我们自己的库 mymath 已经写好了 .pc 文件(参考 pkg-config用法详解),并能够通过 pkg-config 方式找到它(可以通过 pkg-onfig --list-all 查看到 mymath 库)。

接下来我们来编写库 mymathFindmymath.cmake 文件,参照前面对 .cmake 文件说明,内容如下,示例至提供了库目录、库文件、头文件等少量变量信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

#[===============================================[.rst:
Findmymath
----------

如果能找到库,那么变量 PC_mymath_FOUND 存在,
并且可以得到 mymath 相关的头文件和库目录,存储在以 PC_mymath_XXX 开头的变量中,例如:
PC_mymath_INCLUDE_DIRS(头文件目录)、PC_mymath_LIBRARY_DIRS(库文件目录)、
PC_mymath_LIBRARIES(库名称)等,可通过 man pkg-config 命令查看其它变量。

Imported Targets
^^^^^^^^^^^^^^^^

``mymath::mymath``
导出 mymath::mymath 可执行目标,我们的测试库并未提供,该处只是一个示意

变量的命名要遵循相关标准,

Result Variables
^^^^^^^^^^^^^^^^

``mymath_FOUND``
如果找到 mymath 库,该变量值为 True.

``mymath_VERSION``
mymath 库的版本

``mymath_INCLUDE_DIRS``
使用 mymath 库需要包含的头文件

``mymath_LIBRARY_DIRS``
使用 mymath 库需要用到的库文件

Cache Variables
^^^^^^^^^^^^^^^

``mymath_INCLUDE_DIR``
包含 mymath.h 头文件的目录

``mymath_LIBRARY_DIR``
mymath 库所在的目录
]===============================================]


find_package(PkgConfig)
pkg_check_modules(PC_mymath QUIET mymath)

# 定义一些缓存变量
find_path(mymath_INCLUDE_DIR
NAMES mymath.h
PATHS ${PC_mymath_INCLUDE_DIRS} # PC_mymath_INCLUDE_DIRS 变量由pkg-config提供,下同
PATH_SUFFIXES mymath)

find_library(mymath_LIBRARY_DIR
NAMES mymath
PATHS ${PC_mymath_LIBRARY_DIRS})

set(mymath_VERSION ${PC_mymath_VERSION})
set(mymath_INCLUDE_DIRS "/home/lushuangning/Code/cmake/mymath/include")
set(mymath_LIBRARY_DIRS "/home/lushuangning/Code/cmake/mymath/lib")

# 将变量导出给调用者使用
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(mymath
FOUND_VAR mymath_FOUND
REQUIRED_VARS
mymath_INCLUDE_DIR
mymath_LIBRARY_DIR
VERSION_VAR mymath_VERSION
)

点击下载 mymath 库源码

测试代码

最后我们编写一个 test.cpp 文件来测试下是否能找到 mymath 库。

1
2
3
4
5
6
7
# test.cpp
#include "mymath.h"

int main(int argc, char** argv){
mymath::add(1, 2);
return 0;
}

此外,还需要在 test.cpp 的同级目录下编写一个 CMakeLists.txt 来使用 find_package 来找到并使用 mymath 库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cmake_minimum_required(VERSION 3.22.1)

project(myproj)

message("Find path: ${CMAKE_MODULE_PATH}")

find_package(mymath)
if(mymath_FOUND)
add_executable(test test.cpp)
include_directories(${mymath_INCLUDE_DIR})
target_link_libraries(test ${mymath_LIBRARY_DIR})
set_target_properties(test PROPERTIES
INTERFACE_COMPILE_OPTIONS "${PC_math_CFLAGS_OTHER}")
endif()

点击下载测试文件代码

build 目录下执行 cmake .. 命令,此处为了演示,直接指定 CMAKE_MODULE_PATH 的值为当前的 .cmake 所在路径,这样 find_package 命令会直接找到我们编写的 .cmake 文件并读取其中的内容,编译并运行最终程序(只摘取了我们关注的显示信息):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
~/Code/cmake/myproj/build ❯ pwd
/home/lushuangning/Code/cmake/myproj/build
~/Code/cmake/myproj/build ❯ cmake .. -DCMAKE_MODULE_PATH=/home/lushuangning/Code/cmake/mymath
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Find path: /home/lushuangning/Code/cmake/mymath
-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29.2")
-- Found mymath: /home/lushuangning/Code/cmake/mymath/include (found version "1.0")
-- Configuring done
-- Generating done
-- Build files have been written to: /home/lushuangning/Code/cmake/myproj/build
~/Code/cmake/myproj/build ❯ make 4s
[ 50%] Building CXX object CMakeFiles/test.dir/test.cpp.o
[100%] Linking CXX executable test
[100%] Built target test
~/Code/cmake/myproj/build ❯ ./test
Add 1 and 2 is 3

配置模式(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
2
3
# python3Config.cmake
set(python3_INCLUDE_DIR "/usr/include/python3.10")
set(python3_LIBRARY "/usr/lib/python3")

我们来新建一个测试文件 test.cpp

1
2
3
4
5
6
7
8
9
10
11
// test.cpp
#include <Python.h>

int main(int argc, char** argv) {
Py_SetProgramName(argv[0]); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is',ctime(time()))\n");
Py_Finalize();
return 0;
}

同级目录下新建 CMakeLists.txt 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
# CMakeLists.txt
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
project(python LANGUAGES CXX)
find_package(python3 CONFIG
NAMES python3
PATHS /home/lushuangning/Code/cmake/python)
if(python3_FOUND)
message("Find python3 library: ${python3_INCLUDE_DIR}, ${python3_LIBRARY}")
include_directories(${python3_INCLUDE_DIR})
add_executable(test test.cpp)
target_link_libraries(test, ${python3_LIBRARY})
endif()

配置模式查找自己编写的 mymath 库

一个配置文件方式提供的包由包配置文件(必须包含,名为 <lowercasePackageName>-config.cmake 文件或 <PackageName>Config.cmake)和包版本文件(可选,名为 <lowercasePackageName>-config-version.cmake<PackageName>ConfigVersion.cmake)组成。配置文件和版本文件的命名要配对出现,也就是:

1
2
<lowercasePackageName>-config.cmake
<lowercasePackageName>-config-version.cmake

或者是:

1
2
<lowercasePackageName>Config.cmake
<lowercasePackageName>ConfigVersion.cmake

仍然是以我们自己编写的 mymath 库为例,假设 mymath 库提供了如下的文件:

1
2
3
4
5
6
7
8
9
10
├── CMakeLists.txt
├── build
├── include
│   └── mymath.h
├── lib
│   └── libmymath.a
├── mymathConfig.cmake
├── mymathConfigVersion.cmake
└── src
└── mymath.cpp

配置文件 mymathConfig.cmake

mymathConfig.cmake 文件有两种方式可以生成:

  • 在 CMake 中 include(CMakePackageConfigHelpers),用 CMakePackageConfigHelpers 提供的 configure_package_config_file()来生成。

  • 直接通过 set 设置对应的变量,本文采用这种方式提供,如下:

1
2
3
# mymathConfig.cmake
set(mymath_INCLUDE_DIR "/home/lushuangning/Code/cmake/mymath/include")
set(mymath_LIBRARY "/home/lushuangning/Code/cmake/mymath/lib/libmymath.a")

可以参考 配置模式的查找细节说明 小节来查看 .cmake 文件的搜索路径,我们的例子将在 find_package 中通过 PATHS 选项来指定。

版本文件 mymathConfigVersion.cmake

find_package 找到一个配置文件后,会尝试去查找版本文件。版本文件的主要作用是用来验证包的版本是否与 find_package 命令中指定的版本信息匹配。如果匹配的话,就会使用配置文件中的内容,否则会忽略配置文件中的内容。

和配置文件一样,版本文件也有两种方式生成:

  • 在 CMake 中 include(CMakePackageConfigHelpers),用 CMakePackageConfigHelpers 提供的 write_basic_package_version_file() 来生成。我们的例子采用自动生成的方式,在 mymath 库的 CMakeLists.txt 中添加:
1
2
3
4
5
6
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/mymathConfigVersion.cmake"
VERSION ${Inner_VERSION}
COMPATIBILITY AnyNewerVersion
)
  • 自己手动写版本文件的校验规则。

生成自己的库

mymath 库的 CMakeLists.txt 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.18)
project(mymath)

set(CMAKE_CXX_STANDARD 14)
set(Inner_VERSION 1.2.3.4)
add_library(mymath STATIC src/mymath.cpp)
set_property(TARGET mymath PROPERTY VERSION ${Inner_VERSION})

target_include_directories(mymath
PUBLIC
${PROJECT_SOURCE_DIR}/include
)

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/mymathConfigVersion.cmake"
VERSION ${Inner_VERSION}
COMPATIBILITY AnyNewerVersion
)

源码点击此处下载

build 目录中执行 cmake ..make 生成 libmymath.a 库。

测试代码

最后我们编写一个 test.cpp 文件来测试下是否能找到 mymath 库。

1
2
3
4
5
6
7
# test.cpp
#include "mymath.h"

int main(int argc, char** argv){
mymath::add(1, 2);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 顶层的CMakeLists.txt:/XXX/CMakeLists.txt
cmake_minimum_required(VERSION 3.18.0)
project(find_package_test)

find_package(mymath 1.2.3.2...1.2.4.0
CONFIG
PATHS /home/lushuangning/Code/cmake/mymath
)

if(mymath_FOUND)
message("Find mymath: ${mymath_INCLUDE_DIR}; ${mymath_LIBRARY}")
add_executable(test test.cpp)
include_directories(${mymath_INCLUDE_DIR})
target_link_libraries(test ${mymath_LIBRARY})
endif()

点击下载测试文件代码

测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
~/Code/cmake/myproj/build ❯ pwd
/home/lushuangning/Code/cmake/myproj/build
~/Code/cmake/myproj/build ❯ cmake ..
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
Find mymath: /home/lushuangning/Code/cmake/mymath/include; /home/lushuangning/Code/cmake/mymath/lib/libmymath.a
-- Configuring done
-- Generating done
-- Build files have been written to: /home/lushuangning/Code/cmake/myproj/build
~/Code/cmake/myproj/build ❯ make 7s
[ 50%] Building CXX object CMakeFiles/test.dir/test.cpp.o
[100%] Linking CXX executable test
[100%] Built target test
~/Code/cmake/myproj/build ❯ ./test
Add 1 and 2 is 3

配置模式的查找细节说明

这一部分可在官方文档上找到 find_package——config-mode-search-procedure

配置模式的查找目录

CMake 会从如下从几个目录中取搜索配置文件,下面列出了将会搜索的目录,每一个目录后面通过字母来标记不同的操作系统(W 表示 Windows,U 表示 UNIX,A 表示 Apple),目录中的 <prefix> 是目录的前缀,将在下一节介绍是怎么生成的。

1
2
3
4
5
6
7
8
9
10
<prefix>/                                                        (W)
<prefix>/(cmake|CMake)/ (W)
<prefix>/<name>*/ (W)
<prefix>/<name>*/(cmake|CMake)/ (W)
<prefix>/(lib/<arch>|lib*|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/ (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/ (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (W/U)

CMake 3.25 版本里还加入了 <prefix>/<name>*/(cmake|CMake)/<name>*/ (W)

在支持 macOS 的 FRAMEWORK 和 BUNDLE 系统中,会搜索如下框架和应用程序包目录是否包含配置文件:

1
2
3
4
5
6
<prefix>/<name>.framework/Resources/                              (A) 
<prefix>/<name>.framework/Resources/CMake/ (A)
<prefix>/<name>.framework/Versions/*/Resources/ (A)
<prefix>/<name>.framework/Versions/*/Resources/CMake/ (A)
<prefix>/<name>.app/Contents/Resources/ (A)
<prefix>/<name>.app/Contents/Resources/CMake/ (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_PATHCMAKE_FRAMEWORK_PATHCMAKE_APPBUNDLE_PATH 三个变量;例如 cmake -DCMAKE_PREFIX_PATH=/tmp/test。可以通过 NO_CMAKE_PATH 选项或将 CMAKE_FIND_USE_CMAKE_PATH 设置为 FALSE 来跳过
  • 特定的 CMake 环境变量,包含:
    <PackageName>_DIRCMAKE_PREFIX_PATHCMAKE_FRAMEWORK_PATHCMAKE_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_PATHCMAKE_SYSTEM_FRAMEWORK_PATHCMAKE_SYSTEM_APPBUNDLE_PATH
  • CMake 的 System Package Registry 中存储的路径
  • PATHS 选项指定的路径
      
    CMAKE_FIND_ROOT_PATH 变量用于指定搜索的根路径。

find_package 命令调用之前设置 CMAKE_FIND_PACKAGE_RESOLVE_SYMLINKSTRUE,这样如果查找到的路径是一个符号链接,会将符号链接对应的真实路径存起来。

配置模式下的版本配置文件

当指定 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:主版本,默认值为0
  • PACKAGE_FIND_VERSION_MINOR:次版本,默认为0
  • PACKAGE_FIND_VERSION_PATCH:补丁版本,默认为0
  • PACKAGE_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
2
3
4
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
  [REQUIRED] [[COMPONENTS] [components...]]
  [OPTIONAL_COMPONENTS components...]
  [NO_POLICY_SCOPE])

几个重要的参数介绍:

  • 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.11.0、等。同样也可以指定版本范围(CMake 3.19及之后才支持),格式为:versionMin...[<]versionMaxversionMinversionMax 均是 major[.minor[.patch[.tweak]]] 形式的版本号,默认情况下会包含这个指定区间两端的版本号,但如果指定了 <,那么会排除掉 versionMax,例如 1.1.1.1...1.1.2.01.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# mymathConfig.cmake,假定它位于./mymath/mymath目录下
# 作用就是校验COMPONENTS是否是test组件,只有当COMPONENTS为空或者为test组件时,包mymath才会被找到

message(${mymath_FIND_COMPONENTS}) #`find_package`命令的`COMPONENTS`传入的
if(${mymath_FIND_COMPONENTS} STREQUAL "")
message("Empty comps.")
set(mymath_INCLUDE_DIR "/XXX/mymath")
set(mymath_LIBRARY "/XXX/mymath/libmymath.a")
else()
foreach(comp ${mymath_FIND_COMPONENTS})
if (comp MATCHES "test")
message("Find comp test")
set(mymath_INCLUDE_DIR "/XXX/mymath")
set(mymath_LIBRARY "/XXX/mymath/libmymath.a")
endif()
endforeach()
endif()
1
2
3
4
5
6
7
8
9
10
11
12
13
# 顶层目录的CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)
project(find_package_mymath)
find_package(mymath
CONFIG
REQUIRED
COMPONENTS test
PATHS ./mymath/mymath
)

if(mymath_FOUND)
message("Find mymath: ${mymath_INCLUDE_DIR}; ${mymath_LIBRARY};")
endif()
  • OPTIONAL_COMPONENTS:与 COMPONENTS 的区别是,不强制要求这些组件必须存在。不影响 CMake 的执行。

完整命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
find_package(<PackageName> [version] [EXACT] [QUIET]
  [REQUIRED] [[COMPONENTS] [components...]]
  [OPTIONAL_COMPONENTS components...]
  [CONFIG|NO_MODULE]
  [NO_POLICY_SCOPE]
  [NAMES name1 [name2 ...]]
  [CONFIGS config1 [config2 ...]]
  [HINTS path1 [path2 ... ]]
  [PATHS path1 [path2 ... ]]
  [PATH_SUFFIXES suffix1 [suffix2 ...]]
  [NO_DEFAULT_PATH]
  [NO_PACKAGE_ROOT_PATH]
  [NO_CMAKE_PATH]
  [NO_CMAKE_ENVIRONMENT_PATH]
  [NO_SYSTEM_ENVIRONMENT_PATH]
  [NO_CMAKE_PACKAGE_REGISTRY]
  [NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
  [NO_CMAKE_SYSTEM_PATH]
  [NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
  [CMAKE_FIND_ROOT_PATH_BOTH |
  ONLY_CMAKE_FIND_ROOT_PATH |
  NO_CMAKE_FIND_ROOT_PATH])

这里介绍一下与基本命令有差异的地方:

  • 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 来表示只使用配置模式搜索。
完整命令:只支持配置模式搜索。