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

  • 多个OpenCV版本的问题

    • apt或brew等系统包管理工具安装的opencv,和手动编译的OpenCV共存问题
    • 手动编译安装了多个版本的OpenCV问题,也许你同时需要opencv2和opencv3,甚至opencv4
  • 多个protobuf版本问题

    • protobuf的python包需要和proto C编译器protoc版本一致,否则带python layer的prototxt解析失败
    • 安装了TensorFlow时被迫安装的protobuf3,但是Caffe这边用的python2,python protobuf包的版本问题

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

  • 明确find_package()的N大查找顺序
  • 知道如何让find_package()找到非CMake构建安装的依赖包

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模式:

  • find_package()中指定CONFIG关键字
  • find_package()中指定NO_MODULE关键字
  • find_package()中使用了不在"basic signature"(也就是Module模式下所有支持的配置)关键字

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

Module模式下find_package()的用法

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

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.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模式要找Config.cmake或-config.cmake。查找顺序为:

  1. 名为_ROOT的cmake变量或环境变量。CMake3.12新增。设定CMP0074 Policy来关闭。

    注意:如果定义了_DIR cmake变量,那么_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字段指定的路径中查找。

再次总结思路:

    1. 判断find_package()实际执行的是module模式还是config模式
    • 1.1 find_package()这样的用法并不能看出是module模式还是config模式。要看CMAKE_MODULE_PATH或cmake安装路径下是否有Find.cmake脚本存在,并且这个脚本是否能正确的找到包。如果上述两个位置不存在Find.cmake,或者这个Find.cmake执行失败,则进入config模式。
    • 1.2 通过CONFIG、NO_MODULE、CONFIG模式特有字段,来设定为config模式
    1. 明确_DIR是config模式特有的缓存变量
    • 2.1可以在find_package()前设定_DIR,指向包含Config.cmake或-config.cmake的目录。

      _ROOT先设定,再设定_DIR,最后find_package();并且两个都能找到包,则_DIR起作用。
    • 2.2 也可在find_package()后使用例如打印。
    • 2.3 module模式下在find_package()前使用_DIR,并不能用来帮助find_package()找到包;并且在find_package()后,也并没有_DIR缓存变量自动存在。
    1. 明确_ROOT是cmake3.12起支持的变量

      _ROOT变量被find_package, find_library, find_path, find_program, find_file支持。因此,尽管从find_package()文档页看会以为_ROOT只被config模式支持而不被module模式支持,但是module模式下通过另外4个find命令会间接的使用到_ROOT,从而find_package命令的module模式间接的支持_ROOT变量。

      _ROOT设定后,find_package()的config模式会在_ROOT目录及其子目录下寻找cmake的config文件;而_DIR则很傻,不会在子目录中寻找。
    1. 检查路径是否拼写正确

      以上的3点是正确的,但有时候总发现幺蛾子,怀疑上面三点说的不对。这时候要检查路径是否拼写正确。
    • 4.1 路径是否拼写错误,比如少字母、字母写错、大小写拼错
    • 4.2 如果使用了环境变量来构成cmake变量,注意使用\(ENV{varName}而不是\)varName。

find_package()的使用的更多相关文章

  1. Cmake find_package()相关

    也就是find_package可以帮助直接找到库的头文件和库文件(.lib,dll .etc) References: http://blog.csdn.net/dbzhang800/article/ ...

  2. Cmake中的find_package功能

    find_package其实在windows下扮演的角色并不是很重要.在Unix下就非常重要了,find_package可以根据cmake内置的.cmake的脚本去找相应的库的模块,当然,内建了很多库 ...

  3. ubuontu16.04安装Opencv库引发的find_package()错误信息处理及其简单使用

    在安装完Opencv库之后,打算测试一下Opencv库是否成功安装.下面是用的例子对应的.cpp代码以及对应的CMakeLists.txt代码: .cpp文件: #include <stdio. ...

  4. cmake find_package 命令

    1. find_package(<Name>)命令首先会在模块路径中寻找 Find<name>.cmake,这是查找库的一个典型方式. 具体查找路径依次为CMake:  变量$ ...

  5. Cmake find_package 需要指定具体的so

    需要使用cmake的find_package将boost库添加到项目中,通过cmake --help-module FindBoost 可以查看cmake引入Boost的帮助信息: 可以看到,Boot ...

  6. CMake和Linux编程:find_package的使用

    1.第一个CMake例子 在 t1 目录建立 main.c 和 CMakeLists.txt(注意文件名大小写): main.c 文件内容: //main.c #include <stdio.h ...

  7. 多版本opencv管理; find_package()的原理解析

    近期用cmake编译程序时,报错找不到opencv2.由于我电脑里安装了多个版本的opencv,管理不善,借此机会梳理一下思路. 1. Cmake -- find_package(Opencv REQ ...

  8. cmake find_package 中,include_directories,target_link_libraries 的值怎么知道?

    拿Sophus库为例: find_package(Sophus REQUIRED) include_directories(${Sophus_INCLUDE_DIRS}) target_link_li ...

  9. cmake:善用find_package()提高效率暨查找JNI支持

    cmake提供了很多实用的cmake-modules,通过find_package()命令调用这些modules,用于写CMakeLists.txt脚本时方便的查找依赖的库或其他编译相关的信息,善用这 ...

  10. find_package()的查找*.cmake的顺序

    1. find_package(<Name>)命令首先会在模块路径中寻找 Find<name>.cmake,这是查找库的一个典型方式.具体查找路径依次为CMake: 变量${C ...

随机推荐

  1. 黏包现象、struct模块和解决黏包问题的流程、UDP协议、并发编程理论、多道程序设计技术及进程理论 _

    目录 黏包现象 二.struct模块及解决黏包问题的流程 三.粘包代码实战 UDP协议(了解) 并发编程理论 多道技术 进程理论 进程的并行与并发 进程的三状态 黏包现象 什么是粘包 1.服务端连续执 ...

  2. 大前端html基础学习01

    根目录 相对路径:针对图片数量比较多的情况,新建一个文件夹,将所有图片放进去,imgs/cat.webp (1)/:下一级 (2)a/b/c/cat.webp 返回路径(向外找):从下一级html中找 ...

  3. AI绘画提示词创作指南:DALL·E 2、Midjourney和 Stable Diffusion最全大比拼 ⛵

    作者:韩信子@ShowMeAI 深度学习实战系列:https://www.showmeai.tech/tutorials/42 自然语言处理实战系列:https://www.showmeai.tech ...

  4. 疫情可视化part3

    前言 之前在part2中说的添加自定义主题配色已经开发完成了,除此之外我还添加了一些的3d特效. 前期文章 这是part1的文章:https://blog.csdn.net/xi1213/articl ...

  5. 【每日一题】【动态规划&二分】2022年2月9日-NC91 最长上升子序列(三)

    描述给定数组 arr ,设长度为 n ,输出 arr 的最长上升子序列.(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个) 方法1:双层循环实现 ...

  6. android nativate 动态注册 静态注册

    说明:在java函数的入口比较容易分析, 把activity的生命周期或者关键函数通过放在so层,分析起来就困难多了 1.在MainActivity中 package com.demo.nativat ...

  7. pandas中groupby的使用

    一.缘由 在爬取大量的数据之后,需要对数据进行分组的处理,于是就使用了groupby,但是我需要的并不是分组之后数据的聚合分析,我需要的是原生的某些数据.但是却找不到网上的相关案例.于是,我就自己尝试 ...

  8. python 之 random.sample() 报ValueError: Sample larger than population or is negative

    def device_id(): device = ''.join(random.sample(string.digits, 19)) return device print(device_id()) ...

  9. JavaScript:是一种什么样的编程语言?

    有关JavaScript的发展历程,百度百科上已经说得很清楚了,这里不赘述,只是想谈一下我刚刚接触JS的一些感触. 作为后端java开发者,初次学习JS的时候,真的觉得JS非常的不严谨,很混乱.由于它 ...

  10. windows通过sshfs挂载linux目录

    之前讲过一种方法,PC跟VM在同局域网的情况下,可以用samba的方式挂载linux系统的目录到windows上.但是当PC跟VM不同局域网时这种方式就没办法了. 网络环境 在示意图中,PC只能直连物 ...