Cmake中find_package命令的搜索模式之模块模式(Module mode)

一、模块模式简介

  前面介绍了find_package有两种搜索包的模式(参考find_package介绍),本篇文章介绍其中的一种:模块模式(Module Mode)。在这种模式下,当调用find_package命令查找<PackageName>包的时候,实际上会去查找一个名为Find<PackageName>.cmake的文件,这个文件的主要任务就是确定一个包是否可用,查找的结果会反映在变量<PackageName>_FOUND上供find_package的调用者使用。当找到可用的包,同时也会提供使用这个包所需要的变量、宏和导入目标(例如库文件)。
  前面已经介绍过使用系统提供的FindLibLZMA.cmake来查找LibLZMA库,这个文件是CMake在安装的时候就提供的,位于CMake的安装目录之下。因此对于未提供该文件的第三方库,我们可以通过自己生成Find<PackageName>.cmake来供CMake使用,接下来将介绍如何利用自己生成的.cmake文件找到自己编写的库。
  模块模式依赖另一个程序:pkg-config,在继续往下之前,请参考pkg-config用法详解了解这个命令。

二、标准变量名称

  Find<PackageName>.cmake文件承担了定义<PackageName>包相关的变量的作用,这些变量称作"标准变量"。一旦find_package调用成功,这些变量将返回给调用者使用,为了保证不同的包之间返回的变量不冲突,对编写的Find<PackageName>.cmake返回的标准变量名称有如下约束:所有的变量都是以PackageName_开头,PackageName就是文件Find<PackageName>.cmake中的<PackageName>,必须完全一致,大小写敏感。简单的列举几个变量定义如下,更多的变量定义见本文的四、对标准变量名称的更多说明

  • PackageName_INCLUDE_DIRS:使用包需要包含的头文件。
  • PackageName_LIBRARIES:使用包所需要的库文件,是全路径或者链接器能在库搜索目录下找到的库文件名称。
  • PackageName_DEFINITIONS:使用包所需要的编译选项。
  • PackageName_LIBRARY:库的路径,只有当包提供的是单个库的时候才能使用这形式。
  • PackageName_INCLUDE_DIR:使用包所需要包含的头文件目录,只能在单个库的使用,使用者需要将该路径加入到搜索路径中。

三、编写.cmake文件

  接下来我们来写一个.cmake文件,假设我们的包名为mymath,该包提供一个libmymath.a的库,其中包含一个add接口,简单计算两个整数的和并打印出结果(对这个库的更多信息可以参考pkg-config用法详解4.1~4.3小节)。在编写Findmymath.cmake之前,我们先来看下.cmake文件的格式。

3.1 .cmake文件的格式

  • 文件开头是license信息
  • 接着是一个CMake支持的多行注释(单行注释以#开头;多行注释是指:以#[开头,紧接着跟着0个或多个=,之后是[,接下来就是注释内容,注释可以跨越多行,然后以]0个或多个=]组成结束,开头的=个数要和结尾的=个数相等),.cmake要求注释以.rst:开头:
# 多行注释,0个=的情况
#[[
...
中间的内容都是注释
...
]]
# 多行注释,多个=的情况,开始的=和结尾的=要保持数量一致,此例子中为5个=
#[=====[
...
中间的内容都是注释
...
]=====]
  • 接下来在注释中间申明find_package的标准变量及相关说明。
    1)首先是包的名字,分为两行,第一行是包名字,第二行是包名字下方的下划线---(与包名字等长度)。
    2)接着是对包的一个简要描述,这个没有特殊要求。
    3)接下来分为几个部分,主要是作用对几类变量的申明(导入变量、结果变量、缓存变量),格式都是一致的:首先是变量类型的说明,并在其下方以等长^^^^标识,接着是一段文件对变量内容的简要描述,接着是我们要定义的标准变量了,变量以``标准变量``标识(两对``符号,中间是变量名称),每个变量下可以对变量做一个简短的描述说明。
  • 注释部分到此结束,接下来是对库进行真正查找,并把注释部分申明的变量进行赋值的过程。
    1)先尝试使用pkg-config来找到真正的库,pkg-config是系统提供的命令用于找系统中是否存在相关的库(参考pkg-config用法详解),在CMake中使用如下两条,CMake会从<PackageName>.pc文件中读取对应的变量。
    find_package(PkgConfig)
    pkg_check_modules(PC_mymath QUIET mymath)
    2)如果能找到库,那么变量PC_mymath_FOUND存在,并且可以得到mymath相关的头文件和库目录,并且是存储在以PC_mymath_XXX开头的变量中,例如PC_mymath_INCLUDE_DIRS(头文件目录)、PC_mymath_LIBRARY_DIRS(库文件目录)、PC_mymath_LIBRARIES(库名称)等等(具体有哪些变量可以参考man pkg-config或者在CMakeCache.txt中过滤PC_mymath查看)。
  • 上一步利用了pkg-config获得的变量还不是最终要给find_package返回的变量,我们要对返回的变量做正确的赋值,并最终调用include(FindPackageHandleStandardArgs)find_package_handle_standard_args将变量返回给find_package调用处。
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(mymath
FOUND_VAR mymath_FOUND
REQUIRED_VARS
 mymath_LIBRARY
 mymath_INCLUDE_DIR
VERSION_VAR mymath_VERSION
)

3.2 编写自己的Findmymath.cmake文件

  假定我们使用的库mymath已经提供了.pc文件,并能够通过pkg-config方式找到它(可以通过pkg-onfig --list-all查看到mymath库,参考pkg-config用法详解)。

  接下来我们来编写库mymathFindmymath.cmake文件,参照前面.cmake文件说明,内容如下,示例只提供了库目录、库文件、头文件等少量变量信息:

# Findmymath.cmake

#[============[.rst:
Findmymath
----------

对这个文件的描述:查找mymath库

Imported Targets
^^^^^^^^^^^^^^^^

如果提供导出可执行目标,可以在下面进行定义:
``mymath::mymath``
    导出mymath::mymath可执行目标,我们的测试库并未提供,该处只是一个示意

Result Variables
^^^^^^^^^^^^^^^^

可以在下面定义一些普通变量:

``mymath_FOUND``
    如果找到mymath库,该变量值为True.
``mymath_VERSION``
    mymath库的版本.
``mymath_INCLUDE_DIRS``
    使用mymath库需要包含的头文件.
``mymath_LIBRARIES``
    使用mymath库需要用到的库文件.

Cache Variables
^^^^^^^^^^^^^^^

可以在下面定义一些缓存变量:

``mymath_INCLUDE_DIR``
    包含mymath.h头文件的目录.
``mymath_LIBRARY``
    mymath库所在的目录.
]============]

find_package(PkgConfig)
pkg_check_modules(PC_mymath QUIET mymath)

find_path(mymath_INCLUDE_DIR
    NAMES mymath.h
    PATHS ${PC_mymath_INCLUDE_DIRS}
    PATH_SUFFIXES mymath)

find_library(mymath_LIBRARY
    NAMES mymath
    PATHS ${PC_mymath_LIBRARY_DIRS})

set(mymath_VERSION ${PC_mymath_VERSION})
set(mymath_INCLUDE_DIRS "/just/for/include/test") # 只是为了测试用,没有实际作用
set(mymath_LIBRARY_DIRS "/just/for/library/test") # 只是为了测试用,没有实际作用

# 将变量导出给调用者使用
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(mymath
  FOUND_VAR mymath_FOUND
  REQUIRED_VARS
    mymath_LIBRARY
    mymath_INCLUDE_DIR
    mymath_INCLUDE_DIRS
    mymath_LIBRARY_DIRS
  VERSION_VAR mymath_VERSION
)

  我们的测试文件test.cpp如下:

// test.cpp
#include "mymath.h"
int main(int argc, char** argv)
{
    mymath::add(1, 2);
    return 0;
}

  最后,在我们的CMakeLists.txt来使用find_package来找到并使用mymath

# CMakeLists.txt,和测试文件test.cpp在同个目录
cmake_minimum_required(VERSION 3.10.2)
project(find_package_test)
message("Find path: ${CMAKE_MODULE_PATH}")
find_package(mymath)
if (mymath_FOUND)
    add_executable(test test.cpp)
    include_directories(${mymath_INCLUDE_DIR})
    target_link_libraries(test ${mymath_LIBRARY})
    set_target_properties(test PROPERTIES
        INTERFACE_COMPILE_OPTIONS "${PC_mymath_CFLAGS_OTHER}") # 这个语句在本例中不是必须,只是说明可以通过这种方式获取编译mymath库需要使用的编译选项
endif()

  在CMakeLists.txt下执行cmake命令,此处为了演示,直接指定CMAKE_MODULE_PATH的值为当前的.cmake所在路径,这样find_package命令会直接找到我们编写的.cmake文件并读取其中的内容,编译并运行最终程序(只摘取了我们关注的显示信息):

# 执行cmake
cmake . -DCMAKE_MODULE_PATH=./

# 运行结果,首次会打印出来找到的库以及所在的全路径、版本号等信息
......
-- Found PkgConfig: /usr/local/bin/pkg-config (found version "0.29.2")
-- Found mymath: /XXX/mymath/lib/libmymath.a (found version "1.0")
......

# 执行make
make

# 运行程序
./test

# 运行结果
Add 1 and 2 is 3

四、对标准变量名称的更多说明

  • PackageName_INCLUDE_DIRS:使用包需要包含的头文件。

  • PackageName_LIBRARIES:使用包所需要的库文件,是全路径或者链接器能在库搜索目录下找到的库文件名称。

  • PackageName_DEFINITIONS:使用包所需要的编译选项。

  • PackageName_EXECUTABLE:可执行文件的全路径。

  • PackageName_YYY_EXECUTABLE:与上面类似,不过此处的YYY表示包中提供的其他可执行文件的名字,通常是全大写,可以使用这种方式避免名称冲突。可以用于包提供多个可执行文件的场景。

  • PackageName_LIBRARY_DIRS:可选,库目录。

  • PackageName_ROOT_DIR:包查找的根目录

  • PackageName_VERSION_VV:指定模块的版本号是VV,但是要注意,一个模块可能有多个历史的版本,只能有一个版本的变量设置成true,例如模块mymath有三个版本,分别是Version 1Version 2Version 3,那么mymath_VERSION_1mymath_VERSION_2mymath_VERSION_3这三个变量只能有一个设置为true,否则会报错。

  • PackageName_WRAP_YY:当该变量设置为false,表明相关的包装命令无法使用。

  • PackageName_Yy_FOUND:此处的Yy表示包的组件,必须跟find_package命令提供的组件名称完全匹配,如果该变量设置为false,表明组件未找到或者不可用。可用于检查哪些组件是可用的。

  • PackageName_FOUND:如果能找到模块,那么该变量会置为true

  • PackageName_NOT_FOUND_MESSAGE:未找到时候的消息提示。

  • PackageName_RUNTIME_LIBRARY_DIRS:可选,运行库的搜索路径,当可执行文件需要链接到共享库的时候需要指定该变量,以让链接器能找到对应的共享库路径。在windows下,会将该变量内容加入到PATH,Linux下会加入到LD_LIBRARY_PATH

  • PackageName_VERSION:找到模块的全版本字符串,值得注意的是当前许多存在的模块使用的是PackageName_VERSION_STRING替代。

  • PackageName_VERSION_MAJOR:主版本号

  • PackageName_VERSION_MINOR:次版本号

  • PackageName_VERSION_PATCH:补丁版本号

  • PackageName_LIBRARY:库路径,只有当模块提供的是单个库的时候才能使用这形式,多个库可以参考下面的命令。

  • PackageName_Yy_LIBRARY:模块PackageName提供的库Yy的路径,当一个包下有多个库、或者其他包有同名的库时(也就是不同包之间库重名的时)使用。

  • PackageName_INCLUDE_DIR:使用库需要包含的头文件目录,只能在单个库的使用。使用者需要将该路径加入到搜索路径中。

  • PackageName_Yy_INCLUDE_DIR:多个库时候,指定使用库Yy需要包含的头文件。


附录:参考文档

  1. https://cmake.org/cmake/help/v3.0/module/FindPkgConfig.html
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,252评论 6 516
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,886评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,814评论 0 361
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,869评论 1 299
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,888评论 6 398
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,475评论 1 312
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,010评论 3 422
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,924评论 0 277
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,469评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,552评论 3 342
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,680评论 1 353
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,362评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,037评论 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,519评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,621评论 1 274
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,099评论 3 378
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,691评论 2 361

推荐阅读更多精彩内容