一、模块模式简介
前面介绍了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用法详解)。
接下来我们来编写库mymath
的Findmymath.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 1
、Version 2
、Version 3
,那么mymath_VERSION_1
、mymath_VERSION_2
、mymath_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
需要包含的头文件。
网友评论