Cmake命令之find_package介绍

一、搜索模式

  find_package用于查找包(通常是使用三方库),并返回关于包的细节(使用包所依赖的头文件、库文件、编译选项、链接选项等)
  与find_libaray直接在指定搜索目录下搜索库不同,find_package命令可以获取更多的信息,那么它的搜索方式也是与find_libaray不一样,它有两种不同的搜索方式,因此在介绍这个命令的细节之前,先简单介绍一下find_package命令的两种搜索模式:模块模式Module mode)和配置模式Config mode)。

1.1 模块模式(Module mode

  在该模式下,Cmake会搜索一个名为Find.cmake的文件,其中为待搜索包的名称。
  搜索路径的顺序依次是:

  • 从变量CMAKE_MODULE_PATH指定的路径中进行查找
  • Cmake安装路径中查找。Cmake会在其安装路径下提供很多.cmake文件,例如/XXX/cmake/Modules/目录下(不同的系统安装目录可能不一致)

  如果找到文件Find.cmakeCmake会读取并处理该文件,简而言之,它负责检查一些条件(如版本号是否满足等)是否满足,并在找到包后,返回给调用者一些变量,用以获取包的详细信息。
  一般来说,Find.cmake文件不是随包本身一起提供的,更多的是外部针对已有包的重新包装,例如操作系统、Cmake程序、甚至是调用find_package命令的工程针对已有的包提供针对该包的.cmake文件。

1.2 配置模式(Config mode

  该模式下,CMake会搜索-config.cmake文件或Config.cmake文件。如果find_package命令中指定了具体的版本,也会搜索-config-version.cmakeConfigVersion.cmake文件,因此配置模式下通常会提供配置文件和版本文件(注意形式上要保持一致),并且作为包的一部分一起提供给使用者。

  该模式下对.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.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可以通过_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 命令返回的结果

  _FOUND变量用来表示包是否找到,True表示包找到了,False表示未找到满足条件的包。如果包被找到,那么还会提供其他与这个包相关的变量供调用者使用,例如包的头文件、库文件等。这些变量都是以_开头的,具体的命名格式请参考Cmake中find_package命令的搜索模式之模块模式(Module mode)的四、对标准变量名称的更多说明章节。

2.4 搜索模式和命令之间的关系

  搜索模式有两种:模块模式和配置模式。命令有两种形式:基本命令和完整命令。他们之间的关系是:

  • 基本命令: 首先使用模块模式,如果没找到包,则会切换到配置模式。可以通过将CMAKE_FIND_PACKAGE_PREFER_CONFIG变量设置为true来改变顺序,这样会优先使用配置模式,如果没找到再切换到模块模式。此外,基本命令可以通过MODULE选项来强制指定只使用模块模式,也可以指定NO_MODULECONFIG来表示只使用配置模式搜索。
  • 完整命令:只支持配置模式搜索。

三、使用示例

  我们将以两个例子分别展示两种搜索模式。本例中会利用我自己系统(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表示WindowsU表示UNIXA表示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)

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

/.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_*开头的命令都会使能,的查找顺序依次如下:

  • 查找名为_ROOTCMake变量和名为_ROOT的环境变量,是待查找的包名。包的根变量是通过栈维护的,因此如果在当前查找模块中调用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环境变量,包含:
    _DIRCMAKE_PREFIX_PATHCMAKE_FRAMEWORK_PATHCMAKE_APPBUNDLE_PATH,可以通过NO_CMAKE_ENVIRONMENT_PATH选项或将CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH设置为FALSE来跳过
  • HINTS指定的路径
  • 标准系统环境变量,例如PATH,注意,/bin/sbin会自动添加到PATH指定的目录中
  • CMakeUser Package Registry中存储的路径
  • 当前系统的平台路径,典型的就是软件安装目录,例如Linux下的/usr/local,包含如下几个变量:
    CMAKE_SYSTEM_PREFIX_PATHCMAKE_SYSTEM_FRAMEWORK_PATH
    CMAKE_SYSTEM_APPBUNDLE_PATH
  • CMakeSystem Package Registry中存储的路径
  • PATHS指定的路径

  CMAKE_FIND_ROOT_PATH用于指定搜索的根路径。

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

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

  当指定version参数,配置模式将仅会查找能兼容指定版本的包,如果指定了EXACT,则只会查找精确匹配指定版本的包。CMake本身不会对版本号做任何转换,而是通过查找到包的版本校验文件(包自身提供的)ConfigVersion.cmake(或-config-version.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:表示是否包含高版本,当前支持INCLUDEEXCLUDE
  • 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命令返回后,这些变量就失效了。如果版本校验通过,那么如下_VERSION_XXX变量会被设置,供find_package调用者使用:

  • _VERSION:包的全版本字符串
  • _VERSION_MAJOR:主版本
  • _VERSION_MINOR:此版本
  • _VERSION_PATCH:补丁版本
  • _VERSION_TWEAK:小版本
  • _VERSION_COUNT:点分版本组成的数量,范围0~4

附录:参考文档

  1. https://cmake.org/cmake/help/latest/command/find_package.html#find-package

你可能感兴趣的:(Cmake命令之find_package介绍)