一、find_package可以解决的问题
当构建一个依赖第三库或外部库的project时(即:project需要链接第三方库或外部库),我们需要知道以下信息:
去哪儿找第三 方库的头文件 .h | 对比GCC的 -I 参数 |
---|---|
去哪儿找第三方库的链接文件 (.so/.dll/.lib/.dylib/…) | 对比GCC的 -L 参数 |
链接的第三方库的文件的名字 | 对比GCC的 -l 参数 |
知道上面的信息后,就可以在CMakeLists.txt中方便的包含第三方库的头文件、访问的链接第三方库的库文件(.a、.so)了;
二、为什么使用find_package
第三方库的安装路径,在不同的机器上可能不同,所以在CMakeLists.txt中指定包含路径、链接路径和库,不太现实,除非你指定工程所需要的依赖库安装在固定的目录(貌似不太现实),否则在你的机器上构建成功了,在别人的机器上可能构建失败。或修改了第三方库的安装路径或版本升级后,在自己的机器上可能也无法构建成功。
使用cmake的find_package可解决上面的问题。
举个栗子:
比如说,我们需要一个第三方库 curl,那么我们的 CMakeLists.txt 需要指定头文件目录,和库文件,类似:
include_directiories(/usr/include/curl)
target_link_libraries(myprogram path/curl.so)
如果借助于cmake提供的finder会怎么样呢?使用cmake的Modules目录下的FindCURL.cmake,相应的CMakeList.txt 文件:
find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIR})
target_link_libraries(curltest ${CURL_LIBRARY})
#使用find_package,不用关心curl具体安状在什么位置,find_package会替我们解决,那么是如何智能查找的呢?
三、find_package的原理
find_package首先会在模块路径中寻找Findxxxx.cmake,这是查找库的典型方式。具体查找路径依次为CMake变量${CMAKE_MODULE_PATH}中的所有目录,如果没有,再查找它自己的模块目录/share/cmake-x.y/Modules/($CMAKE_ROOT的具体值可以通过CMake中message命令输出)。如果找到了xxxx模块,那么Findxxxx.cmake一般会设置以下变量供CMakeLists.txt使用:
xxxx_FOUND #为true
xxxx_INCLUDE_DIRS #include路径
xxxx_LIBRARY_DIRS #library路径
xxxx_LIBRARIES #library的名字
xxxx_yyyy_VERSION #具体详见Findxxxx.cmake
为了能支持各种常见的库和包,CMake自带了很多Findxxxx模块。可以通过命令 cmake –help-module-list (输入cmake –help,然后双击Tab会有命令提示)得到你的CMake支持的模块的列表,也可以直接查看模块路径: ls /usr/share/cmake/Modules/
四、基本语法和模式
根据cmake官方文档可以知道,find_package()有Module模式(基本用法,basic signature)和Config模式(full signature,完全用法),其中Module模式是基础,Config模式则更复杂高级些。
Module模式基本语法:
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
Config模式基本语法:
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_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])
更详细的请参见:https://cmake.org/cmake/help/v3.18/command/find_package.html#find-package
只有以下3种情况下才是Config模式:
find_package()中指定CONFIG关键字
find_package()中指定NO_MODULE关键字
find_package()中使用了不在"basic signature"(也就是Module模式下所有支持的配置)关键字
只要不指定"CONFIG",不指定“NO_MODULE",也不使用"full signature"中的关键字,那我就是在Module模式。排查find_package()的第一步,应当判断它是Module模式还是Config模式。
五、基本用法
1、Module模式下find_package()的用法
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
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<PackageName>.cmake的文件。
先在CMAKE_MODULE_PATH变量对应的路径中查找。如果路径为空,或者路径中查找失败,则在cmake module directory(cmake安装时的Modules目录,比如/usr/local/share/cmake/Modules)查找。
2、Config模式下find_package()的用法
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_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。查找顺序为:
名为<PackageName>_ROOT的cmake变量或环境变量。CMake3.12新增。设定CMP0074 Policy来关闭。
注意:如果定义了<PackageName>_DIR cmake变量,那么<PackageName>_ROOT 不起作用。
cmake特定的缓存变量:
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通过设定NO_CMAKE_PATH来关闭这一查找顺序
cmake特定的环境变量
<PackageName>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
可以通过NO_CMAKE_ENVIRONMENT_PATH来跳过。
HINT字段指定的路径
搜索标准的系统环境变量PATH。
其中如果是以/bin或者/sbin结尾的,会自动转化为其父目录。
通过指定NO_SYSTEM_ENVIRONMENT_PATH来跳过。
存储在cmake的"User Package Registry"(用户包注册表)中的路径。
通过设定NO_CMAKE_PACKAGE_REGISTRY,或者:
设定CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY为true,
来避开。
设定为当前系统定义的cmake变量:
CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
通过设定NO_CMAKE_SYSTEM_PATH来跳过。
在cmake的"System Package Registry"(系统包注册表)中查找。
通过设定NO_CMAKE_SYSTEM_PACKAGE_REGISTRY跳过。
或者通过设定CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY为true。
从PATHS字段指定的路径中查找。