• 【cmake】彻底弄懂cmake中的可见性/传递性 INTEFACE 问题


    参考

    原理

    从 modern cmake(>=3.0) 开始,使用的范式从 director-oriented 转换到了 target-oriented。 这其中最重要的有三个概念:

    1. target
    2. target相应的properties
    3. 可见性

    所谓target就是编译的目标,一般就三种:

    • 静态库: 使用add_library()
    • 动态库: 使用add_library() 指定SHARED关键字
    • 可执行文件: 使用add_executable

    所谓properties就是target的属性,最常见的有以下五种:

    • 编译标志:使用target_complie_option
    • 预处理宏标志:使用 target_compile_definitions
    • 头文件目录:使用 target_include_directories
    • 链接库:使用 target_link_libraries
    • 链接标志:使用 target_link_options

    所谓可见性就是上述这些属性在不同target之间的传递性。有三种:

    • PRIVATE
    • PUBLIC
    • INTERFACE

    上面的这些都是很好理解。但INTERFACE是不好理解的。我们详细讲解。

    INTERFACE

    说到INTERFACE,我们先来看其他两种可见性:PUBLIC和PRIVATE。

    假设我们有目标A和目标B。目标A编译成可执行文件,是我们最终要运行的目标。而目标B则编译成目标A的一个依赖,比如说,静态库。

    PUBLIC的意思就是 目标B的属性 不仅自己使用,还传递给依赖它的目标A。

    PRIVATE的意思就是 目标B的属性 不会传递,只给目标B自己使用。

    而INTERFACE则极为特殊:它的属性都 不会自己使用,只传递给目标A。

    INTERFACE就是纯粹的利他主义,我自己不用,但我甘于奉献,让别人用。INTERFACE只做个纯粹的“接口”。这类似于电话接线员。接线员不能听到任何信息,他们只是把信息转发给别人。

    那么为啥会有这么奇怪的INTERFACE呢?

    这是因为有些目标是没有实质内容的,比如header-only的库。他们没办法编译成静态库。因为它们是没有源码的,只有头文件。一旦编译,就会报错。他们的唯一作用,就是被别人引用。

    我们参考cmake官方文档上的这个例子

    cmake-buildsystem(7)

    add_library(Eigen INTERFACE)
    
    target_sources(Eigen INTERFACE
      FILE_SET HEADERS
        BASE_DIRS src
        FILES src/eigen.h src/vector.h src/matrix.h
    )
    
    add_executable(exe1 exe1.cpp)
    target_link_libraries(exe1 Eigen)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里 Eigen库是个header-only的库。

    首先我们声明它的可见性为INTERFACE。这表示它自己不会使用自己的任何属性。它只是个接线员。(注意:源文件也可以看做是一种"属性",因此,Eigen这个库的所有源文件和头文件都不会被它自己使用,所以它不会编译出任何东西

    使用target_souces来指定它的源文件。

    最后两行,我们编译出exe1这个可执行目标,然后把Eigen链接到exe1上。

    exe1由于引用了Eigen,所以Eigen的所有属性都传递到exe1身上。也就是说,Eigen的所有源码都作为了exe1的源码。

    INTERFACE的实现机制

    我们还可以从它内部的实现机制来理解INTERFACE是什么

    我们这里就以target_include_directories为例,其他的属性同理。

    在cmake内部,有两个变量:INCLUDE_DIRECTORIES 和 INTERFACE_INCLUDE_DIRECTORIES 。

    这两个变量存储的都是头文件目录。和系统的PATH变量类似,里面可以有多个路径,以分号分割。

    但是区别是:INCLUDE_DIRECTORIES 是当前目标搜索的头文件目录,而INTERFACE_INCLUDE_DIRECTORIES 是下一个目标要搜索的头文件目录

    当目标B去搜索头文件的时候,就会在INCLUDE_DIRECTORIES 中搜索。这是简单清晰的。

    而假如A引用了B(或者说目标A依赖于目标B),那么INTERFACE_INCLUDE_DIRECTORIES 中的路径就会赋给目标A的INCLUDE_DIRECTORIES。

    所以,使用PRIVATE PUBLIC和INTERFACE就能控制是否将当前搜索路径传递给下一个目标。

    PRIVATE就是不把当前的INCLUDE_DIRECTORIES 传递给INTERFACE_INCLUDE_DIRECTORIES 。

    PUBLIC就是把当前的INCLUDE_DIRECTORIES 传递给INTERFACE_INCLUDE_DIRECTORIES 。

    INTERFACE就是自己不使用当前的INCLUDE_DIRECTORIES ,但是把当前的INCLUDE_DIRECTORIES 传递给 INTERFACE_INCLUDE_DIRECTORIES 。

    旧 cmake 和modern cmake 的区别 (什么是target-oriented)

    了解旧cmake和modern cmake的区别 是我们理解为什么cmake会进行这样的设计的关键。

    在cmake的早期版本中(2.xx,新版本叫modern cmake)是使用directory-oriented的方式来管理这些属性的传递性的。当你定义了一个属性,就意味着当前文件夹和子文件夹会使用这些属性。

    旧版本的cmake在2015年左右经历过一次大更新,全面升级为modern cmake。所有旧的命令都变为了target-oriented。 以下是两者的对照表。

    在这里插入图片描述

    directory-oriented 方式有许多的不足之处。最大的不足之处在于:你必须按照实际目录的方式来管理cmake的依赖关系。举个例子:假如你有两个平行的目录之间互相依赖,这样就变得十分麻烦。

    但是target-oriented 的方式是可以忽略实际的文件夹层次的。target可以随便放置在任何文件夹当中。只要你设计好target的依赖关系,所有的依赖关系都理清了。

    与面向对象类比

    为什么cmake会提出这样的设计呢?

    这其实是类比面向对象思想的!

    target 类比一个对象。properties就是对象的属性(成员函数、成员变量)。

    在面向对象设计中,public private等关键字即是访问权限控制(相对于属性来说),同时也是继承类型(相对于类来说)的设置

    同理,在cmake中,PRIVATE 、PUBLIC和 INTERFACE即是访问权限控制(相对于属性来说),同时也是继承类型(相对于target来说)的设置。

    举个例子:

    在面向对象中,假设我们有基类B和子类A。

    对于类B来说,假设它具有两个属性。一个是public属性,一个是private属性。

    class A: public B
    {
    };
    
    class B
    {
    public:
        int property1
    private:
        int property2
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当A以public的方式继承B的时候。所有B public的属性都会传递给 A public的属性。所有B private的属性则不会传递。

    这也对照了cmake的设计:你在设置properties的时候,可以设定这个property是private还是 public的。而且,在设置target的时候,也需要设置这个target是private还是public的。

    • 对于private的property,不会传递,只会自己用。

    • 对于private的property,会传递,也自己用。

    • 对于interface的property,会传递,但不会自己用。

    而target则控制整个target的传递性

    • 对于private的target,所有属性不会传递,只会自己用。
    • 对于public的target,public和interface 属性会传递,所有属性也会自己用。
    • 对于interface的target,所有interface 和 public属性会传递,但不会自己用。
  • 相关阅读:
    jar包运行报错jar中没有主清单属性、springGateway访问接口报错302,跳转login接口
    医院如何实现安全又稳定的跨网文件数据交换呢?
    KD树邻域搜索原理,一看就懂,不懂请扇我
    爱上开源之DockerUI-如何实现Web端的Xshell终端模拟器
    “GT/Serdes/高速收发器”相关的FPGA调研
    数据结构:线性表之-队列
    【JavaEE进阶系列 | 从小白到工程师】JavaEE的可变参数使用
    flume采集mysql日志数据发送到kafka
    c语言系统编程十三:Linux线程间的同步与互斥
    Netty 学习(八):新连接接入源码说明
  • 原文地址:https://blog.csdn.net/weixin_43940314/article/details/127750526