• CMake教程系列-03-依赖管理


    1.引入

    假设我需要使用第三方库去做一些事情,如何在配置中体现这种依赖关系?

    • 如果这个第三方库在我们开发过程中更新了自己的API,如何保证我们的程序不受第三方库的API变更影响?而cmake可以保证这两项。

    2.查找依赖

    ind_package旨在使用预先设置的配置文件来查找依赖项,

    • 其函数原型为:
    find_package(PACKAGE_NAME_CASE_SENSITIVE
                 [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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    主要用的参数如下:
    PACKAGE_NAME_CASE_SENSITIVE

    • 这个名称表示了需要查找的库的名称,大小写敏感,与之对应的是调用了包含此名称的配置文件。如果使用该函数始终找不到库,请仔细查看文件名称中包含的库名称大小写。

    version

    • 依赖的版本号。
    • 如果依赖的配置同时提供了版本文件,则会使用该值对比配置中的版本而确定是否可以使用。

    EXACT

    • 该关键字声明了版本号必须严格对应配置中的版本号后,此依赖才可以被使用。大一点小一点均不可。

    QUIET

    • 该关键字关闭了查找信息(不包含查找失败/错误信息)的输出。

    CONFIG

    • 该关键字声明了需要使用依赖项通过自己的cmake代码,使用cmake自动生成的配置文件,入口配置文件名称一般为 <LOW_CASE_PACKAGE_NAME>-config.cmake 或 <ALL_CASE_PACKAGE_NAME>Config.cmake 。
    • 请注意一个是使用全小写的依赖名称,而另一个使用包含大小写的依赖项名称。
      这一点很多人忽略了,导致了自己项目中始终找不到配置文件,即使查找路径正确。
    • 额外使用 version 关键字时,cmake会调用文件 <LOW_CASE_PACKAGE_NAME>-config-version.cmake 或 <ALL_CASE_PACKAGE_NAME>ConfigVersion.cmake 来查看版本是否匹配。
    • 由于是通过依赖项的cmake配置自动生成的,一般情况下不会过期或出现错误。所以我推荐使用此模式。
    • 需要注意的几点:
    <PACKAGE_NAME>_DIR 
    你可以在 find_package 之前直接设置这个宏至依赖包路径下来告诉cmake需要查找的路径。对于一些特殊的场景有奇效。
    
    CMAKE_PREFIX_PATH 
    cmake统一使用该list中的路径来查找所有依赖项。所以如果你把依赖项都放在一起,请将他们所在的根目录 APPEND 或 REPEND 到此list中。
    
    CMAKE_FRAMEWORK_PATH 
    此路径为MacOS专用,存放系统 framework 的依赖项位置。
    
    CMAKE_APPBUNDLE_PATH 
    此路径为MacOS专用,存放 app bundle的位置。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 对于上述前两个变量/列表而言,cmake会在每一条的以下扩展路径中查找配置文件:
    <EACH_PATH>/ 根目录
    <EACH_PATH>/cmake
    <EACH_PATH>/<PACKAGE_NAME>
    <EACH_PATH>/<PACKAGE_NAME>/cmake
    <EACH_PATH>/<lib/share>/cmake
    <EACH_PATH>/<lib/share>/<PACKAGE_NAME>
    <EACH_PATH>/<lib/share>/<PACKAGE_NAME>/cmake
    <EACH_PATH>/<PACKAGE_NAME>/<lib/share>/<PACKAGE_NAME>
    <EACH_PATH>/<PACKAGE_NAME>/<lib/share>/<PACKAGE_NAME>/cmake
    
    其实通用做法很简单:将你需要提供的配置文件扔到 root/share/<PACKAGE_NAME> 或 root/lib/cmake/<PACKAGE_NAME> 中就行了。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 使用该模式时,一般情况下会提供依赖项对应的 target 名称(包含或不包含namespace)以供使用。
      极少数情况下也提供依赖项对应的各种宏,这主要是为了兼容预先cmake提供的MODULE模式文件使用方式(设置CMAKE_FIND_PACKAGE_PREFER_CONFIG 而无需修改任何 find_package 代码即可优先使用 CONFIG 模式)。

    • 当未使用 REQUIRED 关键字时,我们可以使用 <PACKAGE_NAME>_FOUND 来判断是否找打了合适的依赖项。

    该关键字不可与下一关键字MODULE同时声明。
    MODULE

    • 该关键字声明了需要使用 cmake官方 或 依赖项本身 或 本项目 提供的 Find<PACKAGE_NAME>.cmake 文件。
    • 使用了上一条相同的查找规则。需要特别注明的是,它提供了一个额外的查找路径 CMAKE_MODULE_PATH ,你可以将自己写好的配置文件路径 PREPEND 到这个 list中来优先使用你的配置文件。
    • 额外使用 version 关键字时,cmake会使用依赖项中某些文件(例如头文件或README)中存在的版本号对应配置文件中的宏 <PACKAGE_NAME>_VERSION 来查看版本是否匹配。
    • 使用该模式时,一般情况下会提供依赖项对应的宏以供使用:
    <PACKAGE_NAME>_INCLUDE_DIRS / <PACKAGE_NAME>_INCLUDE_DIR 头文件路径
    <PACKAGE_NAME>_LIBRARIES / <PACKAGE_NAME>_LIBRARY 库名称(包含路径和配置表达式)
    <PACKAGE_NAME>_VERSION / <PACKAGE_NAME>_VERSION_STRING 完整版本号
    <PACKAGE_NAME>_FOUND 是否查找到该依赖
    
    • 1
    • 2
    • 3
    • 4
    • 而在少数情况下也提供 target 名称以供下游使用。
      注意:在没有声明上一个及这个关键字时,cmake会根据 CMAKE_FIND_PACKAGE_PREFER_CONFIG 的设置来判断优先使用 CONFIG 模式或 MODULE 模式。

    NAMES

    • 将此关键字后面的名称替换PACKAGE_NAME_CASE_SENSITIVE来查找配置文件,并提供此名称对应的宏。
    • 由于带“S”,你可以多写几个来匹配。

    PATHS

    • 声明查找配置文件的根目录,相当于 CMAKE_PREFIX_PATH, 不过是额外的。

    PATH_SUFFIXES

    • 声明查找根目录下的额外扩展相对路径。

    NO_DEFAULT_PATH

    • 不使用默认路径查找,也就是说不使用上面列举的以 CMAKE_ 为前缀的路径,而必须设置<PAKCAGE_NAME>_DIR 或 PATHS 来查找。
    • 这一项在禁用cmake自带的MODULE文件时有奇效。

    NO_SYSTEM_PATH

    • 不在系统路径中查找。

    3.find_library

    这是原始的cmake查找依赖方式:直接查找依赖项库文件,一般与下面的一项同时使用。

    • 其函数原型为:
    find_library (
              <LIBRARY_NAME>
              name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
              [HINTS [path | ENV var]... ]
              [PATHS [path | ENV var]... ]
              [PATH_SUFFIXES suffix1 [suffix2 ...]]
              [DOC "cache documentation string"]
              [NO_CACHE]
              [REQUIRED]
              [NO_DEFAULT_PATH]
              [NO_PACKAGE_ROOT_PATH]
              [NO_CMAKE_PATH]
              [NO_CMAKE_ENVIRONMENT_PATH]
              [NO_SYSTEM_ENVIRONMENT_PATH]
              [NO_CMAKE_SYSTEM_PATH]
              [CMAKE_FIND_ROOT_PATH_BOTH |
               ONLY_CMAKE_FIND_ROOT_PATH |
               NO_CMAKE_FIND_ROOT_PATH]
             )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这与上面的 find_package 略有不同,
    不同之处:

    • LIBRARY_NAME 由于直接查找库文件而不是查找配置文件,此名称仅作为结果中宏的前缀使用。
    • NAMES 此项声明了库文件的名称。值得注意的是,在UNIX-style系统中,自动添加“lib”作为库名称的前缀。
    • NAMES_PER_DIR 一个名称遍历查找一次,再用另一个名称遍历查找一次。而不是根据路径使用多个名称遍历。

    查找完成后:

    • 如果查找到,则会设置 LIBRARY_NAME 为查找到的库文件的名称(包含全路径)。
    • 如果没有查找到,则会将 LIBRARY_NAME 设置为 <LIBRARY_NAME>-NOTFOUND 。

    所以这里和 find_package 又有不同,我们应当使用以下代码判断是否查找到:

    if (PACKAGE_NAME MATCHES "-NOTFOUND")
        message(FATAL_ERROR "${PACKAGE_NAME} not found!")
    endif()
    
    • 1
    • 2
    • 3

    4.find_path

    这个函数一般是查找头文件或其他的 非库文件 且 非可执行程序。

    • 其函数原型为:
    find_path (
              <FILE_NAME>
              name | NAMES name1 [name2 ...]
              [HINTS [path | ENV var]... ]
              [PATHS [path | ENV var]... ]
              [PATH_SUFFIXES suffix1 [suffix2 ...]]
              [DOC "cache documentation string"]
              [NO_CACHE]
              [REQUIRED]
              [NO_DEFAULT_PATH]
              [NO_PACKAGE_ROOT_PATH]
              [NO_CMAKE_PATH]
              [NO_CMAKE_ENVIRONMENT_PATH]
              [NO_SYSTEM_ENVIRONMENT_PATH]
              [NO_CMAKE_SYSTEM_PATH]
              [CMAKE_FIND_ROOT_PATH_BOTH |
               ONLY_CMAKE_FIND_ROOT_PATH |
               NO_CMAKE_FIND_ROOT_PATH]
             )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    一般情况下,由于需要cmake表达式来让cmake判断使用哪个配置的库,我们通常这么写:

    find_path(<PACKAGE_NAME>_INCLUDE_DIR NAMES header.h PATH_SUFFIXES include/...)
    
    find_library(<PACKAGE_NAME>_LIBRARY_RELEASE NAMES name1 name2)
    find_library(<PACKAGE_NAME>_LIBRARY_DEBUG NAMES name1d name2d)
    select_library_configurations(<PACKAGE_NAME>)
    ...
    target_*(target_name ${<PACKAGE_NAME>})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5.find_program

    这个函数专门用于查找可执行程序。

    • 其函数原型为:
    find_program (
              <VAR>
              name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
              [HINTS [path | ENV var]... ]
              [PATHS [path | ENV var]... ]
              [PATH_SUFFIXES suffix1 [suffix2 ...]]
              [DOC "cache documentation string"]
              [NO_CACHE]
              [REQUIRED]
              [NO_DEFAULT_PATH]
              [NO_PACKAGE_ROOT_PATH]
              [NO_CMAKE_PATH]
              [NO_CMAKE_ENVIRONMENT_PATH]
              [NO_SYSTEM_ENVIRONMENT_PATH]
              [NO_CMAKE_SYSTEM_PATH]
              [CMAKE_FIND_ROOT_PATH_BOTH |
               ONLY_CMAKE_FIND_ROOT_PATH |
               NO_CMAKE_FIND_ROOT_PATH]
             )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.使用依赖

    我们可以将查找到的依赖项用于多个函数中,例如添加头文件路径,添加链接库,添加编译选项等。

    对于不同的查找方式,配置文件或cmake提供了不同的使用方式:

    • 例如 <PACKAGE_NAME>_INCLUDE_DIRS 和 <PACKAGE_NAME>_LIBRARIES 这种方式。
    • 对于头文件来讲,直接加到include_directories中就好了。而对于库来讲,则复杂点:
    • 由于不能混合使用debug库及release库,cmake必须明确知道在不同配置下使用哪个库。所以宏中一般使用到了cmake表达式来处理这种情况
    • 比如:所以我们在写配置时,尽量将debug和release库均查找后使用 select_library_configurations 来生成表达式以便不同配置下使用。
    $<$<CONFIG:DEBUG>:library.lib> $<${NOT:$<CONFIG:DEBUG>>:libraryd.lib>
    
    • 1

    target

    • target 就简单的多了,因为它是一个object,cmake函数可以轻松提取 target 包含的需要使用的属性来使用。
    • 当然,target 包含非namespace与namespace两种形式,不过使用上没区别。

    内部依赖

    • 对于大型项目来说,我们可能需要声明不同的target,并将这些target建立依赖关系。所以我们通常使用以下方式:
    add_dependencies
    
    add_dependencies(<target> [<target-dependency>]...)
    这个很简单,向前者添加依赖项(后者),可以添加多个。在编译或某些配置时,优先处理后者。
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    【微服务部署】二、Jenkins一键打包部署SpringBoot应用步骤详解
    uview+uniapp+springboot 实现小程序上传图片并回显
    【二十】【QT开发应用】listwidget右键菜单和删除item
    十三、Django之添加用户(原始方法实现)
    Flowable工作流基础篇
    质数判定与质数表的求解
    92. 反转链表 II
    JavaScript 从入门到熟悉
    python永久配置pip下载镜像源方法(window版本)
    微信小程序文件上传wx.uploadFile
  • 原文地址:https://blog.csdn.net/u011436427/article/details/125466888