前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[082]破局Cmake中的PRIVATE,PUBLIC,INTERFACE

[082]破局Cmake中的PRIVATE,PUBLIC,INTERFACE

作者头像
王小二
发布2023-08-16 08:50:44
4760
发布2023-08-16 08:50:44
举报

前言

最近看了很多项目的代码,代码是用cmake编译的,由于各种库之间链接关系错综复杂,加上PRIVATE,PUBLIC,INTERFACE属性值,我在添加代码的时候总会遇到稀奇古怪的编译的问题,网上看了很多文章,写的都不是很靠谱,正好看到一个b站视频讲的不错,解决了我很多疑惑,我又有了新的疑惑,折腾了一晚上终于把这个搞明白了,分享给大家。

一、原理

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

代码语言:javascript
复制
    target
    target相应的properties
    可见性

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

代码语言:javascript
复制
    静态库: 使用add_library()
    动态库: 使用add_library() 指定SHARED关键字
    可执行文件: 使用add_executable

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

代码语言:javascript
复制
    编译标志:使用target_complie_option
    预处理宏标志:使用 target_compile_definitions
    头文件目录:使用 target_include_directories
    链接库:使用 target_link_libraries
    链接标志:使用 target_link_options

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

代码语言:javascript
复制
    PRIVATE
    PUBLIC
    INTERFACE

    缺省值为PUBLIC

二、可见性的传递(非常重要)

每一个Target对于自身设置的不同属性处理

代码语言:javascript
复制
    对于private的property,不会传递,只会自己用。
    对于public的property,会传递,也自己用。
    对于interface的property,会传递,但不会自己用

    public和interface的属性是可传递属性

可见性的传递是依靠target_link_libraries,传递的规则如下:

代码语言:javascript
复制
假设如下链接关系
target_link_libraries(B XXX A)// XXX为private,public,interface

    如果XXX为private,A的可传递属性变成B的private property
    如果XXX为public,A的可传递属性变成B的public property
    如果XXX为interface,A的可传递属性变成B的interface property

三、实战1

3.1 最简单的demo

interface_a.h

代码语言:javascript
复制
#ifndef CPP_INTERFACE_A_H
#define CPP_INTERFACE_A_H
 
int addA(int a, int b);
 
#endif //CPP_INTERFACE_A_H

interface_b.h

代码语言:javascript
复制
#ifndef CPP_INTERFACE_B_H
#define CPP_INTERFACE_B_H
 
int addB(int a, int b);
#endif //CPP_INTERFACE_B_H

interface_a.cpp

代码语言:javascript
复制
#include <stdio.h>
#include "interface_a.h"
 
int addA(int a, int b) {
    printf("addA\n");
    return a + b;
}

interface_b.cpp

代码语言:javascript
复制
#include <stdio.h>
#include "interface_b.h"
#include "interface_a.h"
 
int addB(int a, int b)
{
    printf("addB\n");
    return addA(a, b);
}

main.cpp

代码语言:javascript
复制
#include "interface_b.h"
#include <stdio.h>
 
int main()
{
    printf("main\n");
    addB(1, 2);
    return 0;
}

CMakeLists.txt

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.22)
project(CPP)
 
set(CMAKE_CXX_STANDARD 17)
 
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
 
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B A)
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
 
add_executable(CPP main.c)
target_link_libraries(CPP B)

用图来表示代码就如下,CPP调用B中addB,B中的addB调用addA

最后运行的结果

代码语言:javascript
复制
main
addB
addA

这例子简单吧,我们进一步来解读一下CMakeLists.txt,红色为传递过来的属性

查看对应的cmake的编译中间文件,可以进一步验证我们的判断,正好和对应的属性对应。

3.2 main中能否调用addA

可以看到CPP拥有target_include_directories(CPP PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA和target_link_libraries(CPP A)的属性 理论上来说肯定main.cpp可以调用addA

修改main.cpp

代码语言:javascript
复制
#include "interface_b.h"
#include "interface_a.h"
#include <stdio.h>
 
int main()
{
    printf("main\n");
    addA(1, 2);
    addB(1, 2);
    return 0;
}

成功运行

代码语言:javascript
复制
main
addA
addB
addA

3.3 将PUBLIC改成PRIVATE

如果我们对CMakeLists.txt做如下修改,请问上面main.c还能不能正常运行

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.22)
project(CPP)
 
set(CMAKE_CXX_STANDARD 17)
 
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
 
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B PRIVATE A)//改动的地方
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
 
add_executable(CPP main.c)
target_link_libraries(CPP B)

解读一下CmakeLists.txt,红色为传递过来的属性

和3.2中最大的差异就是CPP中includeA没了,那main.c肯定找不到#include "interface_a.h",所以会编译报错找不到头文件interface_a.h 运行结果果然和预料的一样。

代码语言:javascript
复制
/home/kobe/submits/CPP/main.c:2:10: fatal error: interface_a.h: No such file or directory
    2 | #include "interface_a.h"
      |          ^~~~~~~~~~~~~~~

3.4 手动添加includeA

继续修改 CMakeLists.txt

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.22)
project(CPP)
 
set(CMAKE_CXX_STANDARD 17)
 
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
 
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B PRIVATE A)
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
 
add_executable(CPP main.c)
target_link_libraries(CPP B)
target_include_directories(CPP PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)//修改的代码

解读一下CmakeLists.txt,红色为传递过来的属性,紫色是CPP额外加的属性

看到C自身属性添加了includeA,那头文件也有了,链接的时候,CPP链接B,B链接A,最后可以链接到一起,CPP应该可以使用addA了 运行结果果然可以

代码语言:javascript
复制
main
addA
addB
addA

四.实战2

4.1 Interface的作用

修改文件interface_b.cpp,移除B对A的addA的使用

代码语言:javascript
复制
#include <stdio.h>
#include "interface_b.h"
 
int addB(int a, int b)
{
    printf("addB\n");
    return a + b;
}

修改文件cmakelists.txt

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.22)
project(CPP)
 
set(CMAKE_CXX_STANDARD 17)
 
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
 
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B INTERFACE A)
target_include_directories(B PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
 
add_executable(CPP main.c)
target_link_libraries(CPP B)

解读一下CmakeLists.txt,红色为传递过来的属性

因为CPP使用到A的接口和B的接口,B没有使用A的接口,所以按照上面的属性,A,B,CPP三个都可以正常编译运行

代码语言:javascript
复制
main
addA
addB

4.2 add_library(C INTERFACE) -- 比较特殊的用法

修改文件cmakelists.txt

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.22)
project(CPP)
 
set(CMAKE_CXX_STANDARD 17)
 
add_library(A libA/interface_a.c)
target_include_directories(A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includeA)
 
add_library(C INTERFACE)
target_include_directories(C INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/includeB)
 
add_library(B SHARED libB/interface_b.c)
target_link_libraries(B PUBLIC C INTERFACE A)
 
add_executable(CPP main.c)
target_link_libraries(CPP B)

这是种特殊的用法,就是创建一个虚拟的target C,add_library(C INTERFACE)不会编译出任何库和可执行文件,而且C的所有属性必须设置为INTERFACE

解读一下CmakeLists.txt,红色为传递过来的属性

最后也可以完美的运行!

这里C就是一个header-only的库,他的所有属性都是Interface的,不会编译出任何库,唯一作用就是将属性传递给link它的目标。

五、总结

按照1.原理和2.可见性的传递,对应每一个项目,用这样子的表格列出来每一个target对应的属性,也就可以了解到每一个target编译依赖的头文件以及库文件。记住以Target的视角来看待每一个属性,关注两个Target之间的link的属性,以及两个Target之间的属性传递。

六、参考文献

https://chunleili.github.io/cmake/understanding-INTERFACE

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、原理
  • 二、可见性的传递(非常重要)
  • 三、实战1
    • 3.1 最简单的demo
      • 3.2 main中能否调用addA
        • 3.3 将PUBLIC改成PRIVATE
          • 3.4 手动添加includeA
          • 四.实战2
            • 4.1 Interface的作用
              • 4.2 add_library(C INTERFACE) -- 比较特殊的用法
              • 五、总结
              • 六、参考文献
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档