• 多级CMakeLists.txt调用



      在c/c++工程开发中,往往会涉及多级CMakeLists.txt的调用,并且调用方式错综复杂,主要有以下两种方式:

    1. 子目录中的CMakeLists.txt独立生成目标,不作为主目标生成过程主的依赖存在,与主目标并无任何关系。
    2. 子目录中的CMakeLists.txt作为主目标的依赖源文件,不单独生成目标,作为主目标生成过程主的部分源文件,通常以生成.a静态库的方式提供给主CMakeLists.txt使用。

    一.工程目录结构

      下面给出了测试工程目录,进行了两项测试:一是unit-test目录作为独立生成目标,其CMakeLists.txt在主CMakeLists.txt主被调用;二是subfunc和subsubfunc作为主CMakeLists.txt向下两级的依赖,为主CMakeLists.txt提供源文件支持,其CMakeLists.txt为逐级调用的方式:CMakeLists.txt----->/subfunc/CMakeLists.txt------>/subfunc/subsubfunc/CMakeLists.txt
      具体目录结构如下:

    cmaketest
    ├── build                                   // 编译目录,生成的目标执行文件文件、静态库、中间缓存文件均存在此处
    ├── inc                                     // 主头文件目录
    │   └── func1.hpp
    │   └── func2.hpp
    │
    └── src                                     // 主源文件目录
    │    └── func1.cpp
    │    └── func2.cpp
    │
    └── subfunc                                 // 依赖的一级子目录
    │    └── subsubfunc                         // 依赖的二级子目录
    │    │        └── subsubfunc.cpp
    │    │        └── subsubfunc.hpp
    │    │        └──CMakeLists.txt             // 被cmaketest/subfunc/CMakeLists.txt调用
    │    └── subfunc.cpp
    │    └── subfunc.hpp
    │    └── CMakeLists.txt                     // 被cmaketest/CMakeLists.txt调用
    │
    └── unit-test                               // 单元测试目录,独立生成目标文件
    │    └──unit-test.cpp
    │    └── CMakeLists.txt                    // 被cmaketest/CMakeLists.txt调用
    │
    ├── main.cpp
    ├── CMakeLists.txt                        // 最上层、主CMakeLists.txt 
    
    • 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

    二.工程源代码

    2.1 上层目录

    2.1.1 cmaketest/CMakeLists.txt

    cmake_minimum_required(VERSION 3.8)     # 1.cmake版本
    PROJECT(cmaketest)                      # 2.工程名
    
    # set the project name
    set(PROJECT_NAME cmaketest)             # 3.设置工程名
    
    # specify the C++ standard 
    set(CMAKE_CXX_STANDARD 17)              # 4.设置c++标准为c++17
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    # 5.设置本地头文件路径,注意:子目录中的头文件通过target_include_directories添加到${PROJECT_NAME}INCLUDE_DIRECTORIES(
        inc                 # 上层头文件路径
        ${SUB_INCLUDE_DIR}  # 下级头文件目录
    )
    
    # 6.将源文件路径添加到变量SRC_LIST中
    AUX_SOURCE_DIRECTORY(.          SRC_LIST)
    AUX_SOURCE_DIRECTORY(src        SRC_LIST)
    
    # 7.生成目标(可执行文件):cmaketest
    ADD_EXECUTABLE(${PROJECT_NAME} ${SRC_LIST})
    
    # 8.设置编译时依赖的subfunc静态库
    target_link_libraries(${PROJECT_NAME}    #目标:tcu
        subfunc        # sub子目录下的静态库文件
        subsubfunc     # subsub子目录下的静态库文件
    )
    
    # 9.添加子目录,这样子目录中的CMakeLists.txt才会被调用
    add_subdirectory(subfunc)    # 调用subfunc子目录中的CMakeLists.txt,生成静态库而不生成新目标,目标与主CMakeLists.txt中设定的一致
    
    add_subdirectory(unit-test)   # 调用unit-test子目录中的CMakeLists.txt,生成新目标,目标与主CMakeLists.txt中设定的无关,仅仅是调用
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33

    注意:INCLUDE_DIRECTORIES包含的是的头文件的路径可以被各级子目录中的目标所使用,而target_include_directories包含的头文件路径只能被特定目标所使用;另外,注意这里采用的是变量传递( ${SUB_INCLUDE_DIR}引入子路径 )的方式引入子目录中的头文件路径。

    2.1.2 cmaketest/main.cpp

    #include 
    #include 
    #include "func1.hpp"   //应用层头文件1
    #include "func2.hpp"   //应用层头文件2
    
    int main(int argc, char *argv[])
    {
        func1();  //调用上层func1
        func2();  //调用上层func2
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.1.3 cmaketest/inc/func1.hpp

    #ifndef __FUNC1_HPP__
    #define __FUNC1_HPP__
    
    int func1(void);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.1.4 cmaketest/inc/func2.hpp

    #ifndef __FUNC2_HPP__
    #define __FUNC2_HPP__
    
    int func2(void);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.1.5 cmaketest/src/func1.cpp

    #include "subfunc.hpp"   //subfunc头文件
    #include "func1.hpp"     //应用层头文件1
    #include 
    #include 
    
    int func1(void)
    {
        std::cout<<"------------func1函数调用开始----------"<<std::endl;
        subfunc1();
        std::cout<<"------------func1函数调用结束----------"<<std::endl<<std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.1.6 cmaketest/src/func2.cpp

    #include "subfunc.hpp"   //subfunc头文件
    #include "func2.hpp"     //应用层头文件1
    #include 
    #include 
    
    int func2(void)
    {
        std::cout<<"------------func2函数调用开始----------"<<std::endl;
        subfunc2();
        std::cout<<"------------func2函数调用结束----------"<<std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.2 subfunc及subsubfunc子目录

    2.2.1 cmaketest/subfunc/CMakeLists.txt

    # 1.将本目录下的所有.c 文件添加到SUB_DIR_LIB_SRCS变量
    AUX_SOURCE_DIRECTORY(. SUB_DIR_SRC_LIST)
    
    # 2.设置当前的头文件路径
    set(SUB_INCLUDE_DIR 
        ${CMAKE_CURRENT_SOURCE_DIR}          # 当前源文件路径
        ${SUB_SUB_INCLUDE_DIR}               # 由下层subsubfunc目录传递的头文件路径
        CACHE INTERNAL "subfunc include dir" # 这个字符串相当于对变量SUB_INCLUDE_DIR的描述说明,不能省略,但可以自己随便定义,只有添加了这个描述SUB_INCLUDE_DIR变量才能被上层CMakeLists.txt调用!!!
    )
    
    MESSAGE(STATUS "subfunc层头文件路径 :${SUB_INCLUDE_DIR}")
    
    # 3.生成静态库
    add_library(subfunc ${SUB_DIR_SRC_LIST})
    
    # 4.添加subsubfunc子目录,这样子目录中的CMakeLists.txt才会被调用
    add_subdirectory(subsubfunc)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.2.2 cmaketest/subfunc/subfunc.hpp

    #ifndef __SUB_FUNC_HPP__
    #define __SUB_FUNC_HPP__
    
    int subfunc1(void);
    int subfunc2(void);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.2.3 cmaketest/subfunc/subfunc.cpp

    #include "subfunc.hpp"
    #include "subsubfunc.hpp"
    #include 
    #include 
    
    int subfunc1(void)
    {
        std::cout<<"------subfunc1函数调用开始------"<<std::endl;
        /* 中间调用subsubfunc1函数 */
    subsubfunc1();
        std::cout<<"------subfunc1函数调用结束------"<<std::endl;
        return 0;
    }
    int subfunc2(void)
    {
        std::cout<<"------subfunc2函数调用开始------"<<std::endl;
    subsubfunc2();
    
        /* 中间调用subsubfunc2函数 */
    
        std::cout<<"------subfunc2函数调用结束------"<<std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.2.4 cmaketest/subfunc/subsubfunc/CMakeLists.txt

    # 1.将本目录下的所有.c 文件添加到SUB_DIR_LIB_SRCS变量
    AUX_SOURCE_DIRECTORY(. SUB_SUB_DIR_SRC_LIST)
    
    # 2.设置当前的头文件路径
    set(SUB_SUB_INCLUDE_DIR 
        ${CMAKE_CURRENT_SOURCE_DIR}              # 当前源文件路径
        CACHE INTERNAL "subsubfunc include dir"  # 这个字符串相当于对变量SUB_SUB_INCLUDE_DIR的描述说明,不能省略,但可以自己随便定义,只有添加了这个描述SUB_SUB_INCLUDE_DIR变量才能被上层CMakeLists.txt调用!!!
    )
    
    MESSAGE(STATUS "subsubfunc层头文件路径 :${SUB_SUB_INCLUDE_DIR}")
    
    # 3.生成静态库
    add_library(subsubfunc ${SUB_SUB_DIR_SRC_LIST})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.2.5 cmaketest/subfunc/subsubfunc/subsubfunc.hpp

    #ifndef __SUB_SUB_FUNC_HPP__
    #define __SUB_SUB_FUNC_HPP__
    
    int subsubfunc1(void);
    int subsubfunc2(void);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.2.6 cmaketest/subfunc/subsubfunc/subsubfunc.cpp

    #include "subsubfunc.hpp"
    
    #include 
    #include 
    
    int subsubfunc1(void)
    {
        std::cout<<"-subsubfunc1函数调用开始-"<<std::endl;
        
        std::cout<<"-subsubfunc1函数调用结束-"<<std::endl;
        return 0;
    }
    int subsubfunc2(void)
    {
        std::cout<<"-subsubfunc2函数调用开始-"<<std::endl;
    
        std::cout<<"-subsubfunc2函数调用结束-"<<std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    分析:注意头文件目录变量的逐级向上传递,通过设置SUB_SUB_INCLUDE_DIR变量(必须添加CACHE INTERNAL "subsubfunc include dir"描述),将subsubfunc文件下的头文件路径传递给了上级sunfunc文件下的CMakeLists.txt;通过设置SUB_INCLUDE_DIR变量(必须添加CACHE INTERNAL "subfunc include dir"描述),将subfunc文件下的头文件路径(包含之前获得的${SUB_SUB_INCLUDE_DIR})传递给了最上层文件下的CMakeLists.txt。

    2.3 unit-test子目录

    2.3.1 cmaketest/unit-test/CMakeLists.txt

    add_executable(unit-test unit-test.cpp)
    target_link_libraries(unit-test boost_system pthread)
    
    • 1
    • 2

    2.3.2 cmaketest/unit-test/unit-test.cpp

    #include  
    #include 
    
    int main(int argc,char* argv[])
    {
        std::cout<<"unit-test代码调用!!!"<<std::endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.4 源代码git路径

    https://github.com/hututu578/cmaketest

    三.编译及出现的问题

      在项目路径下创建/build文件夹,用于存放cmake编译过程中生成的各种中间文件、库、最终目标文件等:

    cd build
    cmake . .
    make

      在cmake编译的过程中发现了一个问题,就是一开始执行cmake . .时候,并没有完成头文件变量的传递,导致make编译出错。cmake . .执行与make执行结果如下:
    cmake . .
    在这里插入图片描述
    make
    在这里插入图片描述
      可以看到,由于SUB_SUB_INCLUDE_DIR变量并没有传递到subfunc层的CMakeLists.txt,从而导致了在subfunc层并没有包含全部发头文件路径,导致编译的时候找不到subsubfunc.hpp。
    解决方案:
      经过反复测试发现,多执行几次cmake . .(三次以上)就可以解决这个问题,猜测是因为多次执行cmake . .的过程完成了SUB_SUB_INCLUDE_DIR和SUB_INCLUDE_DIR变量向上层的传递,多执行几次cmake . .的执行结果如下:
    在这里插入图片描述

    四.build目录分析

      下面是执行编译完成后的build文件目录结构:
    在这里插入图片描述
    可以看出,unit-test作为独立的子目录,生成了独立的目标文件unit-test。而在subfunc下生成了libsubfunc.a的静态库文件,在subsubfunc下生成了libsubsubfunc.a的静态库文件,这两个库文件供最上层CMakeLists.txt调用,并最终生成一个目标文件cmaketest。

    五.程序执行结果

    在这里插入图片描述

    六.总结

      上述测试了两种类型的多级CMakeLists.txt调用,一种是独立的unit-test生成独立的目标文件,与主CMakeLists.txt仅有一个调用与被调用的关系,并不存在任何的编译依关系;
      而另一种多级CMakeLists.txt调用之间存在上下级的依赖关系,下层的源代码给上层的调用提供支持(以生成静态库的方式),这里进行了分层设计,下层的头文件路径仅传递给调用的上一层而不会传递给最上层,这种变量传递的方式提高了代码的分层性,这里需要注意变量的设置(CACHE INTERNAL属性的必须添加)以完成下层到上层的变量传递,上述的两种分层CMakeLists.txt调用方式可覆盖基本的全部开发场景。

  • 相关阅读:
    leetcode 6103 — 从树中删除边的最小分数
    简述MVC模式
    Azure DevOps (十二) 通过Azure Devops部署一个SpringBoot应用
    opencv实现图像的融合
    【檀越剑指大厂--泛型】泛型总结
    [车联网安全自学篇] 七. ATTACK安全之Android SSH原理分析以及攻击检测
    docker安装及使用-Linux
    计算机组成原理学习笔记:计算机系统的层次结构
    可以免费发外链的论坛有哪些?
    布谷鸟搜索算法的改进及其在优化问题中的应用(Matlab代码实现)
  • 原文地址:https://blog.csdn.net/weixin_42700740/article/details/126364574