• 【cmake】利用cmake的add_subdirectory管理内部和外部库


    利用cmake的add_subdirectory管理内部和外部库

    问题描述

    内容参考自 cmake菜谱

    请从 这里下载源码

    假如你有一个稍微大一点的项目,那么就需要引用许多外部库。同时,你还有许多自己写的内部库。最后,你把他们都链接到自己的可执行文件上。

    你的项目结构可能如下所示

    • 根目录
      • 自己的项目
        • 内部库A
        • 内部库B
      • 外部库

    实践

    项目结构

    为了更好的说明,我们把所有的CMakeLists.txt文件都清理干净,然后从头开始写。

    目录如下

    .
    ├── external
    │   ├── conversion.cpp
    │   └── conversion.hpp
    ├── src
    │   ├── evolution
    │   │   ├── evolution.cpp
    │   │   └── evolution.hpp
    │   ├── initial
    │   │   ├── initial.cpp
    │   │   └── initial.hpp
    │   ├── io
    │   │   ├── io.cpp
    │   │   └── io.hpp
    │   ├── main.cpp
    │   └── parser
    │       ├── parser.cpp
    │       └── parser.hpp
    └── tests
        ├── catch.hpp
        └── test.cpp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    解释:

    • external存放外部库
    • tests存放测试 使用catch2测试
    • src是本项目源码目录
      • evolution/initial/io/parser都是内部库
      • main 可执行目标

    我们从上到下依次来建立CMakeLists.txt以管理项目

    根目录 CMakeLists.txt

    在根目录中建立 CMakeLists.txt

    内容如下

    # cmake最低版本
    cmake_minimum_required(VERSION 3.20)
    # 本项目名
    project(organize-3rd-parties)
    
    # 设置C++17标准,需要满足标准,不用C++拓展语法
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    set(CMAKE_CXX_EXTENSIONS OFF)
    
    # 进入src文件夹下面的CMakeLists.txt
    add_subdirectory(src)
    
    # 进入external文件夹下面的CMakeLists.txt
    add_subdirectory(external)
    
    # # 开启测试
    # enable_testing()
    # # 进入 tests 文件夹下面的CMakeLists.txt
    # add_subdirectory(tests)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    我们这里先暂时把测试的部分注释掉。只关注管理内部和外部库。

    src目录 CMakeLists.txt

    在src中建立 CMakeLists.txt

    内容如下

    # 将main.cpp编译成可执行文件
    add_executable(main main.cpp)
    
    # 进入各个内部库子目录
    add_subdirectory(evolution)
    add_subdirectory(initial)
    add_subdirectory(io)
    add_subdirectory(parser)
    
    # 链接外部库和内部库到可执行目标
    target_link_libraries(main 
        PRIVATE         #不会传播
            evolution   #内部库
            initial     #内部库
            io          #内部库  
            parser      #内部库
            conversion  #外部库
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    内部和外部库目录的 CMakeLists.txt

    以下几个库的管理方式都是一样的
    src\evolution
    src\initial
    src\io
    src\parser
    external\CMakeLists.txt

    我们举一个例子即可

    src\evolution\CMakeLists.txt

    # 编译静态库,暂不添加任何源码。
    add_library(evolution "")
    
    # 随后用target_sources添加源码。
    # 这种方式更好,因为可以更细粒度地访问权限。
    # 例如对cpp文件就是PRIVATE,而hpp则是PUBLIC
    target_sources(evolution
    PRIVATE
        # 注:${CMAKE_CURRENT_LIST_DIR}代表当前的CMakeLists.txt文件所在的绝对路径。
        ${CMAKE_CURRENT_LIST_DIR}/evolution.cpp
    PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/evolution.hpp
    )
    
    # 增加头文件目录
    target_include_directories(evolution
    PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对其他几个库同理,只要更改名字即可

    注意:即使是外部库,也是同样的做法。对cmake来说,没有任何的区别。

    构建运行结果(无测试)

    cmake是一个元构建系统。就是说:它先生成对应系统下的原生构建工具(如VS的MSBuild 或者Linux的makefile)所需要的构建文件,然后再运行这些原生构建工具进行构建。

    最基本的cmake使用分为两步

    1. configure(配置): 用于生成VS的sln文件(Windows),或者makefile文件(Linux/Mac)
    2. build(构建): 用于生成可执行文件和静态库,相当于VS点击build,或者makefile运行make命令

    最后运行生成的可执行文件即可

    我们这里采用的是Win10 + VS2022

    总结起来就两步

    cmake -B build
    cmake --build build --config=Release
    
    • 1
    • 2

    下面,我们详细观察输出结果

    首先进行配置

    PS E:\learn\cmake> cmake -B build
    -- Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.19043.
    -- Configuring done
    -- Generating done
    -- Build files have been written to: E:/learn/cmake/build
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后构建

    注意:假如你在Windows下使用VS,那么**–config=Release是必要的**。因为默认会进行Debug模式的构建。
    而且实际上VS会把所有的四种变体都生成,因此你在配置步骤指定CMAKE_BUILD_TYPE是没有用的!

    PS E:\learn\cmake> cmake --build build --config=Release
    MSBuild version 17.3.1+2badb37d1 for .NET Framework
      Checking Build System
      Building Custom Rule E:/learn/cmake/external/CMakeLists.txt
      conversion.cpp
      conversion.vcxproj -> E:\learn\cmake\build\external\Release\conversion.lib
      Building Custom Rule E:/learn/cmake/src/evolution/CMakeLists.txt
      evolution.cpp
      evolution.vcxproj -> E:\learn\cmake\build\src\evolution\Release\evolution.lib
      Building Custom Rule E:/learn/cmake/src/initial/CMakeLists.txt
      initial.cpp
      initial.vcxproj -> E:\learn\cmake\build\src\initial\Release\initial.lib
      Building Custom Rule E:/learn/cmake/src/io/CMakeLists.txt
      io.cpp
      io.vcxproj -> E:\learn\cmake\build\src\io\Release\io.lib
      Building Custom Rule E:/learn/cmake/src/parser/CMakeLists.txt
      parser.cpp
      parser.vcxproj -> E:\learn\cmake\build\src\parser\Release\parser.lib
      Building Custom Rule E:/learn/cmake/src/CMakeLists.txt
      main.cpp
      main.vcxproj -> E:\learn\cmake\build\src\Release\main.exe
      Building Custom Rule E:/learn/cmake/CMakeLists.txt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    可执行未见位于
    build\src\Release\main.exe

    运行

    PS E:\learn\cmake\build\src\Release> .\main.exe 10 10 10
    length: 10
    number of steps: 10
    rule: 10
         *
        *
       *
      *
     *
    *
             *
            *
           *
          *
         *
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    至此,我们已经学会了如何使用add_subdirectory来管理内部和外部库。

    下面是加餐。

    补充1:测试

    首先我们要开启测试:

    在根目录CMakeLists.txt 把注释的部分解除注释

    # 开启测试
    enable_testing()
    # 进入 tests 文件夹下面的CMakeLists.txt
    add_subdirectory(tests)
    
    • 1
    • 2
    • 3
    • 4

    我们采用catch2这个测试框架。你可以从这里找到catch2源码

    由于catch2是个header-only的库(最新版已经不是了),所以你只需要把头文件所有源码全部复制下来,然后新建一个catch.hpp保存即可。保存的文件建议放在tests文件夹下,这样我们写的测试用例直接include catch.hpp即可使用。

    新建 tests\CMakeLists.txt

    # 编译测试用例为可执行文件
    add_executable(test1
        test.cpp
    )
    
    # 把待测试的库连接到测试用例上
    target_link_libraries(test1 evolution)
    
    # 告知cmake增加了测试,名为my_test,执行test1这个可执行文件
    add_test(
        NAME my_test
        COMMAND test1
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    构建和运行测试

    cmake -B build
    cmake --build build --config=Release
    cd build
    ctest -C Release
    
    • 1
    • 2
    • 3
    • 4

    注意:

    1. ctest 是cmake运行测试的一个工具。这个工具会去寻找add_test来查看测试在哪。
    2. ctest 也需要编译之后才能用。它其实本质上和其他的目标没什么不同。
    3. 需要进入build目录才能使用ctest命令,否则会报错找不到测试用例。
    4. 在Windows下需要指定-C命令,同样是因为VS必须指定构建的变体才行。

    输出结果

    Test project E:/learn/cmake/build
        Start 1: my_test
    1/1 Test #1: my_test ..........................   Passed    0.02 sec
    
    100% tests passed, 0 tests failed out of 1
    
    Total Test time (real) =   0.03 sec
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    补充2:把输出的exe放到根目录的bin目录下

    默认情况下输出的exe在
    build\src\Release\main.exe

    这是在build目录下模拟了源文件目录的结构。

    如果你希望把exe文件 或者 库文件直接输出到根目录的bin 或者 lib目录下,你可以把根目录下面的CMakeLists.txt增加如下3行:

    # cmake最低版本
    cmake_minimum_required(VERSION 3.20)
    # 本项目名
    project(organize-3rd-parties)
    
    # 设置C++17标准,需要满足标准,不用C++拓展语法
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    set(CMAKE_CXX_EXTENSIONS OFF)
    
    # 静态库输出目录
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/lib)
    # 动态库输出目录
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/bin)
    # 可执行文件输出目录
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/bin)
    
    
    # 进入src文件夹下面的CMakeLists.txt
    add_subdirectory(src)
    
    # 进入external文件夹下面的CMakeLists.txt
    add_subdirectory(external)
    
    # 开启测试
    enable_testing()
    # 进入 tests 文件夹下面的CMakeLists.txt
    add_subdirectory(tests)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    实际上,就是利用了几个cmake的内置变量

    变量名含义
    CMAKE_ARCHIVE_OUTPUT_DIRECTORY静态库输出目录
    CMAKE_LIBRARY_OUTPUT_DIRECTORY动态库输出目录
    CMAKE_RUNTIME_OUTPUT_DIRECTORY可执行文件输出目录

    更多内置变量含义请参考我的博文。我会持续更新。

    输出结果:

    在构建完成之后会出现bin和lib这两个目录
    在这里插入图片描述

  • 相关阅读:
    深入了解Kubernetes(k8s):安装、使用和Java部署指南(持续更新中)
    layui 富文本编辑器layedit 以及 图片转base64前端页面显示
    CubeMX+BabyOS 使用方法
    数据结构的各种排序
    工厂模式之工厂方法模式(常用)
    迎战秋招计划
    C#、C++、Java、Python 选择哪个好?
    mongo数据同步的三种方案
    详解 Apache Hudi Schema Evolution(模式演进)
    使用workerman/mqtt做队列(订阅)
  • 原文地址:https://blog.csdn.net/weixin_43940314/article/details/127715482