此文为:轻松入门cmake系列教程
引入库文件时,我们必须知道头文件的路径还有库文件的路径
在cmake某个程序的时候,经常会提示找不到某个所依赖的库,那么这是时候我们就需要检查我们引入依赖库的路径对不对了, Cmake中一个自动寻找函数find_package()可以帮我们实现这个功能。
实践
例子:Boost
实例
cmake_minimum_required(VERSION 3.5)
# Set the project name
project (third_party_include)
# find a boost install with the libraries filesystem and system
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
# check if boost was found
if(Boost_FOUND)
message ("boost found")
else()
message (FATAL_ERROR "Cannot find Boost")
endif()
# Add an executable
add_executable(${PROJECT_NAME} main.cpp)
# link against the boost libraries
target_link_libraries( ${PROJECT_NAME}
PRIVATE
Boost::filesystem
)
这将从默认系统位置搜索boost
理论
(1)查找一个包
- find_package()函数将从
CMAKE_MODULE_PATH
中的文件夹列表中搜索格式为FindXXX.cmake的CMake模块。find_package的参数的确切格式将取决于你要查找的模块。这通常记录在文件FindXXX.cmake的顶部 - 下面是查找Boost的基本示例:
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
- 参数说明:
- Boost -库的名称。这是用于查找模块文件FindBoost.cmake的一部分。
- 1.46.1 - 要查找的Boost的最低版本。
- REQUIRED - 告诉模块这是必需的,如果失败,则编译通不过。
- COMPONENTS - 要查找的库列表。
(2)检查是否找到该包
- 大多数包含的软件包都会设置一个变量XXX_FOUND,该变量可用于检查该软件包在系统上是否可用。
if(Boost_FOUND)
message ("boost found")
include_directories(${Boost_INCLUDE_DIRS})
else()
message (FATAL_ERROR "Cannot find Boost")
endif()
(3)导出变量
- 在找到包之后,它通常会导出变量,这些变量可以告诉用户在哪里可以找到库、头文件或可执行文件。
- 与XXX_FOUND变量类似,它们是特定于包的,通常记录在FindXXX.cmake文件的顶部。
- 本例中导出的变量包括:
- Boost_INCLUDE_DIRS - Boost头文件的路径
- 在某些情况下,你还可以通过使用ccmake或cmake-gui检查缓存来检查这些变量。
(3)别名/导入目标
- 较新版本的CMake允许你使用导入的别名目标链接第三方库。
- 导入目标是由FindXXX模块导出的只读目标(例如Boost::filesystem)。
- 要包括Boost文件系统,你可以执行以下操作:
target_link_libraries( third_party_include
PRIVATE
Boost::filesystem
)
- 这将自动链接Boost::FileSystem和Boost::System库,同时还包括Boost include目录(即不必手动添加include目录)。
- 关于
Boost::filesystem
,是因为boost库中,所有目标通过使用标识符Boost::加子模块的名字来导出。比如:- Boost::system 对于Boost系统库
- Boost::filesystem 对于文件系统库
- 与下面不同的是,这些目标包含它们的依赖项,也就是不需要手动target_include_directories了
(3)非别名目标
-
虽然大多数现代库使用导入的目标,但并非所有模块都已更新。在库尚未更新的情况下,你通常会发现以下变量可用:
- xxx_INCLUDE_DIRS - 指向库的include目录的变量
- xxx_LIBRARY - 指向库路径的变量.
-
然后,可以将这些文件添加到target_include_directory和target_link_library中:
# Include the boost headers
target_include_directories( third_party_include
PRIVATE ${Boost_INCLUDE_DIRS}
)
# link against the boost libraries
target_link_libraries( third_party_include
PRIVATE
${Boost_SYSTEM_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
)
例子:opencv
find_package( OpenCV REQUIRED )
if (OpenCV_FOUND)
include_directories( ${OpenCV_INCLUDE_DIRS} )
target_link_libraries( ${PROJECT_NAME} ${OpenCV_LIBS} )
endif (OpenCV_FOUND)
find_package(OpenCV
PATHS /opt/opencv
NO_DEFAULT_PATH
REQUIRED)
if (OpenCV_FOUND)
include_directories(${OpenCV_INCLUDE_DIRS})
message( ${OpenCV_LIBS})
else()
message("OpenCV not found, so we won't build the project.")
endif()
例子:bzip2
find_package (BZip2)
if (BZIP2_FOUND)
include_directories(${BZIP_INCLUDE_DIRS})
target_link_libraries (test ${BZIP2_LIBRARIES})
endif (BZIP2_FOUND)
总结:
现在用 XXX 代表要查找的 package 名字
find_pacakge(XXX REQUIRED)
会设置一系列变量。
XXX_FOUND 代表库是否查找成功
XXX_INCLUDE_DIRS 代表头文件的路径
XXX_LIBRARIES 代表库文件的路径
例子:单独引入某些组件
一个库可能由好多个组件构成,cmake 可以单独引入这些组件
find_package(Qt5 5.1.0 COMPONENTS Widgets Xml Sql)
引入了 Qt5 中的 Widgets Xml Sql 组件。
理论
语法
实际上,通过 find_package() 可以顺利查找任何符合 cmake package 标准的外部工程。
一般使用这个就足够了:
find_package(<package>
[version] [EXACT]
[QUIET]
[REQUIRED] [[COMPONENTS] [components...]] [OPTIONAL_COMPONENTS components...]
[MODULE|CONFIG|NO_MODULE]
[NO_POLICY_SCOPE])
其完全的签名(用的很少):
find_package(<PackageName> [version] [EXACT]
[QUIET]
[REQUIRED] [[COMPONENTS] [components...]] [OPTIONAL_COMPONENTS components...]
[MODULE|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])
version
和EXACT
: 都是可选的(version
和EXACT
不可以同时出现)(对于版本选择,可以参考下面的目录【版本选择】)- version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。有两种可能的规定形式:
- 一个格式为major[.minor[.patch[.tweak]]的单一版本。
- 格式为versionMin…[<]versionMax的版本范围,其中versionMin和versionMax的格式与单个版本相同。默认情况下,包括两个端点。通过指定<,将排除上端点。仅CMake 3.19或更高版本支持版本范围。
- 如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。
- version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。有两种可能的规定形式:
QUIET
可选字段,中文意思是“静默”- 表示如果查找失败,不会在屏幕进行输出
- (但是如果指定了REQUIRED字段,则QUIET无效,仍然会输出查找失败提示语)
MODULE
可选字段。- find_package有两种方法来查找包(具体参见【原理】目录)
- 如果不填写MODULE字段也不指定CONFIG和NO_MODULE字段
- 那么Module模式查找,如果没有找到包,那么就使用Config模式再次搜索
- 如果填写了MODULE字段(此时不能填写CONFIG和NO_MODULE字段)
- 那么就只在Module模式查找,如果查找失败并不会使用Config模式再次搜索
- 如果填写了CONFIG或者NO_MODULE(这两个是同义词)(此时不能填写MODULE)
- 那么将强制采用纯CONFIG模式
- 问题:如果想要现以Config模式查找然后以Module模式查找,那么可以设置 。此时:
- 可以CMAKE_FIND_PACKAGE_PREFER_CONFIG 设置为 true (v3.15+) 来告诉 find_package 先尝试 CONFIG 模式
- 如果不填写MODULE字段也不指定CONFIG和NO_MODULE字段
- 所以,一般不会填写这个字段
- find_package有两种方法来查找包(具体参见【原理】目录)
REQUIRED
可选字段。- 表示一定要找到包,找不到的话就退出并且报错
- 而如果不指定REQUIRED则cmake会继续执行。
COMPONENTS
与OPTIONAL_COMPONENTS
:可选字段(这两个)COMPONENTS
意思是必选组件- 表示查找的包中必须要找到的组件(components)
- 如果有任何一个找不到就算失败,类似于REQUIRED,导致cmake停止执行,即退出报错
OPTIONAL_COMPONENTS
:可选字段,意思是可选组件- 可选的组件,找不到也不会让cmake停止执行。
- NO_POLICY_SCOPE选项的讨论,请参阅cmake_policy()命令文档。
待本语句执行完成之后,将会设置值< PackageName >_FOUND。< PackageName >_FOUND将被设置为指示是否找到了包。当找到包时,特定于包的信息会通过包本身记录的变量和Imported Targets提供。
版本选择
如果填写了[version]或者EXACT,那么将会检查包的版本。
- CMake没有为版本号的含义建立任何约定。包版本号由包本身提供的“版本”文件检查。对于候选包配置文件< config-file>.cmake,相应的版本文件位于其旁边,并命名为< config-file >-version.cmake或 < config-file>version.cmake。
- 如果没有此类版本文件可用,则假定配置文件与任何请求的版本不兼容。可以使用CMakePackageConfigHelpers模块创建包含通用版本匹配代码的基本版本文件。
- 找到版本文件后,将加载该文件以检查请求的版本号。版本文件加载到嵌套范围中,其中定义了以下变量:
- PACKAGE_FIND_NAME:即 < PackageName >
- 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。
原理
首先,cmake本身不提供任何搜索库的便捷方法,所有搜索库并给变量赋值的操作必须由cmake代码完成,比如FindXXX.cmake和XXXConfig.cmake。只不过,库的作者通常会提供这两个文件,以方便使用者调用。
find_package采用两种模式搜索库(包):
Module模式(模块模式):
- 在Module模式下,CMake搜索一个名为Find< PackageName >. CMake的文件。该文件一般会定义头文件路径、lib路径等。
- 此时:
- 会先去搜索CMAKE_MODULE_PATH指定路径下的FindXXX.cmake文件,如果找不到,那么就去在CMake安装提供的Find Modules中搜索。
- CMAKE_MODULE_PATH,一般是当前项目里面的Module文件夹,这个文件夹中会提供一个FindXXX.cmake
- CMake安装提供的Find Modules,一般是系统路径/usr/local/share/cmake-x.y/Modules/FindXXX.cmake
- 如果找到了FindXXX.cmake文件,那么将会执行这个文件,以便能够找到XXX库
- FindXXX.cmake文件除了会查找库之外,还会给XXX_INCLUDE_DIRS和XXX_LIBRARIES这两个变量赋值
- 会先去搜索CMAKE_MODULE_PATH指定路径下的FindXXX.cmake文件,如果找不到,那么就去在CMake安装提供的Find Modules中搜索。
Config模式(配置模式)
- 先创建一个名为< PackageName >_DIR的缓存项来保存包含该文件的目录(默认情况下,该命令搜索名称为< PackageName >的包。如果给出NAMES选项,则使用其后的名称,而不是< PackageName >。)
- 搜索XXX_DIR指定路径下的XXXConfig.cmake文件
- XXXConfig.cmake名为< PackageName >Config.cmake或< lower case package name >-Config.cmake
- 可以使用CONFIGS选项提供可能的配置文件名的替换集。
- 找到XXXConfig.cmake文件之后,将执行该文件从而找到XXX库。
- 其中具体查找库并给XXX_INCLUDE_DIRS和XXX_LIBRARIES两个变量赋值的操作由XXXConfig.cmake模块完成。
- 对于可能没有***.cmake和***Config.cmake的库文件,可以直接找到其头文件和库文件所在文件夹,直接进行路径赋值:
SET(LAPACK_DIR /usr/local/lib/)
SET(LAPACK_INCLUDE_DIRS /usr/local/include)
SET(LAPACK_LIBRARIES /usr/local/lib)
---
- XXXConfig.cmake文件中内容一般有包内容的位置。
- 配置文件的完整路径存储在cmake变量< PackageName >_CONFIG中。
- CMake在搜索具有适当版本的软件包安装时考虑的所有配置文件都存储在CMake变量< PackageName >_CONSIDERED_CONFIGS中,相关版本存储在< PackageName >_CONSIDERED_VERSIONS.中。
如果无法找到包配置文件,除非指定了QUIET参数,否则CMake将产生描述问题的错误。如果指定了REQUIRED且没有找到包,则会生成致命错误,配置步骤会停止执行。如果< PackageName >_DIR已经被设置为一个不包含配置文件的目录,CMake将忽略它并从头开始搜索。
搜索过程
CMake为包构造一组可能的安装前缀。在每个前缀下搜索配置文件的多个目录。下面的表显示了搜索的目录。每一项都适用于遵循Windows (W)、UNIX (U)或Apple (A)约定的安装树:
<prefix>/ (W)
<prefix>/(cmake|CMake)/ (W)
<prefix>/<name>*/ (W)
<prefix>/<name>*/(cmake|CMake)/ (W)
<prefix>/(lib/<arch>|lib*|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/ (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/ (W/U)
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ (W/U)
在支持macOS框架和BUNDLE的系统上,会在以下目录中搜索包含配置文件的框架或应用包:
<prefix>/<name>.framework/Resources/ (A)
<prefix>/<name>.framework/Resources/CMake/ (A)
<prefix>/<name>.framework/Versions/*/Resources/ (A)
<prefix>/<name>.framework/Versions/*/Resources/CMake/ (A)
<prefix>/<name>.app/Contents/Resources/ (A)
<prefix>/<name>.app/Contents/Resources/CMake/ (A)
在所有情况下,< name >被视为不区分大小写,并对应于任何指定的名称(< PackageName >或NAMES 给出的名称)。
如果设置了CMAKE_LIBRARY_ARCHITECTURE变量,那么带有lib/< arch >的路径将被启用。lib*包含一个或多个值lib64、lib32、libx32或Lib(按此顺序搜索)。
- 如果FIND_LIBRARY_USE_LIB64_PATHS属性设置为TRUE,则在64位平台上搜索具有lib64的路径。
- 如果FIND_LIBRARY_USE_LIB32_PATHS属性设置为TRUE,则在32位平台上搜索具有lib32的路径。
- 如果FIND_LIBRARY_USE_LIBX32_PATHS属性设置为TRUE,则使用x32 ABI在平台上搜索libx32的路径。
- 总是搜索lib路径。
如果指定了PATH_SUFFIXES ,则将后缀逐个附加到每个(W)或(U)目录条目。
这组目录旨在与在安装树中提供配置文件的项目协同工作。上面标有(W)的目录用于Windows上的安装,其中前缀可能指向应用程序安装目录的顶部。标记为(U)的用于UNIX平台上的安装,其中前缀由多个包共享。这只是一个约定,所以所有的(W)和(U)目录仍然在所有平台上搜索。标有(A)的目录用于在Apple平台上安装。CMAKE_FIND_FRAMEWORK和CMAKE_FIND_APPBUNDLE变量决定优先次序。
安装前缀集是通过以下步骤构造的。如果指定了NO_DEFAULT_PATH,则启用所有NO_*选项。
- 3.12新版功能:搜索< PackageName >_ROOT CMake变量和< PackageName >_ROOT环境变量中指定的路径,其中< PackageName >是要找到的包。包的根变量被维护为一个堆栈,因此如果从一个find模块中调用,来自父模块的find模块的根路径也将在当前包的路径之后进行搜索。如果传递了NO_PACKAGE_ROOT_PATH,或者将CMAKE_FIND_USE_PACKAGE_ROOT_PATH设置为FALSE,则可以跳过这个步骤。看到CMP0074政策。
- 在cmake特定缓存变量中指定的搜索路径。这些将用于命令行中,且值为-DVAR=value。这些值被解释为分号分隔的列表。如果传入了NO_CMAKE_PATH,或者将CMAKE_FIND_USE_CMAKE_PATH设置为FALSE,则可以跳过这个步骤
- 在cmake特定环境变量中指定的搜索路径。这些是在用户的shell配置中设置的,因此使用主机的本机路径分隔符(;在Windows和:在UNIX上)。如果传入了NO_CMAKE_ENVIRONMENT_PATH,或者将CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH设置为FALSE,则可以跳过这个步骤
- 由提示选项指定的搜索路径。这些路径应该是由系统自省计算出来的,比如已经找到的另一个项目的位置提供的提示。硬编码猜测应该用路径选项指定。
- 搜索标准的系统环境变量。如果传递了NO_SYSTEM_ENVIRONMENT_PATH,或者将CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH设置为FALSE,则可以跳过此步骤。以/bin或/sbin结尾的路径条目会自动转换为它们的父目录
- 搜索路径存储在CMake用户包注册表。如果传递了NO_CMAKE_PACKAGE_REGISTRY变量,或者将变量CMAKE_FIND_USE_PACKAGE_REGISTRY设置为FALSE,或者将已弃用的变量CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY设置为TRUE,则可以跳过这个变量。关于用户包注册表的详细信息,请参见cmake-packages(7)手册。
- 搜索cmake变量在平台文件中定义的当前系统。如果传入了NO_CMAKE_SYSTEM_PATH,或者将CMAKE_FIND_USE_CMAKE_SYSTEM_PATH设置为FALSE,则可以跳过这个步骤
- 检索路径存储在CMake系统包注册表。如果传递了NO_CMAKE_SYSTEM_PACKAGE_REGISTRY变量,或者将CMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY变量设置为FALSE,或者将CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY变量设置为TRUE,则可以跳过这个变量。
- 搜索由PATHS选项指定的路径。这些通常是硬编码的猜测。
3.16新版功能:添加了CMAKE_FIND_USE__PATH变量来全局禁用各种搜索位置。
CMake变量CMAKE_FIND_ROOT_PATH指定一个或多个要添加到所有其他搜索目录的目录。这有效地“重新根”了给定位置下的整个搜索。CMAKE_STAGING_PREFIX的后代路径将被排除在重新生根之外,因为该变量始终是主机系统上的路径。默认情况下,CMAKE_FIND_ROOT_PATH为空。
CMAKE_SYSROOT变量还可以用来指定一个目录作为前缀。设置CMAKE_SYSROOT还有其他效果。有关该变量的更多信息,请参见文档。
当交叉编译指向目标环境的根目录时,这些变量特别有用,CMake也会在那里进行搜索。默认情况下,首先搜索CMAKE_FIND_ROOT_PATH中列出的目录,然后搜索CMAKE_SYSROOT目录,然后搜索非根目录。默认行为可以通过设置CMAKE_FIND_ROOT_PATH_MODE_PACKAGE进行调整。这个行为可以在每次调用的基础上使用选项手动重写:
- CMAKE_FIND_ROOT_PATH_BOTH:按上面描述的顺序进行搜索。
- NO_CMAKE_FIND_ROOT_PATH:不要使用CMAKE_FIND_ROOT_PATH变量。
- ONLY_CMAKE_FIND_ROOT_PATH:只搜索重新根目录和CMAKE_STAGING_PREFIX下面的目录。
默认搜索顺序被设计为最特定于最不特定于常见用例。项目可以通过多次调用该命令并使用NO_*选项来覆盖该命令:
find_package (<PackageName> PATHS paths... NO_DEFAULT_PATH)
find_package (<PackageName>)
一旦其中一个调用成功,结果变量将被设置并存储在缓存中,以便不再进行调用搜索。
默认情况下,存储在结果变量中的值将是找到文件的路径。在调用find_package之前,CMAKE_FIND_PACKAGE_RESOLVE_SYMLINKS变量可以设置为TRUE,以便解析符号链接并存储文件的真实路径。
每个非必需的find_package调用都可以通过将CMAKE_DISABLE_FIND_PACKAGE_变量设置为TRUE来禁用。