CMake实践

深入理解CMake(3):find_package()的使用

2019-03-28  本文已影响0人  BetterCV

created in 2019-03-03 11:44:11
updated in 2019-3-28 14:31:21

依赖包的实际情况:找不到,不知道如何切换版本

前面两篇从cmake源码中命令行入口参数、Caffe源码顶层CMakeLists.txt进行了解读,整体有了一个印象。在此基础上,考虑实际中最常遇到的问题:基于CMake构建Caffe,如何确保每一个依赖被正确找到?尽管用了CMake用了find_package(),也看到了Caffe官方的CI构建脚本scripts/install-deps.sh,但是自己机器不是docker环境、如何切换多个版本的依赖包?

find_package()命令是用来查找依赖包的,理想情况下,一句find_package()把一整个依赖包的头文件包含路径、库路径、库名字、版本号等情况都获取到,后续只管用就好了。但实际中往往CMake失败就是出在find_package()的失败上(这里不考虑后续make/nmake/msbuild以及编译器、链接器直接执行时的编译、链接出错,只讨论cmake根据CMakeLists.txt执行时候的情况),例如:

上面列出的opencv和protobuf是重灾区,还有没有列出来的比如boost版本问题等。解决起来也不难:

find_package()原理解读

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

区分Module模式和Config模式

Module模式也就是基础用法(Basic Signature,这里Signature表示“用法”,而不是“签名”),Config模式也就是高级用法(Full Signature)。

The CONFIG option, the synonymous NO_MODULE option, or the use of options not specified in the basic signature all enforce pure Config mode. In pure Config mode, the command skips Module mode search and proceeds at once with Config mode search.

也就是说,只有这3种情况下才是Config模式:

换句话说,只要我不指定"CONFIG",不指定“NO_MODULE",也不使用"full signature"中的关键字,那我就是在Module模式。排查find_package()的第一步,应当判断它是Module模式还是Config模式

find-package.jpg image.png

Module模式下find_package()的用法

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]

         [REQUIRED] [[COMPONENTS] [components...]]
         [OPTIONAL_COMPONENTS components...]
         [NO_POLICY_SCOPE])

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

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

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

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

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

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

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

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

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

Config模式下find_package()的用法

find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[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。查找顺序为:

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

project(fk_cmk)

set(OpenCV_ROOT "F:/zhangzhuo/lib/opencv_249/build")

set(OpenCV_DIR "F:/zhangzhuo/lib/opencv_300/build")

find_package(OpenCV QUIET
    NO_MODULE
    NO_DEFAULT_PATH
    NO_CMAKE_PATH
    NO_CMAKE_ENVIRONMENT_PATH
    NO_SYSTEM_ENVIRONMENT_PATH
    NO_CMAKE_PACKAGE_REGISTRY
    NO_CMAKE_BUILDS_PATH
    NO_CMAKE_SYSTEM_PATH
    NO_CMAKE_SYSTEM_PACKAGE_REGISTRY
)

message(STATUS "OpenCV library status:")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

实际上会找到opencv300,也就是OpenCV_DIR这一cmake变量的值最先起作用。

  1. cmake特定的缓存变量:

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

  1. cmake特定的环境变量

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

  1. HINT字段指定的路径

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

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

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

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

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

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

再次总结思路:

上一篇 下一篇

猜你喜欢

热点阅读