现代 cmake (cmake 3.x) 操作大全

  cmake 是一个跨平台编译工具,它面向各种平台提供适配的编译系统配置文件,进而调用这些编译系统完成编译工作。cmake 进入3.x 版本,指令大量更新,一些老的指令开始被新的指令集替代,并加入了一些更加高效的指令/参数。本文归纳了cmake 3.x 版本的常用指令,方便使用时备查。

  关于cmake工具的简单介绍和VS Code cmake Tools 环境配置,可以参考我的另一篇博客。本文参考资料:【公开课】现代CMake高级教程 - Bilibili 小彭老师,CMake Reference Documentation

ps: 本文是大全类文档,适合备查,完整学习cmake强烈建议跟随官方tutorial,会有非常好的效果

cmake 项目构建流程

典型的cmake 项目构建流程如下:

  1. 配置阶段 Configure:根据编写的CmakeLists.txt文件,以及选择的编译系统,生成该系统的构建规则文件。(例如,对make生成Makefile,对MSVC生成sln(可以在VS中打开))

    cmake -B build
    
    • 以上指令可以在当前目录下创建build目录,而不需要事先创建并进入
    • 通过-D设置缓存变量,格式-D,如:
      • 编译器路径:CMAKE_C_COMPILTER, CMAKE_CXX_COMPILER
      • 安装路径(在Configure阶段配置):CMAKE_INSTALL_PREFIX
      • 构建模式:CMAKE_BUILD_TYPE
      • 自定义缓存变量:-Dvar:type=value,可以将option设定为OFF,即表示不启用(会覆盖CMakeLists中的默认选项)
    • -G指定生成器(Generator,即构建系统),可以通过--help查看支持的列表
      • -A 指定架构(For MSVC build system)
      • -T 指定工具链,例如使用ClangCL: -T ClangCL,host=x64 (For MSVC build system)
      • 推荐使用Ninja作为生成器,效率较高
  2. 编译阶段 Build:根据生成的构建规则,调用构建系统进行构建,这一步真正输出项目目标(可执行文件、共享库等)

    cmake --build build
    
  3. 构建完成后,可以在build目录下找到输出结果(MSVC并不是直接放在build下,而是在构建模式对应目录下(Debug, Release),这与其他不同)

项目配置变量

项目配置变量是控制项目构建,以及包含项目信息的关键变量。这些变量可以在命令行配置(缓存变量),也可以在CMakeLists内部修改。

项目构建模式

一般有四种项目构建模式:

  • Debug 调试模式:不优化,生成调试信息 -O0 -g
  • Release 发布模式:最优化,性能最佳 -O3 -DNDEBUG
  • MinSizeRel 最小体积发布 :生成项目文件小,性能优化不完全 -Os -DNDEBUG
  • RelWithDebInfo 带调试信息发布 -O2 -g -DNDEBUG
  • 默认为Debug模式

可以通过在CMakeLists中增加默认选项脚本的方法修改默认选项为Release

if (NOT CMAKE_BUILD_TYPE)
	set(CMAKE_BUILD_TYPE Release)
endif()

项目信息

使用命令project初始化项目信息

project( [...])
project(
        [VERSION [.[.[.]]]]
        [DESCRIPTION ]
        [HOMEPAGE_URL ]
        [LANGUAGES ...])
  • language: 指定项目使用语言,默认C CXX
    • 支持C CXX ASM FORTRAN CUDA OBJC OBJCXX ISPC
  • VERSION 字段:设置项目版本号,会自动配置相关变量

该命令将初始化名为project_name的项目,并给相关变量赋值:

  • PROJECT_NAME: 项目名称
  • CMAKE_PROJECT_NAME:根项目名称
  • PROJECT_SOURCE_DIR:项目源码路径,即初始化project的CMakeLists.txt所在路径
  • PROJECT_BINARY_DIR:项目输出路径,通常是./build路径
  • CMAKE_CURRENT_SOURCE_DIR:当前源码路径
  • CMAKE_CURRENT_BINARY_DIR:当前输出路径,即当前CMakeLists.txt所在路径,子模块中指子模块路径
  • 更多属性,请参考project — CMake 3.25.1 Documentation

项目语言标准

一般通过如下指令设置标准(推荐放在project指令前,会在project语言启用时检测)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
  • 设置C标准为C11, C++设置CXX对应属性
  • CMAKE_C_STANDARD_REQUIRED 在启用语言时检查编译器是否支持该标准
  • CMAKE_C_EXTENSIONS 是否启用GNU拓展语言特性(对跨平台有影响)

添加构建目标

构建目标主要有两种类型:

可执行文件 executable

使用add_executable命令添加可执行文件目标:

add_executable( [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])
  • 添加一个name的可执行文件目标,源文件来自source
    • 可以是列表或者空格分开的多个文件名
    • 也可以是变量${var_name},目录的所有文件可以使用 aux_source_directory添加
  • 其中,source可以省略(>3.11),并在后面以target_sources的形式给出

库 library

add_library( [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [...])
  • STATIC/SHARED 指定生成静态库/动态库,默认静态

另外还有对象库object library

add_library( OBJECT [...])
  • 对象库不输出实际的库文件,而是可以直接被加入到其他构建目标中,如:

    add_library(... $ ...)
    add_executable(... $ ...)
    

    或者使用target_link_libraries()链接到对象库(CMAKE >3.12)

  • 注意:动态库不能直接引用静态库,因为动态库开启PIC,而静态库没有

    • 解决方案:使用对象库替代静态库,或者为静态库配置PIC
      set_property(TARGET target_name PROPERTY POSITION_INDEPENDENT_CODE ON)
      
  • 另外,Windows平台如果要使用动态库,则需要添加宏

    • 声明前添加

      #ifdef _MSC_VER
          __declspce(dllimport)
      #endif
      
    • 定义前加

      #ifdef _MSC_VER
          __declspce(dllexport)
      #endif
      

配置构建目标

设置对象属性

set_target_properties(target_name PROPERTIES
						property_name value
						...)

使用set_target_properties设置编译目标属性

这些属性包括(详细信息参考cmake-properties(7) — CMake 3.25.1 Documentation):

  • 语言标准版本
  • 编译器设置
  • 目标输出位置*_OUTPUT_DIRECTORY

链接三方库

使用target_link_libraries()链接第三方库

target_link_libraries( ... ... ...)

其中,item可以是:

  • 库对象名(使用add_library( IMPORTED)引入)
  • 指向库文件的完整路径
  • 库文件名,这会调用链接器自动搜索(在Linux下可以依据lib搜索)

优化方案(对使用cmake的三方库,限Linux):

find_package(LIBRARY_NAME REQUIRED)
target_link_libraries( [PUBLIC] LIBRARY_NAME::library_target_name)
  • 使用find_package会在/usr/lib/cmake下增加.cmake配置文件,cmake会自动搜索并链接相应的库。
  • 另外,在链接库的时候,还会按照配置文件进行引用传播,引用其的对象自动引用其头文件目录。

编译时定义

使用target_compile_definitions来配置编译时宏定义,这些定义可以在C/C++的宏中被使用

target_compile_definitions(target PUBLIC def)
  • 将def设置到target的编译过程中

安装 install

install(TARGETS ... [destination])

以上命令用于安装对象到destination

子命令还可以是:

  • FILES 安装文件,如头文件等
  • DIRECTORY 目录
  • 详见install — CMake 3.25.1 Documentation

部署 ctest

使用enable_testing以启用ctest,ctest会自动执行命令,并匹配输出,自动化完成测试

以下示例增加一个ctest,并设置匹配的输出结果

add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
    PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")

如果程序输出:Usage:[any]number,就算正确通过

还可以将测试集写为函数,增强代码复用性

function (do_test target arg result)
  add_test(NAME Comp${arg} COMMAND ${target} ${arg})
  set_tests_properties(Comp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION "${arg} is ${result}")
endfunction()

function (do_math_test target)
  do_test(${target} 4 2)
  do_test(${target} -25 "(-nan|nan|0)")
endfunction()
do_math_test(Tutorial)

Scripting Commands

  • cmake命令不区分大小写,不过小写命令更推荐使用
  • cmake 变量,参数等区分大小写

基础设置变量

使用set命令来设置变量:

set( ... [PARENT_SCOPE])
  • 如果value为空格分隔的字符串(不加引号),则被认为是列表
  • 列表也可以用"a;b"(以分号分隔,加引号)等效

CMakeLists 文件结构

通常情况下,CMakeLists文件结构如下所示:

cmake_minimum_required(VERSION 3.0)

project(project_name)

...
  • cmake_minimum_required指定了该项目生成需要的最小版本
  • project()指定项目名称,该命令以下部分都是该项目的配置

批量添加文件

aux_source_directory( )

此命令将添加source_dir下的所有匹配文件到variable中。(匹配文件根据项目语言决定

文件操作指令

  • file是cmake中的文件操作指令,可以完成复制、创建等一系列工作,也可以用于查找文件:

    file(GLOB  CONFIGURE_DEPENDS )
    
    • CONFIGURE_DEPENDS可以在文件更新时自动更新cmake
    • 后面是查找文件的表达式,可以是通配符,如*.cpp *.h
  • configure_file 可以完成文件配置,按照模板文件生成目标文件

    configure_file(Config.h.in ${PROJECT_SOURCE_DIR}/Config.h)
    

    该命令会将模板文件Config.h.in中内容替换为实际配置内容,并将头文件写入source目录下的Config.h中,这样,可以完成对文件内容的修改,如启用选项,编辑#define常量,或其他宏补全效果。

    常用模板格式如下:

    #define FOO_STR "@FOO_STR@"
    #cmakedefine FOO_ENABLE
    

    该命令会@号包含的部分转换为cmake 执行环境中变量,使用cmakedefine如果cmake环境中存在变量,则会将其修改为#define FOO_ENABLE

    即,如果在CMakeLists中设置

    set(FOO_STR "Hello world")
    option(FOO_ENABLE True)
    

    则模板文件中的定义将被输出为:

    #define FOO_STR "Hello world"
    #define FOO_ENABLE
    

输出字符串

使用message命令输出字符串

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

其中模式可以为下列等:

  • STATUS 状态信息,前面带--
  • WARNING 警告信息,黄色警告
  • FATAL_ERROR 致命错误
  • INFO 默认,前面不带任何,白色,输出到stderr

列表 list

使用list命令来构建,操作列表

Reading
  list(LENGTH  )
  list(GET   [ ...] )
  list(JOIN   )
  list(SUBLIST    )

Search
  list(FIND   )

Modification
  list(APPEND  [...])
  list(FILTER  {INCLUDE | EXCLUDE} REGEX )
  list(INSERT   [...])
  list(POP_BACK  [...])
  list(POP_FRONT  [...])
  list(PREPEND  [...])
  list(REMOVE_ITEM  ...)
  list(REMOVE_AT  ...)
  list(REMOVE_DUPLICATES )
  list(TRANSFORM   [...])

Ordering
  list(REVERSE )
  list(SORT  [...])

详见list — CMake 3.25.1 Documentation,[下一小节](#选项 option)会有应用选项的列表操作

选项 option

使用option来生成选项,这些选项**会在ccmake**或cmake-gui中被显示

option(VAR_NAME [Description] [default_value])
  • option中的默认选项会被命令行中的-D选项覆盖

例如,

option(USE_MYMATH "Enable MyMath Library" True)

cmake-gui中,会显示如下,并可以被配置

一般项目情形下,需要使用option来控制某个子功能是否启动,可以使用以下命令(接上例USE_MYMATH):

if (USE_MYMATH)
    add_subdirectory(MathFunctions)
    list(APPEND EXTRA_LIBS MathFunctions)
    list(APPEND EXTRA_INCLUDES MathFunctions)
endif()

如果选项中启用USE_MYMATH,才会进行库引用

生成器表达式

使用生成器表达式可以简化指令

$<$:statement>

只有在变量=值时,才会表现为statement,否则,生成器表达式的值为空

变量与作用域

默认变量传播规则:parent->child

如果要child->parent,则增加选项PARENT_SCOPE

Usage Requirements

在外部引用项目时,需要满足一些特定条件,这些条件称为usage requirements

只要项目开发者编写Usage Requirements,使用者就可以直接通过link完成寻找包和链接功能,而不需要另外的配置(添加include等),是现代cmake的主要构成。

同时,这些引用也是会传递的,不需要进行额外配置(但要保证配置正确)

在配置项目时,一般有三种配置选项PRIVATE|PUBLIC|INTERFACE,其中,PRIVATE仅限于项目自身编译时使用,INTERFACE是Usage Requirements,它要求所有使用该库的项目添加该动作PUBLIC则是两者兼有。

例如,

add_library(MathFunctions mysqrt.cxx)
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

以上指令会使所有引用该库(MathFunctions)的项目自动引用该项目的头文件

你可能感兴趣的:(Others,C,C++,cmake)