cmake:find_package 添加依赖库

1059 篇文章 286 订阅

此文为:轻松入门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])
  • versionEXACT: 都是可选的(versionEXACT不可以同时出现)(对于版本选择,可以参考下面的目录【版本选择】)
    • version指定的是版本,如果指定就必须检查找到的包的版本是否和version兼容。有两种可能的规定形式:
      • 一个格式为major[.minor[.patch[.tweak]]的单一版本。
      • 格式为versionMin…[<]versionMax的版本范围,其中versionMin和versionMax的格式与单个版本相同。默认情况下,包括两个端点。通过指定<,将排除上端点。仅CMake 3.19或更高版本支持版本范围。
    • 如果指定EXACT则表示必须完全匹配的版本而不是兼容版本就可以。
  • 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 模式
    • 所以,一般不会填写这个字段
  • REQUIRED可选字段。
    • 表示一定要找到包,找不到的话就退出并且报错
    • 而如果不指定REQUIRED则cmake会继续执行。
  • COMPONENTSOPTIONAL_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这两个变量赋值

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来禁用。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值