• CMake 官方文档入门


    对比官方文档练习

    step1 基本起始点

    一个基本的项目:从源文件构建可执行程序

    文件目录如下

    请添加图片描述

    编写 CMakeLists.txt 如下:

    # cmake 要求的最小版本
    cmake_minimum_required(VERSION 3.24)
    # 设置项目名称 可以设置版本号
    project(Tutorial VERSION 1.0)
    # 设置 c++ 的语言标准 C++17
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED True)
    
    configure_file(TutorialConfig.h.in TutorialConfig.h)
    
    # 添加可执行程序(前面的是可执行程序的名称 后面是所需要的 source 文件)
    add_executable(Tutorial tutorial.cpp)
    
    target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    解释:

    • 函数

    • configure_file():官方CMake教程对它的解释是:将文件复制到另一个位置并修改其内容。当然,这里的修改其内容也不是任意地修改,也是遵循一定的规则:将input文件复制到output文件,并在输入文件内容中的变量,替换引用为@VAR@或 V A R 的 变 量 值 。 每 个 变 量 引 用 将 替 换 为 该 变 量 的 当 前 值 , 如 果 未 定 义 该 变 量 , 则 为 空 字 符 串 。 可 能 有 些 绕 头 , 再 浅 显 一 点 : c o n f i g u r e f i l e , 复 制 一 份 输 入 文 件 到 输 出 文 件 , 替 换 输 入 文 件 中 被 @ V A R @ 或 者 {VAR}的变量值。每个变量引用将替换为该变量的当前值,如果未定义该变量,则为空字符串。可能有些绕头,再浅显一点:configure_file,复制一份输入文件到输出文件,替换输入文件中被@VAR@或者 VARconfigurefile@VAR@{VAR}引用的变量值。也就是说,让普通文件,也能使用CMake中的变量。

      configure_file( 
                     [COPYONLY] [ESCAPE_QUOTES] [@ONLY]
                     [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
      
      
      • 1
      • 2
      • 3
      • 4
    • target_include_directories():指定目标包含的头文件路径

    • 内置变量

    • PROJECT_BINARY_DIR:全路径/build

    • PROJECT_SOURCE_DIR:全路径/代码在的地方

    • CMAKE_CXX_STANDARD_REQUIRED:可以为每个 Target 初始化 CXX_STANDARD_REQUIRED 属性。而 CXX_STANDARD_REQUIRED 存在的目的是如果用户没有对自己的 CXX_STANDARD 作出宣告的话,那它就会死——但 set(CMAKE_CXX_STANDARD 17) 可以令这些措施一律无意义。这是为了能够对编译器的C++标准兼容性进行更好的约束,但对绝大多数人来说毫无意义。

    tutorial.cpp 如下

    // Copyright (c) 2022 Xiguan Inc
    // Author: xiguan
    // Email: xiguan.teng@qq.com
    // Create on 22-12-2
    // TODO:
    //
    
    #include 
    #include 
    
    #include "TutorialConfig.h"
    
    using namespace std;
    
    int main(int argc, char *argv[]) {
      if (argc < 2) {
        cout << argv[0] << "Version " << Tutorial_VERSION_MAJOR << "."
             << Tutorial_VERSION_MINOR << endl;
    
        cout << "Usage: " << argv[0] << " number" << endl;
        return 1;
      }
    
      const double inputValue = ::std::stod(argv[1]);
    
      const double outputValue = ::sqrt(inputValue);
    
      cout << "The square root of " << inputValue << " is " << outputValue << 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
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    TutorialConfig.h.in 如下:

    // the configured options and settings for Tutorial
    #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
    #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
    
    • 1
    • 2
    • 3

    step2 添加库

    现在给项目添加库(library),在库中实现平方根函数

    文件目录结构如下:

    请添加图片描述

    编写顶层 CMakeLists.txt 如下:

    # cmake 要求的最小版本
    cmake_minimum_required(VERSION 3.10)
    # 设置项目名称 可以设置版本号
    project(Tutorial VERSION 1.0)
    # 设置 c++ 的语言标准 C++17
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED True)
    
    configure_file(TutorialConfig.h.in TutorialConfig.h)
    # 设置 MyFunctions 可选
    option(USE_MYMATH "Use tutorial provided math implementation" ON)
    
    if (USE_MYMATH)
        add_subdirectory(MathFunctions)
        list(APPEND EXTRA_LIBS MathFunctions)
        list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
    endif ()
    
    ## 添加库
    #add_subdirectory(MathFunctions)
    
    
    # 添加可执行程序(前面的是可执行程序的名称 后面是所需要的 source 文件)
    add_executable(Tutorial tutorial.cpp)
    
    target_link_libraries(Tutorial PUBLIC "${EXTRA_LIBS}")
    
    target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" "${EXTRA_INCLUDES}")
    
    # target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/MathFunctions")
    # 链接这个库
    # target_link_libraries(Tutorial PUBLIC MathFunctions)
    
    • 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

    解释:

    • 函数

    • option:用于控制编译流程,相当于C语言中的宏条件编译

      option( "" [value])
      
      • 1

      variable:定义选项名称;help_text:说明选项的含义;value:定义选项默认状态,一般是OFF或者ON,除去ON之外,其他所有值都为认为是OFF。

    • target_link_libraries:将之前打包的库,链接到生成的目标上,不然会出现光声明,没定义的错误,注意也可以直接指定库名,如target_link_libraries(main XXX.so)target_link_libraries(main XXX.a)

    • add_subdirectory:添加一个子目录并构建该子目录

      add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])
      
      • 1

      source_dir
      必选参数。该参数指定一个子目录,子目录下应该包含CMakeLists.txt文件和代码文件。子目录可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前目录的一个相对路径。

      binary_dir
      可选参数。该参数指定一个目录,用于存放输出文件。可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前输出目录的一个相对路径。如果该参数没有指定,则默认的输出目录使用source_dir

      EXCLUDE_FROM_ALL
      可选参数。当指定了该参数,则子目录下的目标不会被父目录下的目标文件包含进去,父目录的CMakeLists.txt不会构建子目录的目标文件,必须在子目录下显式去构建。例外情况:当父目录的目标依赖于子目录的目标,则子目录的目标仍然会被构建出来以满足依赖关系(例如使用了target_link_libraries)

    • list:

      list (subcommand  [args...])
      
      • 1

      subcommand为具体的列表操作子命令,例如读取查找修改排序等。为待操作的列表变量,[args...]为对列表变量操作需要使用的参数表,不同的子命令对应的参数也不一致。

    编写 MathFunctions CMakeLists.txt 如下

    # 生成库
    add_library(MathFunctions mysqrt.cpp)
    
    • 1
    • 2

    解释:

    • 函数
    • 将指定的一些源文件打包成动态库和静态库,第一个参数就是生成库的名字,第二个参数是生成库的类型,后面的参数就都是源文件了,可以是之前定义的列表,也可以用1.cpp 2.cpp 3.cpp去指定。

    tutorial.cpp 如下

    // Copyright (c) 2022 Xiguan Inc
    // Author: xiguan
    // Email: xiguan.teng@qq.com
    // Create on 22-12-2
    // TODO:
    //
    
    #include 
    #include 
    
    #include "TutorialConfig.h"
    #ifdef USE_MYMATH
    #include "MathFunctions.h"
    #endif
    
    using namespace std;
    
    int main(int argc, char *argv[]) {
      if (argc < 2) {
        cout << argv[0] << "Version " << Tutorial_VERSION_MAJOR << "."
             << Tutorial_VERSION_MINOR << endl;
    
        cout << "Usage: " << argv[0] << " number" << endl;
        return 1;
      }
    
      const double inputValue = ::std::stod(argv[1]);
    #ifdef USE_MYMATH
      const double outputValue = mysqrt(inputValue);
    #else
      const double outputValue = ::sqrt(inputValue);
    #endif
      cout << "The square root of " << inputValue << " is " << outputValue << 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
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    TutorialConfig.h.in 如下:

    // the configured options and settings for Tutorial
    #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
    #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
    
    #cmakedefine USE_MYMATH
    
    • 1
    • 2
    • 3
    • 4
    • 5

    MathFunctions 目录

    这个目录的文件就是自己实现了一下 mysqrt 这个函数

    step3 为库添加使用要求

    使用要求库对库和可执行程序的链接,包含命令行提供了更好的控制,也使 CMake 内传递目标属性更加可控

    这儿我们说明实现的是:任何使用 MathFunctions 的实体都需要包含其代码目录,但是 MathFunctions 自己不需要。这个概念叫做 INTERFACE

    target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
    
    • 1

    在 MathFunctions 中添加这个,可以实现这个需求

    target_link_libraries(A B)命令为例,从理解的角度解释:

    • PRIVATE 依赖项B仅链接到目标A,若有C链接了目标A,C不链接依赖项B
    • INTERFACE 依赖项B并不链接到目标A,若有C链接了目标A,C会链接依赖项B
    • PUBLIC 依赖项B链接到目标A,若有C链接了目标A,C也会链接依赖项B

    step4 安装与测试

    安装规则

    对于 MathFunctions 我们希望安装库文件以及头文件,对于应用程序,我们希望安装可执行文件和配置头文件

    在 MathFunctions/CMakeLists.txt 添加

    install(TARGETS MathFunctions DESTINATION lib)
    install(FILES MathFunctions.h DESTINATION include)
    
    • 1
    • 2

    在顶层 CMakeLists.txt 添加

    测试支持

    先在顶层的 CMakeLists.txt 开启测试,然后添加一些基本的测试来验证

    enable_testing()
    
    # does the application run
    add_test(NAME Runs COMMAND Tutorial 25)
    
    # does the usage message work?
    add_test(NAME Usage COMMAND Tutorial)
    set_tests_properties(Usage PROPERTIES PASS_REGULAR_EXPRESSION
            "Usage:.*number")
    
    # define a function to simplify adding tests
    function(do_test target arg result)
        add_test(NAME Comp${arg} COMMAND ${target} ${arg})
        set_tests_properties(Comp${arg} PROPERTIES PASS_REGUKAR_EXPRESSION ${result})
    endfunction()
    
    do_test(Tutorial 4 "4 is 2")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在二进制目录 运行 ctest -N 和 ctest -vv

    step5 添加系统内省

    检查系统中是否有某些函数

    # 顶层 CMakeLists.txt 
    include(CheckSymbolExists)
    set(CMAKE_REQUIRED_LIBRARIES "m")
    check_symbol_exists(log "math.h" HAVE_LOG)
    check_symbol_exists(exp "math.h" HAVE_EXP)
    
    # MathFunctions/CMakeLists.txt 
    target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} PRIVATE ${CMAKE_BINARY_DIR})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    // TutorialConfig.h.in 文件添加
    #cmakedefine HAVE_LOG
    #cmakedefine HAVE_EXP
    
    • 1
    • 2
    • 3

    step6 生成一个安装器

    我们将项目发布给别人使用,多个平台提供二进制和源码包

    顶层 CMakeLists.txt 添加

    # 这儿会把当前平台上所需要运行的库全部包含近来
    include(InstallRequiredSystemLibraries)
    set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
    set(CPACK_PACKGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
    set(CPACK_PACKGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
    include(CPack)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    # 构建生成一个二进制发布包
    cpack
    
    # -G 选项可以制定打包哪一种结构
    cpack -G ZIP -C Debug
    
    # 创建一个源码发布
    cpack --config CPackSourceConfig.cmake
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

  • 相关阅读:
    Discourse 分类图片
    LeetCode--580. 统计各专业学生人数
    MATLAB矩阵
    TDengine:taosBenchmark的使用方式
    Java类的生命周期和子类实例化对象的过程
    Python并发编程之Queue队列
    MVVM的理解
    第三十六章 在 UNIX®、Linux 和 macOS 上使用 IRIS(一)
    [Spring笔记] Spring-40-Spring事务属性
    一起备战蓝桥杯与CCF-CSP之大模拟炉石传说
  • 原文地址:https://blog.csdn.net/xiguanlant/article/details/128149157