• CMake



    前言

    CMakeake是什么,是用来做什么的,以及如何?
    cmake是一个用于管理和构建源代码的工具。
    cmake广泛应用于编译C/C++代码,其实也可以用于编译其他语言。
    cmake可实现跨平台构建、编译c/c++源码。
    cmake除了用于编译外还提供了ctest, cpack用于测试,安装或打包。


    第一章适用于新手,在不了解cmake语言的情况下可快速构建自己的项目

    一、快速开始编译C/C++代码

    1. 只有源码的项目

    .
    ├── include
    │   ├── hea2.h
    │   └── head1.h
    ├── main.c
    └── src
        ├── fun1.c
        └── fun2.c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在根目录下新建CMakeLists.txt文件:

    # 新项目必须的
    cmake_minimum_required(VERSION 3.10)
    # 新项目必须的
    project(tutorial VERSION 1.0)
    # 生成可执行文件必须的
    add_executable(main
        main.c
        src/fun1.c
        src/fun2.c
    )
    # 指定头文件搜索路径
    target_include_directories(main PUBLIC
        ./include
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    接下来,在根目录下新建build目录,然后运行cmake命令

    mkdir build && cd build
    cmake ..	     # 生成构建系统
    cmake --build .  #执行构建
    
    • 1
    • 2
    • 3

    2. 包含库的项目

    和1的区别仅为在CMakeLists.txt中把依赖添加进去:

    target_link_libraries(main
        libname1
        libname2
    )
    target_link_directories(main PUBLIC
        ./lib
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后执行cmake 命令即可

    3. 编译成库给他人使用

    若需要将源文件编译成库而不是可执行文件,则仅需修改CMakeLists.txt文件即可
    生成静态库

    add_library(main STATIC  # STATIC可写可不写
        main.c
        src/fun1.c
        src/fun2.c
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    生成动态库:

    cmake_minimum_required(VERSION 3.10)
    
    project(tutorial VERSION 1.0)
    
    add_library(main SHARED
        main.c
        src/fun1.c
        src/fun2.c
    )
    target_include_directories(main PUBLIC
        ./include
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用cmake的流程

    1. 生成构建系统(buildsystem,比如make工具对应的Makefile文件)
    2. 执行构建(比如make),声明目标文件
    3. 执行测试、安装或打包

    1. 生成构建系统

    通过cmake 命令生成构建系统

    参数含义
    -S指定源文件根目录,必须包含一个CMakeLists.txt文件
    -B指定构建目录,构建生成的中间文件和目标文件的生成路径
    -D指定变量,格式为-D =,-D后面的空格可以省略

    指定当前目录为源文件目录,其中包含CMakeLists.txt文件,使用build目录作为构建目录
    cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DAUTHOR=me

    使用-D设置的变量在CMakeLists.txt中生效,可以设置cmake的内置支持的一些变量控制构建的行为;当然也可以使用自定义的变量,在CMakeLists.txt中自行判断做不同的处理。

    2. 执行构建

    cmake --build .
    –buid 后面跟构建系统所在目录

    3. 执行测试

    如果在CMakeLists.txt中添加如下包含测试的功能则在执行构建后可进行测试

    enable_testing()
    # include(CTest)  # 添加CTest模块, 可代替enable_testing
    add_test(NAME normal_test COMMAND main )
    set_tests_properties(normal_test PROPERTIES PASS_REGULAR_EXPRESSION "yes")  # main程序输出“yes”视为通过
    
    • 1
    • 2
    • 3
    • 4

    4. 安装 && 打包

    在CMakeLists.txt中添加如下内容:

    install(TARGETS binary_nam DESTINATION ./install/bin)
    install(FILES "include/head.h" DESTINATION ./install/inc)
    install(EXPORT Json FILE FILE Json.cmake DESTINATION ./install/lib)
    
    • 1
    • 2
    • 3

    然后执行

    cmake --install .
    或者
    cmake --install . --prefix "~/myuser/installdir"
    
    • 1
    • 2
    • 3

    打包部分测试未通过

    二、cmake 语法简介

    1 变量

    CMake中使用setunset命令设置或者取消设置变量

    一般变量
    设置的变量可以是字符串,数字或者列表(直接设置多个值,或者使用分号隔开的字符串格式为"val1;val2;val3")

    # set var
    set(val1 a)
    set(val2 "hello world")
    # set list
    set(list1 a b)  # saved as "a;b"
    set(list2 a;b)
    set(list3 "a;b")
    
    set(num 90)  # saved as string, but can compare with other number string
    set(falg ON) # bool value
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 如果要设置的变量值包含空格,则需要使用双引号或者使用""转义,否则可以省略双引号;
    2. 如果设置多个值或者字符串值的中间有";“,则保存成list,同样是以”;"分割的字符串;
    3. 变量可以被list命令操作,单个值的变量相当于只有一个元素的列表;
    4. 引用变量:${},在if()条件判断中可以简化为只用变量名

    Cache变量
    Cache变量(缓存条目,cache entries)的作用主要是为了提供用户配置选项,如果用户没有指定,则使用默认值,设置方法如下:

    # set(<variable> <value>... CACHE <type> <docstring> [FORCE])
    set(CACHE_VAR "Default cache value" CACHE STRING "A sample for cache variable")
    
    • 1
    • 2
    1. 主要为了提供可配置变量,比如编译开关;
    2. 引用CACHE变量:$CACHE{}
    3. Cache变量会被保存在构建目录下的CMakeCache.txt中,缓存起来之后是不变的,除非重新配置更新

    环境变量

    修改当前处理进程的环境变量,设置和引用格式为:

    # set(ENV{} [])
    set(ENV{ENV_VAR} "$ENV{PATH}")
    message("Value of ENV_VAR: $ENV{ENV_VAR}")
    
    • 1
    • 2
    • 3

    和CACHE变量类似,要引用环境变量,格式为:$ENV{}。

    2 条件语句

    1. 字符串比较,比如:STREQUAL、STRLESS、STRGREATER等;
    2. 数值比较,比如:EQUAL、LESS、GREATER等;
    3. 布尔运算,AND、OR、NOT;
    4. 路径判断,比如:EXISTS、IS_DIRECTORY、IS_ABSOLUTE等;
    5. 版本号判断;
    6. 使用小括号可以组合多个条件语句,比如:(cond1) AND (cond2 OR (cond3))

    常量

    1. ON、YES、TRUE、Y和非0值均被视为True;
    2. 0、OFF、NO、FALSE、N、IGNORE、空字符串、NOTFOUND、及以"-NOTFOUND"结尾的字符串均视为False

    对于变量,只要其值不是常量中为False的情形,则均视为True。

    3 脚本命令

    消息打印

    message([] "message text"...)
    
    • 1

    mode:

    1. 空或者NOTICE:比较重要的信息,如前面演示中的格式
    2. DEBUG:调试信息,主要针对开发者
    3. STATUS:项目使用者可能比较关心的信息,比如提示当前使用的编译器
    4. WARNING:CMake警告,不会打断进程
    5. SEND_ERROR:CMake错误,会继续执行,但是会跳过生成构建系统
    6. FATAL_ERROR:CMake致命错误,会终止进程

    if-else:

    set(emp_str "")
    if (NOT emp_str AND flag and num less 50 and not not_define_var)
    	message(STATUS "first ")
    elseif (emp_str)
    	message(STATUS "second")
    else()
    	message("third)
    endif()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此外还有 for/while等

    list命令

    1. APPEND,往列表中添加元素;
    2. LENGTH,获取列表元素个数;
    3. JOIN,将列表元素用指定的分隔符连接起来;
    list(APPEND  )
    
    • 1

    文件操作

    CMake的file命令支持的操作比较多,可以读写、创建或复制文件和目录、计算文件hash、下载文件、压缩文件等等。

    file(GLOB_RECURSE ALL_SRC
            src/module1/*.c
            src/module2/*.c
            )
    
    • 1
    • 2
    • 3
    • 4

    GLOB_RECURSE表示执行递归查找,查找目录下所有符合指定正则表达式的文件。

    配置文件生成

    使用configure_file命令可以将配置文件模板中的特定内容替换,生成目标文件。 输入文件中的内容@VAR@或者${VAR}在输出文件中将被对应的变量值替换。 使用方式为:

    PROJECT(tutorial VERSION 1.0.1.1)
    configure_file(version.h.in "${PROJECT_BINARY_DIR}/version.h")
    
    • 1
    • 2

    version.h.in

    #define VERSION "@VERSION@"
    #define MAJOR @tutorial_VERSION_MAJOR@
    #define MINOR @tutorial_VERSION_MINOR@
    #define PATCH @tutorial_VERSION_PATCH@
    
    • 1
    • 2
    • 3
    • 4

    执行系统命令

    使用execute_process命令可以执行一条或者顺序执行多条系统命令,对于需要使用系统命令获取一些变量值是有用的。比如获取当前仓库最新提交的commit的commit id:

    execute_process(COMMAND bash "-c" "git rev-parse --short HEAD" OUTPUT_VARIABLE COMMIT_ID)
    
    • 1

    查找库文件

    通过find_library在指定的路径和相关默认路径下查找指定名字的库,常用的格式如下:

    find_library ( name1 [path1 path2 ...])
    
    • 1

    找到的库就可以被其他target使用,表明依赖关系。

    include其他模块

    include命令将cmake文件或者模块加载并执行

    include(CPack) # 开启打包功能
    include(CTest) # 开启测试相关功能
    
    • 1
    • 2

    CMake自带有很多有用的模块,可以看看官网的链接:cmake-modules,对支持的功能稍微有所了解,后续有需要再细看文档。当然,如果感兴趣,也可以直接看CMake安装路径下的目录CMake\share\cmake-\Modules中的模块源文件。

    关于CMake脚本源文件的示例位于路径:cmake/script_demo.cmake,可以使用cmake -P cmake/script_demo.cmake执行查看结果; 关于配置文件生成的操作在项目根目录的CMakeLists.txt中也有示例。

    三、配置案例

    main CMakeLists.txt

    cmake_minimum_required(VERSION 3.15)
    project(tutorial VERSION 2.3)
    set(binary_name haha)
    add_executable(
        ${binary_name}
        main.cpp
        )
    message("project_source_dir: ${PROJECT_SOURCE_DIR}, binary: ${PROJECT_BINARY_DIR}")
    configure_file(tutorialconfig.h.in tutorialconfig.h)  # comm cmake and src
    #########################  change c++ standard  ################################
    add_library(tutorial_compiler_flags INTERFACE)
    target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_17)
    target_link_libraries(${binary_name} PUBLIC tutorial_compiler_flags)
    #-------------------------------------------------------------------------------
    add_subdirectory(libs/json)  # subproj CMakeLists.txt
    target_link_libraries(${binary_name} PUBLIC Json)
    target_include_directories(${binary_name} PUBLIC 
                                "${PROJECT_BINARY_DIR}"
                                "${PROJECT_SOURCE_DIR}"
                                "${PROJECT_SOURCE_DIR}/libs/json"
                                "${PROJECT_SOURCE_DIR}/libs/math"
                                )
    target_link_options(${binary_name} PUBLIC "-D_BUG_")
    # cmake . -DUSE_MYLIB=OFF
    option(USE_MYLIB "Use providesd imt XXX" ON)
    if (USE_MYLIB)
        target_compile_definitions(${binary_name} PRIVATE "USE_MYLIB")
        message("oo USE_MYLIB macro used")
    else()
        message("oo USE_MYLIB macro not used")
    endif()
    #########################  Generator Expression  ###############################
    set(gcc_like_cxx "$")
    set(msvc_cxx "$")
    target_compile_options(
        tutorial_compiler_flags INTERFACE
        # "$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
        # "$<${msvc_cxx}:-W3>"
        # 仅在构建时启用,在作为项目的模块时不启用
        "$<${gcc_like_cxx}:$>"
        "$<${msvc_cxx}:$>"
    )
    ###########################  install & ctest ############################
    # cmake --install .
    # cmake --install . --prefix "/home/myuser/installdir"
    install(TARGETS ${binary_name} DESTINATION ${PROJECT_SOURCE_DIR}/install/bin)
    install(FILES "${PROJECT_BINARY_DIR}/tutorialconfig.h" DESTINATION ${PROJECT_SOURCE_DIR}/install/include)
    # install(EXPORT Json FILE Json.cmake DESTINATION ${PROJECT_SOURCE_DIR}/install/lib)
    enable_testing()  # enable testing
    add_test(NAME Runs COMMAND ${binary_name} 25)
    add_test(NAME Usage COMMAND ${binary_name} 4)
    set_tests_properties(Usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
    add_test(NAME StandardUse COMMAND ${binary_name} 4)
    set_tests_properties(StandardUse PROPERTIES PASS_REGULAR_EXPRESSION "4 is 2")
    function(do_test target arg result)
        add_test(NAME Comp${arg} COMMAND ${target} ${arg})
        set_tests_properties(Comp${arg} PROPERTIES PASS_REGULAR_EXPRESSION ${result})
    endfunction()
    do_test(${binary_name} 4 "4 is 2")
    do_test(${binary_name} 5 "5 is 2")
    do_test(${binary_name} 6 "6 is 2")
    do_test(${binary_name} 7 "7 is 2")
    do_test(${binary_name} 8 "8 is 2")
    do_test(${binary_name} 9 "9 is 2")
    #############################  Dashboard  ######################
    include(CTest)  # include CTest module
    if(UNIX)
        message(hello------------)
    endif()
    ##########################  system introspection  ##############################
    include(CheckCXXSourceCompiles)  # include this module, check_XXX_compiles
    check_cxx_source_compiles(
        "
        #include 
        int main() {
            std::log(1.0);
            return 0;
        }
        "
        HAVE_LOG
    )
    check_cxx_source_compiles(
        "
        #include 
        int main() {
            std::exp(1.0);
            return 0;
        }
        "
        HAVE_EXP
    )
    if (HAVE_LOG AND HAVE_EXP)
        target_compile_definitions(${binary_name} PRIVATE "HAVE_LOG" "HAVE_EXP")
        message("support exp and log function")
    else()
        message("not support exp or log function")
    endif()
    #########################  packaging && installer ? failed  #############################
    # This module will include any runtime libraries that are needed by the project for the current platform
    include(InstallRequiredSystemLibraries)
    # set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
    set(CPACK_PACKAGE_VERSION_MAJOR "${tutorial_VERSION_MAJOR}")
    set(CPACK_PACKAGE_VERSION_MINOR "${tutorial_VERSION_MINOR}")
    set(CPACK_SOURCE_GENERATOR "TGZ")
    include(CPack)
    # then cpack / cpack -G ZIP -C Debug  -G:generator,-C:multi-config specify configuration
    
    • 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
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    sub CMakeLists.txt

    include(MakeTable.cmake)  # include and execute
    # cmake_minimum_required(VERSION 3.0)
    # project(cppjsonrw VERSION 1.9)
    add_library(Json json_reader.cc json_value.cc json_writer.cc ${CMAKE_CURRENT_BINARY_DIR}/table.h)
    target_include_directories(
        Json
        INTERFACE  # consumers require but the producer doesn't
        # if not exprot
        ${CMAKE_CURRENT_SOURCE_DIR}  
        ${CMAKE_CURRENT_BINARY_DIR}
        # else export
        # $
        # $
    )
    target_link_libraries(Json PUBLIC tutorial_compiler_flags)
    message("Jproject source dir: ${PROJECT_SOURCE_DIR}")
    message("Jcmake source dir: ${CMAKE_SOURCE_DIR}")
    message("Jcmake current source dir: ${CMAKE_CURRENT_SOURCE_DIR}")
    #######################  static or dynamic lib  ################################
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
    option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
    ###########################  install  ############################
    set(installable_libs tutorial_compiler_flags Json)
    # if (TARGET Json)
    #     list(APPEND installable_libs Json)
    # endif()
    install(TARGETS ${installable_libs} EXPORT Json DESTINATION ${PROJECT_SOURCE_DIR}/install/lib)
    install(FILES json.h DESTINATION ${PROJECT_SOURCE_DIR}/install/include)
    #######################  custom command and generated file  ####################
    
    • 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

    四、配置说明

    配置项目

    # 配置项目
    project(tutorial VERSION 1.0.1 LANGUAGES C CXX)
    
    • 1
    • 2

    指定编译器语言版本

    set(CMAKE_C_STANDARD 99)
    set(CMAKE_CXX_STANDARD 17)
    
    • 1
    • 2

    配置编译选项

    通过命令add_compile_options可为所有编译器配置选项(同时对多个编译器生效);通过设置变量CMAKE_C_FLAGS可以配置C编译器选项;设置变量CMAKE_CXX_FLAGS可配置C++编译器的编译选项。

    add_compile_options(-Wall -g)
    // add_compile_options("-g;-Wall")
    set(CMAKE)
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -std=c++17")
    
    • 1
    • 2
    • 3
    • 4
    • 5

    debug or release

    cmake --build . -DCMAKE_BUILD_TYPE=Debug
    
    • 1
    set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
    
    • 1
    • 2
    set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
    
    • 1
    • 2

    附录

    内建变量

    PROJECT_SOURCE_DIR最近的CMakeLists.txt中含project命令所在目录
    PROJECT_BINARY_DIR最近的含有project的对应构建树中所在目录
    CMAKE_SOURCE_DIR顶层CMakeLists.txt所在目录
    CMAKE_BINARY_DIR对应构建树顶层目录
    CMAKE_CURRENT_SOURCE_DIR当前CMakeLists.txt所在目录
    CMAKE_CURRENT_BINARY_DIR对应构建树当前所在目录
    _VERSION_MAJOR

    reference

    https://cmake.org/cmake/help/latest/
    cmake-commands
    cmake-modules(7)-https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html

  • 相关阅读:
    线上应用故障排查之一:高CPU占用
    Java项目源码javaweb花店销售管理系统
    Java基础之《Ajax+JQuery(JavaEE开发进阶Ⅱ)》—JQuery DOM操作
    AIR32F103(九) CAN总线的通信和ID过滤机制及实例
    JavaScript事件流:深入理解事件处理和传播机制
    04、添加 com.fasterxml.jackson.dataformat -- jackson-dataformat-xml 依赖报错
    redis的生产环境启动方案
    vlookup查找匹配值超过255个字符显示#Value的解决办法
    【GO语言基础】控制流
    推荐一本程序员、软件开发人员职业生涯指南的书
  • 原文地址:https://blog.csdn.net/surfaceyan/article/details/133957747