1. 检索模式
1.1 module模式
在这个模式下会查找一个名为find.cmake的文件,首先去CMAKE_MODULE_PATH指定的路径下去查找,然后去cmake安装提供的查找模块中查找(安装cmake时生成的一些cmake文件)。找到之后会检查版本,生成一些需要的信息。
1.2 config模式
在这个模式下会查找一个名为-config.cmake(<小写包名>-config.cmake)或者Config.cmake 的文件,如果指定了版本信息也会搜索名为-config-version.cmake 或者 ConfigVersion.cmake的文件。
1.2.1 搜索路径
该模式搜索配置和版本文件的路径比module模式复杂的多:
首先在CMAKE_FIND_PACKAGE_REDIRECTS_DIR指定的路径下查找。如果没有找到配置文件,则按照下面的逻辑进行查找:
cmake会为包构建可能的前缀,然后再前缀目录下搜索多个可能的目录,cmake指定的安装目录肯定是构建的前缀之一。
简单解释一下,prefix就是我们cmake编译的时候指定的安装路径(CMAKE_STAGING_PREFIX的路径)(cmake指定的安装目录肯定是搜索路径之一,还可能有其他可能的目录);
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/就表示可能在/lib/cmake/或者/lib*/cmake/或者/share/cmake/目录下.
如果FIND_LIBRARY_USE_LIB64_PATHS被置为true,则lib64的路径将再64位平台上被搜索。
如果FIND_LIBRARY_USE_LIB32_PATHS被置为true,则lib32的路径将再32位平台上被搜索。
lib路径始终被搜索。
使用以下步骤构建安装前缀集(搜索路径的prefix字段)。如果指定NO_DEFAULT_PATH,则启用所有NO_*选项:
(1). _ROOT环境变量中指定的搜索路径,其中是要查找的包(find_package的第一个参数保留大小写)。如果传递了NO_PACKAGE_ROOT_PATH,或者将CMAKE_FIND_USE_PACKAGE_ROOT_PATH设置为FALSE,则可以跳过此操作。
(2). 搜索cmake特定缓存变量中指定的路径。通过命令行-DVAR=value传递进来的路径,多个路径需要以分号隔开。包含
CMAKE_PFEFIX_PATH、
CMAKE_FRAMEWORK_PATH、
CMAKE_APPBUNDLE_PATH三个变量;
例如
cmake -DCMAKE_PREFIX_PATH=/usr/local/lib;/lib。可以通过NO_CMAKE_PATH选项或将CMAKE_FIND_USE_CMAKE_PATH设置为FALSE来跳过
(3). 搜索特定于cmake的环境变量中指定的路径。如果传递了NO_CMAKE_ENVIRONMENT_PATH,或者将CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH设置为FALSE,则可以跳过此操作:
<PackageName>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
(4). HINTS指定的路径或者标准系统环境变量指定的路径,例如:PATH,LD_LIBRARY_PATH;在~/.bashrc或者/etc/profile文件中指定
(5). 搜索当前系统的平台文件中定义的cmake变量(就是当前系统定义的一些cmake相关的变量)
例如:
CMAKE_INSTALL_PREFIX
CMAKE_STAGING_PREFIX
但是可以被传递NO_CMAKE_INSTALL_PREFIX或者CMAKE_FIND_USE_INSTALL_PREFIX设置为FALSE跳过。
如果传递了NO_CMAKE_SYSTEM_PATH,或者将CMAKE_FIND_USE_CMAKE_SYSTEM_PATH设置为FALSE,则可以跳过所有这些位置:
CMAKE_SYSTEM_PREFIX_PATH
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_SYSTEM_APPBUNDLE_PATH
这些变量包含的路径一般是系统默认安装路径:/usr/local等。
(6). PATHS指定的路径
1.2.2 版本配置文件
当指定version参数,配置模式将仅会查找能兼容指定版本的包,如果指定了EXACT,则会查找精确匹配指定版本的包。
CMake本身不会对版本号做任何转换,而是通过查找到包的版本校验文件(包自身提供的)<PackageName>ConfigVersion.cmake(或<PackageName>-config-version.cmake),调用版本配置文件做校验,版本配置文件可以通过CMakePackageConfigHelpers模块来辅助创建。
当find_package命令中指定version参数后,会把version参数解析出来,赋值到PACKAGE_FIND_XXX中,定义了以下变量:
单一版本号:
PACKAGE_FIND_NAME:包名
PACKAGE_FIND_VERSION:全版本字符串
PACKAGE_FIND_VERSION_MAJOR:主版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MINOR:次版本,如果未指定则为0
PACKAGE_FIND_VERSION_PATCH:补丁版本,如果未指定则为0
PACKAGE_FIND_VERSION_TWEAK:小版本,如果未指定则为0
PACKAGE_FIND_VERSION_COUNT:版本组成部分的数量,范围为0~4
范围版本号:
PACKAGE_FIND_VERSION_RANGE:全版本范围字符串
PACKAGE_FIND_VERSION_RANGE_MIN:表示是否包含低版本,当前只支持INCLUDE,也就是说必然会包含低版本
PACKAGE_FIND_VERSION_RANGE_MAX:表示是否包含高版本,当前支持INCLUDE和EXCLUDE
PACKAGE_FIND_VERSION_MIN:低版本的全版本字符串
PACKAGE_FIND_VERSION_MIN_MAJOR:低版本的主版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MIN_MINOR:低版本的次版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MIN_PATCH:低版本的补丁版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MIN_TWEAK:低版本的小版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MIN_COUNT:低版本组成部分的数量,范围为0~4
PACKAGE_FIND_VERSION_MAX:高版本的全版本字符串
PACKAGE_FIND_VERSION_MAX_MAJOR:高版本的主版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MAX_MINOR:高版本的次版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MAX_PATCH:高版本的补丁版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MAX_TWEAK:高版本的小版本号,如果未指定则为0
PACKAGE_FIND_VERSION_MAX_COUNT:高版本组成部分的数量,范围为0~4
当版本配置文件完成版本校验后,会设置命名如PACKAGE_VERSION_XXX的变量供find_package使用,具体的变量如下:
PACKAGE_VERSION:版本文件中提供的全版本字符串
PACKAGE_VERSION_EXACT:如果精确匹配,那么该变量为True
PACKAGE_VERSION_COMPATIBLE:如果版本兼容,那么该变量为True
PACKAGE_VERSION_UNSUITABLE:如果未找到合适的版本,该变量为True
上面的PACKAGE_VERSION_XXX几个变量仅用于find_package命令检查配置文件是否提供了一个可接受的版本,一旦find_package命令返回后,这些变量就失效了。
如果版本校验通过,那么如下_VERSION_XXX变量会被设置,供find_package调用者使用:
_VERSION:包的全版本字符串
_VERSION_MAJOR:主版本
_VERSION_MINOR:此版本
_VERSION_PATCH:补丁版本
_VERSION_TWEAK:小版本
_VERSION_COUNT:点分版本组成的数量,范围0~4
2. 命令格式
2.1 基础命令
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])
module模式和config模式都支持该命令格式。
version:请求与该包兼容的版本。有两种可能的形式可以指定它:
(1):格式为major[.minor[.patch]的单一版本,例如:每个部分都是一个数字,均为可选:0.0.0.0或0.0.0或0.0.
(2):格式为versionMin…[<]versionMax,其中versionMin和versionMax具有与单个版本相同的格式和对整数组件的约束(例如:0.0.0.0...1.1.1.1)。默认情况下,包括两个端点。通过指定<,将排除上结束点。版本范围仅支持CMake 3.19或更高版本。
EXACT: 选项要求精确匹配版本。此选项与版本范围的规格不兼容(版本的第二种格式)。
MODULE:只是用module模式进行查找,不进行config模式查找,如果没指定的话,优先使用module模式搜索,如果未找到在进行config模式搜索。
QUIET:禁用信息消息。正常情况当找到包时,CMake会打印一些信息,指定该选项时会禁止掉这些打印。如果找不到包,仍然会输出错误信息并终止执行过程。
REQUIRED:当包没有被找到时,停止处理,及cmake停止继续运行。
COMPONENTS:指定要查找的组件列表,一般一个包可能有很多组件组成(例如一个库生成需要很多其他的依赖库),通常找到这些全部组件才认为包被找到。
OPTIONAL_COMPONENTS:指定要查找的组件列表,与COMPONENTS的区别是,不要求这些组件必须存在。不影响CMake的执行。
REGISTRY_VIEW:用于windows,在这不做考虑。
GLOBAL:将把所有导入的目标提升到导入项目中的全局作用域。可以通过设置CMAKE_FIND_PACKAGE_TARGETS_GLOBAL变量来启用此功能。
2.2 完整命令
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[CONFIG|NO_MODULE]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[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_INSTALL_PREFIX]
[NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])
CONFIG/NO_MODULE:这两个选项二选一即可,表示强制find_package命令使用config模式搜索,忽略module模式搜索。
NAMES:默认情况下find_package命令会查找名为的包。如果NAMES指定了名称,则会使用这些名字来查找包而忽略参数。
PATHS/HINTS:config模式下指定.cmake文件的搜索路径。
NO_XXX_PATH:config模式下忽略指定的路径。
3. find_package实例用法
cmake引用外部自定义库的测试案例:
首先写个加法器,用于生成库文件供其他程序调用,在/home/findpack下创建三个文件:
add.c
#include "./include/add.h"
int add(int first, int second)
{
int val = 0;
val = first+second;
return val;
}
add.h
#ifndef _ADD_H_
#define _ADD_H_
#include <stdio.h>
int add(int first, int second);
#endif
main.c
#include "add.h"
int main(void)
{
int result = 0;
result = add(1, 1);
printf("result = %d\n", result);
return 0;
}
执行:gcc -shared -fPIC -o libadd.so add.c 生成libadd.so库文件,在该目录下创建 include, lib两个文件,include用于存放add.h,lib用于存放libadd.so,此时当前目录结构如下:
add.c main.c include lib CMakeLists.txt
然后创建cmake目录,cmake目录下创建FindAdd.cmake文件,用于find_package函数查找,内容如下:
//CMAKE_SOURCE_DIR:CMakeLists.txt所在的目录
//在指定目录(${CMAKE_SOURCE_DIR}/include)下查找add.h文件,如果找到将路径返回给ADD_INCLUDE_DIR
find_path(ADD_INCLUDE_DIR add.h ${CMAKE_SOURCE_DIR}/include)
//在指定目录(${CMAKE_SOURCE_DIR}/lib)下查找add.h文件,如果找到将路径返回给ADD_LIBRARY,
//NAMES : 要查找的库名字,可以有多个
//PATHS:查找的路径,可以有多个
find_library(ADD_LIBRARY NAMES add PATHS ${CMAKE_SOURCE_DIR}/lib)
//如果ADD_INCLUDE_DIR ADD_LIBRARY都有结果,设置ADD_FOUND为真,作为find_package的返回结果
if (ADD_INCLUDE_DIR AND ADD_LIBRARY)
set(ADD_FOUND TRUE)
endif (ADD_INCLUDE_DIR AND ADD_LIBRARY)
然后返回我们上层目录,编辑我们的CMakeLists.txt:
cmake_minimum_required(VERSION 1.0)
project(TEST)
//指定find_package查找的路径,CMAKE_MODULE_PATH为module模式下邮箱查找的路径
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
add_executable(main main.c)
find_package(Add)
//如果查找完成,找到相应的库就进行头文件和库的引用
if(ADD_FOUND)
target_include_directories(main PRIVATE ${ADD_INCLUDE_DIR})
target_link_libraries(main ${ADD_LIBRARY})
endif(ADD_FOUND)
这个时候整个查找流程就完成了,创建build,进入该目录进行cmake编译,执行是没有问题的。
另外cmake有自带的内部依赖库,我们直接find_package(库名),即可;如果想要引用非内部库,例如grpc的库,需要将grpc使用cmake方式进行编译安装,然后find_package直接查找即可。
以上是find_package的module模式的测试方案,感兴趣的话也可以按照我上面的描述自己写一个config模式的查找测试方案。