CMake

CMake

概念定义

目标

目标通常指的是需要构建的最终产物,比如可执行文件、库文件等。每个目 标都有一组与之相关联的源文件、依赖关系、编译选项等。

变量与函数

${}表示一个变量

常用自动定义变量 说明
${PROJECT_NAME} 代指项目名
${PROJECT_BINARY_DIR} 表示项目的二进制路径,exe文件会在此路径下生成
${PROJECT_SOURCE_DIR} 代指当前项目的源代码目录
注意:在子目录的CMakeList.txt中,其指向的仍是顶级目录的源代码目录
${CMAKE_CURRENT_SOURCE_DIR} 当前CMakeList.txt所在目录
常用函数 说明
TIMESTAMP 获取当前的时间戳,并将其格式化为指定的格式。

CMake变量占位符

在头文件中,形如@name@的为CMake变量占位符,它们将在CMake配置阶段被替换为实际的值。

软件版本号构成

  1. 主要部分(major):这是版本号中的第一部分,通常表示软件的主要版本或发行版。当主要部分发生变化时,通常意味着软件进行了重大的更新或改进,可能包括新的功能、用户界面更改或底层架构的改进。这种变化可能会对软件的兼容性和用户体验产生显著影响。
  2. 次要部分(minor):这是版本号中的第二部分,用于表示在主要版本下的次要更新或修订。次要更新通常包括性能改进、小功能添加或错误修复,这些更改通常不会破坏软件的兼容性或用户的主要工作流程。
  3. 修订号(patch):这是版本号中的第三部分,用于表示对软件的微小修订或错误修复。修订号的变化通常意味着软件中的一些小问题或漏洞已经被修复,而这些修复通常不会对软件的主要功能或用户体验产生显著影响。

作用域

说明
PRIVATE 包含目录仅对当前目标可见,不会传递给依赖该目标的其他目标。
INTERFACE 包含目录对当前目标不可见,但会传递给依赖该目标的其他目标。这通常用于库目标,以导出其公共头文件。( 消费者需要、但生产者不需要)
PUBLIC 包含目录对当前目标可见,并且也会传递给依赖该目标的其他目标。这通常用于同时需要包含目录进行编译和导出其头文件的情况。

静态库与动态库

静态库链接阶段会被链接到最终目标中(比如可执行程序)

缺点:同一个静态库如果被不同的程序引用,那么内存中会存在这个静态库函数的多份拷贝。

动态库在链接阶段不会被拷贝最终目标中,程序在运行阶段才会加载这个动态库。

优点:多个程序就算引用了同一个动态库,内存中也只存在一份动态库函数的拷贝。

一、基础

基本用法

1
2
3
4
5
cmake_minimum_required(VERSION )   #指定最低CMake版本号

project(name) #设置项目名称

add_executable(a.cpp) #生成exe

控制流语句

if() endif()

1
2
3
4
5
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/MathFunctions)
endif()

foreach() endforeach()

类似Python中的for,foreach()有两种形式:

  • 遍历列表:

    1
    2
    3
    foreach(i in LISTS list_var)
    ...
    endforeach()
  • 遍历范围:

    1
    2
    3
    foreach(i RANGE start stop [step])
    ...
    endforeach()

二、进阶

set和PROJECT_NAME

${PROJECT_NAME}代指项目名,${}表示一个变量

1
2
3
add_executable(${PROJECT_NAME} a.cpp b.cpp c.cpp)
#新建一个名称与项目名相同的exe,它由三个源文件组成
#多个源文件之间用空格隔开
1
2
3
set(SRC_LIST a.cpp b.cpp c.cpp)
#设置一个名为SRC_LIST的变量,该变量表示三个源文件
#通过${SRC_LIST}引用该变量

于是上面的两个代码块可以这样写:

1
2
set(SRC_LIST a.cpp b.cpp c.cpp)
add_executable(${PROJECT_NAME} ${SRC_LIST})

项目版本号和配置头文件

project()可以设置项目的版本号,例:

1
project(Tutorial VERSION 1.0)

configure_file()

1
2
3
4
5
6
7
8
9
10
11
12
configure_file(TutorialConfig.h.in TutorialConfig.h)

##configure_file(a.h.in b.h)用于复制a.h.in并粘贴至b.h,在复制过程中,如果a.h.in中定义了一些CMake变量,那么将会更新这些变量。

#.in只是一个表示这是输入文件的后缀

例如:
如果TutorialConfig.h.in中定义了项目版本号的CMake变量:
#define TUTORIAL_VERSION "@TUTORIAL_VERSION@"
那么在TutorialConfig.h重新生成时(复制后替换自身时),@TUTORIAL_VERSION@ 将会替换成当前的项目版本号,
即:
#define TUTORIAL_VERSION "1.0"

${PROJECT_BINARY_DIR}表示项目的二进制路径,exe文件会在此路径下生成

target_include_directories()

target_include_directories(目标名 作用域 包含目录)

为目标添加包含目录(使目标能访问目录里的文件,比如头文件)

作用域

说明
PRIVATE 包含目录仅对当前目标可见,不会传递给依赖该目标的其他目标。
INTERFACE 包含目录对当前目标不可见,但会传递给依赖该目标的其他目标。这通常用于库目标,以导出其公共头文件。
PUBLIC 包含目录对当前目标可见,并且也会传递给依赖该目标的其他目标。这通常用于同时需要包含目录进行编译和导出其头文件的情况。

例:

1
2
3
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
) #使得该项目能使用目录下的头文件或其他文件

版本控制

创建一个用于控制版本和保存配置信息的头文件(后文将称之为配置头文件,一般与项目同名),包含以下内容(示例):

1
2
3
4
// the configured options and settings for myproject
#define myproject_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define myproject_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define myproject_VERSION_PATCH @PROJECT_VERSION_PATCH@

配置CMake时,用set(),configure_file()对该头文件重新生成。

后续运行时,可通过包含配置头文件,使用诸如

1
cout << argv[0] << " Version " << myproject_VERSION_MAJOR << "."<< myproject_VERSION_MINOR << std::endl;

这样的代码打印版本号

编译时间戳

有时候我们需要知道编译时的时间戳,并在程序运行时打印出来。

string()

1
2
3
4
5
6
string(TIMESTAMP COMPILE_TIME %Y%m%d-%H%M%S)
#TIMESTAMP 用于获取时间戳
#COMPILE_TIME 是一个变量名
#%Y%m%d-%H%M%S 是一种时间格式

#整个string()的意思是生成一个格式为%Y%m%d-%H%M%S的格式化字符串的时间戳,并将其保存到COMPILE_TIME变量中

在配置信息头文件中添加内容:

1
2
3
4
5
6
// the configured options and settings for myproject
#define myproject_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define myproject_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define myproject_VERSION_PATCH @PROJECT_VERSION_PATCH@

#define TIMESTAMP @COMPILE_TIME@

构建完后,会变为(示例):

1
#define TIMESTAMP 20230220-203532

之后打印此值即可

指定C++标准

例:

1
2
3
4
5
#设置C++标准为C++17
set(CMAKE_CXX_STANDARD 17)
#强制要求编译器必须支持参数CMAKE_CXX_STANDARD指定的C++标准
set(CMAKE_CXX_STANDARD_REQUIRED True)

从 GCC 6.1 开始,当不指定任何版本 C++ 标准时,默认版本是 C++ 14

添加库

基本命令

add_library()
1
2
3
4
5
6
7
8
9
10
#库在名为MathFunctions的目录,目录中有同名头文件和一个源文件mysqrt.cpp

#添加一个叫 MathFunctions 的库文件。
add_library(MathFunctions mysqrt.cpp)

#add_library命令用于定义一个库。当执行add_library(MathFunctions mysqrt.cpp)时,CMake会创建一个名为MathFunctions的库,并将mysqrt.cpp文件编译为该库的一部分。

#一旦库被定义,就可以在其他目标(如可执行文件或其他库)中使用target_link_libraries命令来链接这个库。

#这里使用 add_library 命令生成的 MathFunctions 库是 静态链接库
add_subdirectory()

add_subdirectory命令用于向当前项目添加一个子目录。

target_link_libraries命令用于为指定的目标(如可执行文件或库)添加需要链接的库。

target_link_libraries(目标 作用域 一个或多个库)

1
target_link_libraries(MyExecutable PRIVATE MathFunctions)
target_include_directories()

target_include_directories命令用于为指定的目标(如可执行文件或库)添加包含目录。这些包含目录是编译器在查找头文件时应该搜索的路径。

1
2
3
target_include_directories(MyTarget PRIVATE include)
#MyTarget是一个之前定义的目标(比如通过add_executable或add_library命令创建的可执行文件或库)
#include是一个相对于当前CMakeLists.txt文件的路径,指向包含头文件的目录。

流程示例

  1. add_library(MathFuntions mysqrt.cpp)创建一个名为MathFuntions的库,同时将mysqrt编译为库的一部分。

  2. add_subdirectory(MathFunctions)将库所在的目录添加为顶级目标的子目录。

  3. add_executable()为顶级目标生成exe

  4. target_link_libraries()使顶级目标链接MathFunctions库

  5. target_include_directories 将 MathFunctions 添加为头文件目录

    代码示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    add_subdirectory(MathFunctions)

    add_executable(${PROJECT_NAME} myproject.cpp)

    target_link_libraries(${PROJECT_NAME} PUBLIC MathFunctions)

    target_include_directories(${PROJECT_NAME} PUBLIC
    ${PROJECT_BINARY_DIR}
    ${PROJECT_SOURCE_DIR}/MathFunctions
    )

将库设置为可选项

基本命令

option()

option(选项名称 选项描述(文档) 默认值(ON/OFF))

1
2
3
option(USE_MYMATH "Use tutorial provided math implementation" ON)

#默认值若不指定则为OFF
list()

list()用于操作列表变量

具体操作如下:

操作 代码 说明
创建或追加 list(APPEND [ …]) 将一个或多个元素追加到名为 <list_var> 的列表变量中。如果列表不存在,它将被创建。
获取长度 list(LENGTH ) 计算名为 <list_var> 的列表的长度,并将结果存储在 <output_var> 变量中。
获取元素 list(GET [ …] ) 从名为 <list_var> 的列表中获取一个或多个指定索引处的元素,并将结果存储在 <output_var> 变量中。索引是从 0 开始的。
插入元素 list(INSERT [ …]) 在名为 <list_var> 的列表的指定索引处插入一个或多个元素。
移除元素 list(REMOVE_ITEM [ …])
或者list(REMOVE_AT [ …])
这两个命令分别用于从名为 <list_var> 的列表中移除指定值或指定索引处的元素。
反转列表 list(REVERSE )
排序 list(SORT ) 对名为 <list_var> 的列表进行排序
遍历 使用foreach()

流程示例

1、修改CMakeList.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
option(USE_MYMATH "Use tutorial provided math implementation" ON)

if(USE_MYMATH)
add_subdirectory(MathFunctions)

#使用列表变量EXTRA_LIBS装所有要链接的库
list(APPEND EXTRA_LIBS MathFunctions)
#使用列表变量EXTRA_INCLUDES装所有要链接的库所在的目录
list(APPEND EXTRA_INCLUDES ${PROJECT_SOURCE_DIR}/MathFunctions)
endif()

add_executable(${PROJECT_NAME} myproject.cpp)

#链接EXTRA_LIBS中的所有库
target_link_libraries(${PROJECT_NAME} PUBLIC ${EXTRA_LIBS})

#使顶级目标包含EXTRA_INCLUDES中的所有目录
target_include_directories(${PROJECT_NAME} PUBLIC
${PROJECT_BINARY_DIR}
${EXTRA_INCLUDES}
)

2、在顶级目标源文件中添加预命令

1
2
3
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif

还可以添加优先选择逻辑:

1
2
3
4
5
#ifdef USE_MYMATH
const double outputValue = mysqrt(inputValue);
#else
const double outputValue = sqrt(inputValue);
#endif

3、修改配置头文件,添加定义:

1
2
// myproject.h.in
#cmakedefine USE_MYMATH

添加库的使用要求

基本命令

target_compile_definitions()

该命令用于为目标添加编译时定义。这些定义在编译时会被传递给编译器,通常用于设置宏或条件编译标志。

target_compile_definitions( [ …])

1
2
target_compile_definitions(MyTarget PRIVATE MY_MACRO)
#为目标 MyTarget 添加编译定义 MY_MACRO
target_compile_options()

为目标设置编译选项。这些选项会直接传递给编译器,允许你指定额外的编译标志,如优化级别、警告等级或调试信息等。

target_compile_options( [BEFORE] [ …])

1
2
target_compile_options(MyTarget PRIVATE -Wall -Wextra)
#为目标 MyTarget 添加编译选项 -Wall -Wextra

使用示例

在库的CMakeList.txt中加入这个:

1
2
3
4
5
6
# MathFunctions/CMakeLists.txt
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
#只要引用该库,顶级目标就会自动包含该库的目录
#所以上文中用于装目录的EXTRA_INCLUDES变量可以丢了

CMake
https://fiy-pc.github.io/2024/10/29/CMake/
作者
FIY-pc
发布于
2024年10月29日
许可协议