关于find_package()

在使用cmake引用第三方库(比如OpenCV)时,我们总是使用find_package()这个指令来实现对包的查找(比如find_package(OpenCV))。调用完后就可以使用一些似乎凭空出现的变量如${OpenCV_INCLUDE_DIRS}以及${OpenCV_LIBS},分别指示了OpenCV库的头文件路径以及各个库文件位置。

find_package(OpenCV)
include_directories(${OpenCV_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})
# 这样就可以使用到OpenCV了

cmake官方文档对find_package()的解释是这样的:

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER])

其中[]中的内容表示为可选项。下面我们来解释find_package()是如何工作的。

find_package()的工作原理

实际上,find_package(<PackageName>)运行时,会去指定路径查找一些名字为

Find<PackageName>.cmake

<PackageName>Config.cmake

<lowercasePackageName>-config.cmake

<lowercasePackageName>-config-version.cmake # 指定版本信息

<PackageName>ConfigVersion.cmake # 指定版本信息

的文件,注意这里的命名格式是非常的固定的,基本都是FindXXX.cmake或者XXXConfig.cmake。这些后缀为.cmake的文件本质上也是使用cmake语言编写的脚本文件,它们会定义一些变量,比如<PackageName>_INCLUDE_DIRS<PackageName>_LIBS等。当find_package()找到这些文件时,会执行这些文件并将其中定义的变量引入到当前的cmake环境中。

对于搜索这些后缀为.cmake的文件,find_package()采用两种策略来实现

find_package()的模块模式

这里引用官方文档的解释:

在这种模式下,CMake 搜索名为Find<PackageName>.cmake的文件,首先在 CMAKE_MODULE_PATH 中列出的位置中查找,然后在 CMake 提供的 Find Modules 中查找安装。如果找到该文件,CMake 将读取并处理该文件。它负责查找包、检查版本并生成任何需要的消息。一些 Find 模块对版本控制提供有限支持或不支持;检查查找模块的文档。

一般来说,FindXXX.cmake并非为包所提供,大多数包提供的是更为严谨的XXXConfig.cmake,这将在后面说到。也就是说,FindXXX.cmake是一个较为简单的查找模块,大多为用户或者cmake本身自行编写或者提供。查找时,cmake会优先查找cmake环境变量的CMAKE_MODULE_PATH中的路径(这个变量默认为空),然后再查找cmake自带的Find Modules中的路径,这部分可以使用cmake --help-module-list查看cmake自带的模块列表,可以看到很多FindXXX.cmake文件。

或者对于一些轻量级的包,本身并不提供XXXConfig.cmake或者FindXXX.cmake,而是使用其他包管理器(比如package-config,使用后缀为.pc的文件来管理),若想使用find_package()引用这些包,就需要自行编写FindXXX.cmake文件。(当然也可以直接使用cmake中的pkg_check_modules()来引用这些包)。下面给出一个FindXXX.cmake的例子,引用的是ffmpeg这个包。

#FindFFMEPG.cmake
set(FFMPEG_SOURCE /home/ruby/ffmpeg_loc) #指定ffmpeg位置 set(FFMPEG_INCLUDE_DIRS ${FFMPEG_SOURCE}/include)
set(FFMPEG_LIBDIRS_DIRS ${FFMPEG_SOURCE}/lib) find_library(FFMPEG_AVCODEC_LIBRARY avcodec ${FFMPEG_LIBDIRS_DIR})
find_library(FFMPEG_AVFORMAT_LIBRARY avformat ${FFMPEG_LIBDIRS_DIR})
find_library(FFMPEG_AVUTIL_LIBRARY avutil ${FFMPEG_LIBDIRS_DIR})
find_library(FFMPEG_SWSCALE_LIBRARY swscale ${FFMPEG_LIBDIRS_DIR})
find_library(FFMPEG_SWRESAMPLE_LIBRARY swresample ${FFMPEG_LIBDIRS_DIR}) set(FFMPEG_LIBS ${FFMPEG_AVCODEC_LIBRARY} ${FFMPEG_AVFORMAT_LIBRARY} ${FFMPEG_AVUTIL_LIBRARY} ${FFMPEG_SWSCALE_LIBRARY} ${FFMPEG_SWRESAMPLE_LIBRARY})

可以看到,我们做到工作无非是设置一些变量,这些变量指向了ffmpeg的头文件路径以及库文件路径,然后使用find_library()来查找对应的库文件。

在使用时,我们可以使用set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} /path/to/FindFFMPEG.cmake)来指定FindFFMPEG.cmake的位置,然后使用find_package(FFMPEG)来引用ffmpeg这个包,然后就可以使用${FFMPEG_INCLUDE_DIRS}${FFMPEG_LIBS}了。若这个脚本就在当前目录下,可以直接使用set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})来指定。

find_package()的配置模式

这里引用官方文档的解释:

在这种模式下,CMake 搜索名为“<lowercasePackageName>-config.cmake”或“<PackageName>Config.cmake”的文件。如果指定了版本详细信息,它还将查找``<lowercasePackageName>-config-version.cmake`` 或``<PackageName>ConfigVersion.cmake``(请参阅:ref:version selection 以了解如何将它们分开使用版本文件)。

在配置模式下,可以为该命令提供一个名称列表,以作为包名称进行搜索。 CMake 搜索配置和版本文件的位置比模块模式复杂得多。

配置和版本文件通常作为包的一部分安装,因此它们往往比查找模块更可靠。它们通常包含包内容的直接知识,因此不需要在配置或版本文件本身中进行搜索或试探。

对于大多数的第三方包,都会提供XXXConfig.cmake文件,这个文件会定义一些变量,比如XXX_INCLUDE_DIRSXXX_LIBS/XXX_LIBRARIS等。当模块模式搜索不到时,自动切换到配置模式进行搜索。配置模式的搜索非常繁琐,会尽一切可能去搜索。有一些我也看不懂,这里挑几个比较易懂且常用的来说。

  • XXX_DIR指定的路径下搜索XXXConfig.cmake文件,XXX_DIR为变量或者环境变量,指定到配置文件所在路径
  • CMAKE_PREFIX_PATH指定的路径下搜索XXXConfig.cmake文件
  • 在环境变量PATH下搜索XXXConfig.cmake文件
  • .....

其中,第2和第3种方式提供了一种以前缀路径的方式来指定包的位置,当本级路径搜索不到时,cmake会将本级路径作为前缀去搜索该路径下其他文件中是否有配置文件。匹配规则如下:

其中的<prefix>或者<前缀>即为上述指定的根路径,以根路径为前缀,一直去搜索,直到找到配置文件为止。例如find_package(OpenCV),其中PATH指定有一个路径为/usr/lib/x86_64-linux-gnu/,查找时便会找

  • /usr/lib/x86_64-linux-gnu/
  • /usr/lib/x86_64-linux-gnu/cmake/
  • /usr/lib/x86_64-linux-gnu/OpenCV(opencv)/
  • /usr/lib/x86_64-linux-gnu/cmake/OpenCV(opencv)/ ....

等能匹配上的路径,注意,这里的<name>*中的name对应于find_package()中的参数,即find_package(OpenCV)中的OpenCV。但是在作为前缀路径时,name参数不区分大小写,且允许有后缀,如opencv4.5。规则中的|代表选其一,<arch>为系统架构,比如x86,64位架构下就会搜索/lib/x86_64-linux-gnuarm64架构下则会搜索/lib/aarch64-linux-gnu等。

如何灵活使用?

对于自己写的FindXXX.cmake,在使用时用

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR})
# or不在本级目录
set(MY_FINDXXX "path/to/FindXXX.cmake")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${MY_FINDXXX})

对于三方库,可以直接用find_package()

find_package(OpenCV)

倘若找不到,可以用locate命令查找一下,然后手动指定路径

locate OpenCVConfig.cmake

然后将配置文件所在路径赋值给XXX_DIR

set(OpenCV_DIR /path/to/opencv)

对于有多个配置文件的项目(如Qt),使用CMAKE_PREFIX_PATH来指定路径

set(CMAKE_PREFIX_PATH /path/to/qt)

给出一个例子

进入到可以匹配到路径的路径,我这里是/home/ruby/Qt5.14.0/5.14.0/gcc_64,里面的格式为

也就是最好找到带有lib字眼或者cmake字眼的那一级目录即可,然后将这个路径赋值给CMAKE_PREFIX_PATH

set(CMAKE_PREFIX_PATH /home/ruby/Qt5.14.0/5.14.0/gcc_64)

即可。使用这个方法可以简便的使用不同版本的Qt,比如我这里有5.14.0和5.15.0两个版本,只需要将CMAKE_PREFIX_PATH指定到对应的路径即可。

set(CMAKE_PREFIX_PATH /home/ruby/Qt5.15.0/5.15.0/gcc_64)
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} /home/ruby/Qt5.14.0/5.14.0/gcc_64)
# 注意,这两个路径正常直接find是find不到的,只能加入CMAKE_PREFIX_PATH中 #find_package(Qt5 <version> COMPONENTS Core Widgets REQUIRED), version填版本号,如 find_package(Qt5 5.15.0 COMPONENTS Core Widgets REQUIRED)
# or
find_package(Qt5 5.14.0 COMPONENTS Core Widgets REQUIRED)
可以加入EXACT来精确匹配,当匹配不到时会报错
find_package(Qt5 5.14.0 EXACT COMPONENTS Core Widgets REQUIRED)

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

  1. ROS_Kinetic_02 ROS Kinetic 迁移指南及中文wiki指南(Migration guide)

    ROS_Kinetic_02 ROS Kinetic 迁移指南(Migration guide) 对于ROS Kinetic Kame有些功能包已经更新改变,提供关于这些包的迁移注意或教程.主要针对于 ...

  2. cmake简明使用指南

    cmake简明使用指南 Last update 2018/8/8 先执行cmake生成makefile,然后看看里面的内容,(至少在ubuntu16.04上的cmake3.5.1上),有如下内容提供: ...

  3. ubuntu日常使用指南

    目录 换源 开发相关的基本包 vimrc python, pip zsh, oh-my-zsh, josh 配置android相关环境 查看库文件(libxxx.a/libxxx.so,动态静态库均可 ...

  4. OpenCV On Android环境配置最新&最全指南(Android Studio篇)

    本文是从本人简书上搬运而来,属本人原创,如有转载,请注明出处:http://www.jianshu.com/p/6e16c0429044 简介 本文是<OpenCV On Android环境配置 ...

  5. JavaScript权威指南 - 函数

    函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...

  6. UE4新手之编程指南

    虚幻引擎4为程序员提供了两套工具集,可共同使用来加速开发的工作流程. 新的游戏类.Slate和Canvas用户接口元素以及编辑器功能可以使用C++语言来编写,并且在使用Visual Studio 或 ...

  7. JavaScript权威指南 - 对象

    JavaScript对象可以看作是属性的无序集合,每个属性就是一个键值对,可增可删. JavaScript中的所有事物都是对象:字符串.数字.数组.日期,等等. JavaScript对象除了可以保持自 ...

  8. JavaScript权威指南 - 数组

    JavaScript数组是一种特殊类型的对象. JavaScript数组元素可以为任意类型,最大容纳232-1个元素. JavaScript数组是动态的,有新元素添加时,自动更新length属性. J ...

  9. const extern static 终极指南

    const extern static 终极指南 不管是从事哪种语言的开发工作,const extern static 这三个关键字的用法和原理都是我们必须明白的.本文将对此做出非常详细的讲解. co ...

  10. Atitit.研发管理软件公司的软资产列表指南

    Atitit.研发管理软件公司的软资产列表指南 1. Isv模型下的软资产1 2. 实现层面implet1 3. 规范spec层1 4. 法则定律等val层的总结2 1. Isv模型下的软资产 Sof ...

随机推荐

  1. mosquitto(MQTT)服务器搭建和基本使用

    一.安装 搭建一个mqtt服务器,这里我们采用mosquitto 1. 下载地址:https://mosquitto.org/download/ 2. 选择windows:https://mosqui ...

  2. 常见return错误

    常见return错误 3221225477 (0xC0000005): 访问越界,一般是读或写了野指针指向的内存. 3221225725 (0xC00000FD): 堆栈溢出,一般是无穷递归造成的. ...

  3. Nginx增加网页认证功能

    Nginx增加网页认证功能 增加认证功能模块 ngx_http_auth_basic_module 模块实现让访问者,只有输入正确的用户密码才允许访问web内容.web上的一些内容不想被其他人知道,但 ...

  4. 关于C++当中全局变量的释放问题

    一.由来 主要是在修改公司的一个MFC项目的时候遇到的问题,我在MFC页面的析构函数当中对一个全局图像变量进行了释放,具体如下: ai_engine_OCR::~ai_engine_OCR() { / ...

  5. 两台笔记本电脑实现同一wifi下虚拟主机网络实现互通

    一台win笔记本 (安装vmware) 一台macbookpro 本人考虑到M1的macbook,无法安装vmware,这让我这个linux运维人员很是dan疼,没办法只能在自己的win笔记本上安装v ...

  6. cmu15545笔记-Join算法(Join Algorithms)

    目录 Overview Nested Loop Join Naïve Block Index Sort-Merge Join Hash Join Simple Hash Join Partition ...

  7. Abp Vnext 中如何统一接口返回值

    ABP Vnext Vue 的实现 https://github.com/WangJunZzz/abp-vnext-pro 在使用 abp 的过程中,如果提供给第三方接口要实现返回值统一需要怎么做? ...

  8. Ansible常用功能说明 [异步、并发、委托等]

    文章目录 Ansible的同步模式与异步模式 Ansible的异步和轮询 [async.poll] Ansible的并发限制 [serial.max_fail_percentage] Ansible的 ...

  9. Vue.js 组件基础

    1.前言 本节讲述vue组件的基本格式以及实际开发的应用场景,本节内容兼容Vue2.x与Vue3.x 2.组件基础 组件的本质其实就是一个对象,这个对象包含多个属性,常见的属性有:template/d ...

  10. C#获得本地IP地址的各种方法

    网上有很多种方法可以获取到本地的IP地址.一线常用的有这么些: 枚举本地网卡 using System.Net.NetworkInformation; using System.Net.Sockets ...