cmake find_package的基本原理与详细示例(一)

一、find_package可以解决的问题

当构建一个依赖第三库或外部库的project时(即:project需要链接第三方库或外部库),我们需要知道以下信息:

去哪儿找第三 方库的头文件 .h 对比GCC的 -I 参数
去哪儿找第三方库的链接文件 (.so/.dll/.lib/.dylib/…) 对比GCC的 -L 参数
链接的第三方库的文件的名字 对比GCC的 -l 参数

知道上面的信息后,就可以在CMakeLists.txt中方便的包含第三方库的头文件、访问的链接第三方库的库文件(.a、.so)了;

二、为什么使用find_package

          第三方库的安装路径,在不同的机器上可能不同,所以在CMakeLists.txt中指定包含路径、链接路径和库,不太现实,除非你指定工程所需要的依赖库安装在固定的目录(貌似不太现实),否则在你的机器上构建成功了,在别人的机器上可能构建失败。或修改了第三方库的安装路径或版本升级后,在自己的机器上可能也无法构建成功。

          使用cmake的find_package可解决上面的问题。

         举个栗子:

              比如说,我们需要一个第三方库 curl,那么我们的 CMakeLists.txt 需要指定头文件目录,和库文件,类似:

                 include_directiories(/usr/include/curl)
                 target_link_libraries(myprogram path/curl.so)
               如果借助于cmake提供的finder会怎么样呢?使用cmake的Modules目录下的FindCURL.cmake,相应的CMakeList.txt 文件:
                 find_package(CURL REQUIRED)
                 include_directories(${CURL_INCLUDE_DIR})
                 target_link_libraries(curltest ${CURL_LIBRARY})

                  #使用find_package,不用关心curl具体安状在什么位置,find_package会替我们解决,那么是如何智能查找的呢?

三、find_package的原理

           find_package首先会在模块路径中寻找Findxxxx.cmake,这是查找库的典型方式。具体查找路径依次为CMake变量${CMAKE_MODULE_PATH}中的所有目录,如果没有,再查找它自己的模块目录/share/cmake-x.y/Modules/($CMAKE_ROOT的具体值可以通过CMake中message命令输出)。如果找到了xxxx模块,那么Findxxxx.cmake一般会设置以下变量供CMakeLists.txt使用:

          xxxx_FOUND    #为true

          xxxx_INCLUDE_DIRS   #include路径

          xxxx_LIBRARY_DIRS    #library路径

          xxxx_LIBRARIES           #library的名字

          xxxx_yyyy_VERSION    #具体详见Findxxxx.cmake

          为了能支持各种常见的库和包,CMake自带了很多Findxxxx模块。可以通过命令 cmake –help-module-list (输入cmake –help,然后双击Tab会有命令提示)得到你的CMake支持的模块的列表,也可以直接查看模块路径: ls /usr/share/cmake/Modules/

四、基本语法和模式

        根据cmake官方文档可以知道,find_package()有Module模式(基本用法,basic signature)和Config模式(full signature,完全用法),其中Module模式是基础,Config模式则更复杂高级些。

        Module模式基本语法:

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
             [REQUIRED] [[COMPONENTS] [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])

        Config模式基本语法:                                              

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

    更详细的请参见:https://cmake.org/cmake/help/v3.18/command/find_package.html#find-package

    只有以下3种情况下才是Config模式:

                find_package()中指定CONFIG关键字
                find_package()中指定NO_MODULE关键字
                 find_package()中使用了不在"basic signature"(也就是Module模式下所有支持的配置)关键字
     只要不指定"CONFIG",不指定“NO_MODULE",也不使用"full signature"中的关键字,那我就是在Module模式。排查find_package()的第一步,应当判断它是Module模式还是Config模式。

五、基本用法

    1、Module模式下find_package()的用法         

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
             [REQUIRED] [[COMPONENTS] [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])

Module模式下,相比于Config模式,可选配置参数少一些,并且如果按用户指定的配置却找不到包,就会自动进入Config模式(如上图所示)。

关键字解释

version和EXACT: 都是可选的,version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。

QUIET 可选字段,表示如果查找失败,不会在屏幕进行输出(但是如果指定了REQUIRED字段,则QUIET无效,仍然会输出查找失败提示语)。

MODULE 可选字段。前面提到说“如果Module模式查找失败则回退到Config模式进行查找”,但是假如设定了MODULE选项,那么就只在Module模式查找,如果Module模式下查找失败并不回落到Config模式查找。

REQUIRED可选字段。表示一定要找到包,找不到的话就立即停掉整个cmake。而如果不指定REQUIRED则cmake会继续执行。

COMPONENTS,components:可选字段,表示查找的包中必须要找到的组件(components),如果有任何一个找不到就算失败,类似于REQUIRED,导致cmake停止执行。

OPTIONAL_COMPONENTS和components:可选的模块,找不到也不会让cmake停止执行。

Module模式查找顺序
Module模式下是要查找到名为Find<PackageName>.cmake的文件。

先在CMAKE_MODULE_PATH变量对应的路径中查找。如果路径为空,或者路径中查找失败,则在cmake module directory(cmake安装时的Modules目录,比如/usr/local/share/cmake/Modules)查找。

  2、Config模式下find_package()的用法

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模式下的查找顺序

比Module模式下要多得多。而且,新版本的CMake比老版本的有更多的查找顺序(新增的在最优先的查找顺序)。它要找的文件名字也不一样,Config模式要找<PackageName>Config.cmake或<lower-case-package-name>-config.cmake。查找顺序为:

名为<PackageName>_ROOT的cmake变量或环境变量。CMake3.12新增。设定CMP0074 Policy来关闭。
注意:如果定义了<PackageName>_DIR cmake变量,那么<PackageName>_ROOT 不起作用。

cmake特定的缓存变量:

CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通过设定NO_CMAKE_PATH来关闭这一查找顺序

cmake特定的环境变量

<PackageName>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通过NO_CMAKE_ENVIRONMENT_PATH来跳过。

HINT字段指定的路径

搜索标准的系统环境变量PATH。
其中如果是以/bin或者/sbin结尾的,会自动转化为其父目录。
通过指定NO_SYSTEM_ENVIRONMENT_PATH来跳过。

存储在cmake的"User Package Registry"(用户包注册表)中的路径。
通过设定NO_CMAKE_PACKAGE_REGISTRY,或者:
设定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY为true,
来避开。

设定为当前系统定义的cmake变量:

CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
通过设定NO_CMAKE_SYSTEM_PATH来跳过。

在cmake的"System Package Registry"(系统包注册表)中查找。
通过设定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY跳过。
或者通过设定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY为true。

从PATHS字段指定的路径中查找。

猜你喜欢

转载自blog.csdn.net/lianshaohua/article/details/108402470