Cmake命令之find_package介绍

2022-03-29  本文已影响0人  Domibaba

一、搜索模式

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

1.1 模块模式(Module mode

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

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

1.2 配置模式(Config mode

  该模式下,CMake会搜索<lowercasePackageName>-config.cmake文件或<PackageName>Config.cmake文件。如果find_package命令中指定了具体的版本,也会搜索<lowercasePackageName>-config-version.cmake<PackageName>ConfigVersion.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(<PackageName> [version] [EXACT] [QUIET] [MODULE]
  [REQUIRED] [[COMPONENTS] [components...]]
  [OPTIONAL_COMPONENTS components...]
  [NO_POLICY_SCOPE])

  几个重要的参数介绍:

# 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;

2.2 完整命令

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])

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

2.3 命令返回的结果

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

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

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

三、使用示例

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

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),目录中的<prefix>是目录的前缀,将在4.1.2介绍是怎么生成的:

<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)

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

<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>相关的路径,会按照如下顺序搜索:

  可以通过PATH_SUFFIXES变量指定搜索路径的后缀,会在上述的每一个路径中都添加后缀路径进行查找。

4.1.2 配置模式查找目录的前缀<prefix>如何生成

  如果NO_DEFAULT_PATH选项指定了的话,那么所有以NO_*开头的命令都会使能,<prefix>的查找顺序依次如下:

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

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

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

  当指定version参数,配置模式将仅会查找能兼容指定版本的包,如果指定了EXACT,则只会查找精确匹配指定版本的包。CMake本身不会对版本号做任何转换,而是通过查找到包的版本校验文件(包自身提供的)<PackageName>ConfigVersion.cmake(或<PackageName>-config-version.cmake),调用版本配置文件做校验,版本配置文件可以通过CMakePackageConfigHelpers模块来辅助创建。可以参考Cmake中find_package命令的搜索模式之配置模式(Config mode)中的例子。

  当find_package命令中指定version参数后,会把version参数分解出来,赋值到PACKAGE_FIND_XXX中,供版本配置文件校验版本号使用,具体赋值的变量如下:

  当指定的版本是一个范围时,上述变量会存放范围中较小的那个版本号,这个主要是为了保证对没有实现版本范围的兼容,此外,也会赋值如下变量:

  当版本配置文件完成版本校验后,会设置如下PACKAGE_VERSION_XXX变量供find_package使用,具体的变量如下:

  上面的PACKAGE_VERSION_XXX几个变量仅用于find_package命令检查配置文件是否提供了一个可接受的版本,一旦find_package命令返回后,这些变量就失效了。如果版本校验通过,那么如下<PackageName>_VERSION_XXX变量会被设置,供find_package调用者使用:


附录:参考文档

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

猜你喜欢

热点阅读