一、搜索模式
find_package
用于查找包(通常是使用三方库),并返回关于包的细节(使用包所依赖的头文件、库文件、编译选项、链接选项等)
与find_libaray
直接在指定搜索目录下搜索库不同,find_package
命令可以获取更多的信息,那么它的搜索方式也是与find_libaray
不一样,它有两种不同的搜索方式,因此在介绍这个命令的细节之前,先简单介绍一下find_package
命令的两种搜索模式:模块模式(Module mode
)和配置模式(Config mode
)。
1.1 模块模式(Module mode
)
在该模式下,Cmake
会搜索一个名为Find
的文件,其中
为待搜索包的名称。
搜索路径的顺序依次是:
- 从变量
CMAKE_MODULE_PATH
指定的路径中进行查找 - 从
Cmake
安装路径中查找。Cmake
会在其安装路径下提供很多.cmake
文件,例如/XXX/cmake/Modules/
目录下(不同的系统安装目录可能不一致)
如果找到文件Find
,Cmake
会读取并处理该文件,简而言之,它负责检查一些条件(如版本号是否满足等)是否满足,并在找到包后,返回给调用者一些变量,用以获取包的详细信息。
一般来说,Find
文件不是随包本身一起提供的,更多的是外部针对已有包的重新包装,例如操作系统、Cmake
程序、甚至是调用find_package
命令的工程针对已有的包提供针对该包的.cmake
文件。
1.2 配置模式(Config mode
)
该模式下,CMake
会搜索
文件或
文件。如果find_package
命令中指定了具体的版本,也会搜索
或
文件,因此配置模式下通常会提供配置文件和版本文件(注意形式上要保持一致),并且作为包的一部分一起提供给使用者。
该模式下对.cmake
文件的搜索路径的顺序比较复杂,具体见本文的4.1
节。
.cmake
后缀文件的作用是什么?
find_package
的两种搜索模式都会按照一定规则从路径下搜索.cmake
后缀的文件,两种模式下的.cmake
文件作用都是为了给find_package
命令的调用方返回有关包的信息(头文件路径、库文件路径、编译连接选项、版本信息等等),对于两种模式的进一步说明可以参看Cmake中find_package命令的搜索模式之模块模式(Module mode)和Cmake中find_package命令的搜索模式之配置模式(Config mode).
二、命令格式
find_package
命令有两种格式,基本命令格式和完整命令格式。
2.1 基本命令
find_package
([version] [EXACT] [QUIET] [ MODULE
]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
几个重要的参数介绍:
-
PackageName
:待查找包的名称。此外它还决定两种搜索模下的.cmake
文件名称:例如模块模式下的名称为Find
,而配置模式下为.cmake
/-config.cmake
。-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
可以通过
这个变量来获得。举个简单的例子:_FIND_COMPONENTS
# 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()
# 顶层目录的CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)
project(find_package_test)
find_package(mymath
CONFIG
REQUIRED
COMPONENTS test
PATHS ./mymath/mymath
)
if(mymath_FOUND)
message("Find mymath: ${mymath_INCLUDE_DIR}; ${mymath_LIBRARY};")
endif()
# 执行cmake .
cmake .
# 输出为
test
Find comp test
Find mymath: /XXX/mymath; /XXX/mymath/libmymath.a;
-
OPTIONAL_COMPONENTS
:与COMPONENTS
的区别是,不强制要求这些组件必须存在。不影响CMake
的执行。
2.2 完整命令
find_package
([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
命令会查找名为
的包。如果NAMES
选项后指定了名称,则会使用这些名字来查找包而忽略
参数。 -
PATHS
/HINTS
:配置模式下指定.cmake
文件的搜索路径。 -
NO_XXX_PATH
:配置模式下忽略指定的路径,具体的含义可以参考4.1.2
节。
2.3 命令返回的结果
变量用来表示包是否找到,True
表示包找到了,False
表示未找到满足条件的包。如果包被找到,那么还会提供其他与这个包相关的变量供调用者使用,例如包的头文件、库文件等。这些变量都是以
开头的,具体的命名格式请参考Cmake中find_package命令的搜索模式之模块模式(Module mode)的四、对标准变量名称的更多说明
章节。
2.4 搜索模式和命令之间的关系
搜索模式有两种:模块模式和配置模式。命令有两种形式:基本命令和完整命令。他们之间的关系是:
-
基本命令
: 首先使用模块模式,如果没找到包,则会切换到配置模式。可以通过将CMAKE_FIND_PACKAGE_PREFER_CONFIG
变量设置为true
来改变顺序,这样会优先使用配置模式,如果没找到再切换到模块模式。此外,基本命令可以通过MODULE
选项来强制指定只使用模块模式,也可以指定NO_MODULE
或CONFIG
来表示只使用配置模式搜索。 -
完整命令
:只支持配置模式搜索。
三、使用示例
我们将以两个例子分别展示两种搜索模式。本例中会利用我自己系统(macOS
)已经安装的库LibLZMA
(如何编写自己的库并让find_package
两种模式能搜索到,请参考另外两篇文章看Cmake中find_package命令的搜索模式之模块模式(Module mode)和Cmake中find_package命令的搜索模式之配置模式(Config mode),尝试搜索这个库,并利用这个库提供的接口lzma_version_string
(在头文件lzma.h
中提供)来获取它的版本号,并打印出来,测试程序如下:
// test.cpp
// 打印lzma库的版本号
#include "lzma.h"
#include
int main(int argc, char** argv)
{
const char* version = lzma_version_string();
std::cout << "lzma version is: " << version << std::endl;
return 0;
}
3.1 模块模式查找LibLZMA
库
模块模式的CMakeLists.txt
内容如下:
# CMakeLists.txt
find_package(LibLZMA MODULE) # MODULE指定使用模块模式查找
if (LibLZMA_FOUND)
message("Find lzma library: ${LIBLZMA_INCLUDE_DIR}, ${LIBLZMA_LIBRARY}")
include_directories(${LIBLZMA_INCLUDE_DIR})
add_executable(test test.cpp)
target_link_libraries(test ${LIBLZMA_LIBRARY})
endif()
# 命令行执行
cmake .
make
./test
# 命令行输出(只展示跟例子相关的输出)
Find lzma library: /usr/local/include, /usr/local/lib/liblzma.dylib
lzma version is: 5.2.5
3.2 配置模式查找LibLZMA
库
由于lzma
库本身未提供lzmaConfig.cmake
,我们简单的编写一个,内容就是为find_package
提供lzma
库所在的头文件和库文件,并在find_package
中指定查找该.cmake
所在的路径:
# lzmaConfig.cmake
set(lzma_INCLUDE_DIR "/usr/local/include")
set(lzma_LIBRARY "/usr/local/lib/liblzma.dylib")
配置模式的CMakeLists.txt
内容如下:
find_package(lzma CONFIG
NAMES lzma
PATHS ./)
if (lzma_FOUND)
message("Find lzma library: ${lzma_INCLUDE_DIR}, ${lzma_LIBRARY}")
include_directories(${lzma_INCLUDE_DIR})
add_executable(test test.cpp)
target_link_libraries(test ${lzma_LIBRARY})
endif()
# 命令行执行
cmake .
make
./test
# 命令行输出(只展示跟例子相关的输出)
Find lzma library: /usr/local/include, /usr/local/lib/liblzma.dylib
lzma version is: 5.2.5
四、更多细节
4.1 配置模式(Config module
)查找过程:
提示:当使用配置模式时,不论是基本命令还是完整命令都会遵循该查找过程。
4.1.1 配置模式的查找目录
CMake
会从如下从几个目录中取搜索配置文件,下面列出了将会搜索的目录,每一个目录后面通过字母来标记不同的操作系统(W
表示Windows
,U
表示UNIX
,A
表示Apple
),目录中的
是目录的前缀,将在4.1.2
介绍是怎么生成的:
/ (W) /(cmake|CMake)/ (W) / */ (W) / */(cmake|CMake)/ (W) /(lib/ |lib*|share)/cmake/ */ (U) /(lib/ |lib*|share)/ */ (U) /(lib/ |lib*|share)/ */(cmake|CMake)/ (U) / */(lib/ |lib*|share)/cmake/ */ (W/U) / */(lib/ |lib*|share)/ */ (W/U) / */(lib/ |lib*|share)/ */(cmake|CMake)/ (W/U)
在支持macOS
的FRAMEWORK
和BUNDLE
系统中,会搜索如下框架和应用程序包目录是否包含配置文件:
/ .framework/Resources/ (A) / .framework/Resources/CMake/ (A) / .framework/Versions/*/Resources/ (A) / .framework/Versions/*/Resources/CMake/ (A) / .app/Contents/Resources/ (A) / .app/Contents/Resources/CMake/ (A)
上面列举的目录中,
是大小写不敏感的,并且会跟
或者NAMES
指定的名字进行匹配。
CMAKE_LIBRARY_ARCHITECTURE
变量指定的时候,也会搜索lib/
相关的路径,会按照如下顺序搜索:
如果
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
变量指定搜索路径的后缀,会在上述的每一个路径中都添加后缀路径进行查找。
4.1.2 配置模式查找目录的前缀
如何生成
如果NO_DEFAULT_PATH
选项指定了的话,那么所有以NO_*
开头的命令都会使能,
的查找顺序依次如下:
- 查找名为
的_ROOT CMake
变量和名为
的环境变量,_ROOT
是待查找的包名。包的根变量是通过栈维护的,因此如果在当前查找模块中调用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
环境变量,包含:
、_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
,这样如果查找到的路径是一个符号链接,会将符号链接对应的真实路径存起来。
4.2 配置模式下的版本配置文件
当指定version
参数,配置模式将仅会查找能兼容指定版本的包,如果指定了EXACT
,则只会查找精确匹配指定版本的包。CMake
本身不会对版本号做任何转换,而是通过查找到包的版本校验文件(包自身提供的)
(或
),调用版本配置文件做校验,版本配置文件可以通过CMakePackageConfigHelpers
模块来辅助创建。可以参考Cmake中find_package命令的搜索模式之配置模式(Config mode)中的例子。
当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_FIND_VERSION_COUNT
:版本组成部分的数量,范围为0~4
当指定的版本是一个范围时,上述变量会存放范围中较小的那个版本号,这个主要是为了保证对没有实现版本范围的兼容,此外,也会赋值如下变量:
-
PACKAGE_FIND_VERSION_RANGE
:全版本范围字符串 -
PACKAGE_FIND_VERSION_RANGE_MIN
:表示是否包含低版本,当前只支持INCLUDE
,也就是说必然会包含低版本 -
PACKAGE_FIND_VERSION_RANGE_MAX
:表示是否包含高版本,当前支持INCLUDE
和EXCLUDE
-
PACKAGE_FIND_VERSION_MIN
:低版本的全版本字符串 -
PACKAGE_FIND_VERSION_MIN_MAJOR
:低版本的主版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MIN_MINOR
:低版本的次版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MIN_PATCH
:低版本的补丁版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MIN_TWEAK
:低版本的小版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MIN_COUNT
:低版本组成部分的数量,范围为0~4
-
PACKAGE_FIND_VERSION_MAX
:高版本的全版本字符串 -
PACKAGE_FIND_VERSION_MAX_MAJOR
:高版本的主版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MAX_MINOR
:高版本的次版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MAX_PATCH
:高版本的补丁版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MAX_TWEAK
:高版本的小版本号,如果未指定则为0 -
PACKAGE_FIND_VERSION_MAX_COUNT
:高版本组成部分的数量,范围为0~4
当版本配置文件完成版本校验后,会设置如下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
命令返回后,这些变量就失效了。如果版本校验通过,那么如下
变量会被设置,供find_package
调用者使用:
-
:包的全版本字符串_VERSION -
:主版本_VERSION_MAJOR -
:此版本_VERSION_MINOR -
:补丁版本_VERSION_PATCH -
:小版本_VERSION_TWEAK -
:点分版本组成的数量,范围_VERSION_COUNT 0~4
附录:参考文档
- https://cmake.org/cmake/help/latest/command/find_package.html#find-package