CMake 和 make,shell 脚本一样,本质是一种 DSL 语言。在了解 CMake 的基本概念和用法之后,作为一种编程语言,还是得从最基本的变量,流程控制(for 循环,if 条件),函数等开始学习。在最开始,我们强调一点——CMake 作为一门语言是区分大小写的!只是具体到通常使用的内置命令/自定义函数/自定义宏,不区分大小写。

不得不说,CMake 这一类文本化的语言语法都非常反人类,而且CMake 的官方文档写的真是垃圾中的垃圾。

本文的主要内容

  • 调试输出(IO)
  • 变量
  • 字符串操作
  • 列表操作
  • 数学表达式

调试输出

在 CMakeLists 中,有着和 echo 类似的用于向控制台输出信息的 message 命令

1
message([<mode>] "message text")

其中的 mode 通常为

  • NOTICE: 缺省时默认的选项,表示正常输出到控制台的重要提示信息
  • WARNING: 表示输出到控制台的警告信息,但不会中断 CMake 的运行
  • STATUS: 表示正常输出到控制台的一般提示性信息,和 CMake 自动输出的提示信息一样,每一条自动以--开头,通常不需要关注
  • FATAL_ERROR: 表示致命错误,CMake 通常不会执行到此,如果执行到了这条语句,就会输出这里的信息并停止生成构建系统

message 命令会在生成构建系统时输出信息,而不是在编译阶段输出信息,例如正常输出

1
2
3
4
5
6
7
# message("hello,cmake")
# message(NOTICE "hello,cmake NOTICE")
# message(STATUS "hello,cmake STATUS")

hello,cmake
hello,cmake NOTICE
-- hello,cmake STATUS

报错输出

1
2
3
4
5
6
# message(FATAL_ERROR "hello,cmake")
CMake Error at CMakeLists.txt:9 (message):
hello,cmake


-- Configuring incomplete, errors occurred!

我们可以在 CMakeLists.txt 中利用 message 命令输出各种 CMake 变量的具体信息,有助于我们了解当前 CMake 的状况,还可以在 CMake 执行到关键部分时,输出相应的提示信息。

变量

变量介绍

CMake 把变量分成普通变量、缓存变量和环境变量三类:

  • 普通变量的含义是在多次生成构建系统的过程中,CMake 并没有记住这个变量,而是每一次构建时重新处理,普通变量还有作用域,并不是全局有效的,例如自定义函数中会有独立的变量作用域
  • 缓存变量会被 CMake 通过缓存文件 CMakeCache.txt 记下来,在多次生成构建系统时可以重复地、全局地使用
  • 环境变量就是当前 CMake 进程中获取的环境变量,我们可以获取并使用,也可以进行临时性的修改(不建议)

注意这里的 CMake 变量和 C++预处理的宏不是一回事,CMake 不会把自己的变量传递给编译器,如果希望给编译器传递相关的宏,需要使用target_compile_definitions之类的命令

在 CMake 这种 DSL 语言中,变量和字符串总是容易混淆的东西,并没有建立一个完整的类型系统,因此语法非常反人类:

  • 关于字符串和字符串列表:
    • 对于不含空格的单个字符串,加不加引号对于 CMake 来说都一样
    • 对于含有空格的情况,空格在不加引号时会被视作分隔符,在引号内则不会,例如A B C被视作三个字符串,"A B C"被视作一个字符串
    • 无论加不加引号,;都会被视作字符串列表中的分隔符
    • 单个字符串被视作只有一个元素的字符串列表,因此使用 CMake 的列表操作也是可以的
    • 字符的处理: 转义字符加双斜杠,例如"ABC\\nDEF";特殊字符需要加单斜杠,如 "ABC\"D",在 CMake 中的路径分隔符应当使用/表示
  • 变量的名称: 几乎可以由任何文本组成,建议简单使用字母、数字、-_组成,CMake 内部的习惯是纯大写加下划线
  • 变量的值: 值在本质上都是字符串或字符串列表
    • 其中的0-9字符可以被解释为数字
    • 其中的 TRUE/FALSE,ON/OFF,YES/NO,Y/N 可以被解释为布尔变量,此时不区分大小写,建议使用 ON/OFF
    • 由于访问变量的本质是字符串的展开替换,${var}不同于"${var}",可能被拆成多个传递,建议把访问后的值加引号,避免值在解析时被错误地拆开

${var}不同于"${var}"(其中的 var 是一个列表),在某些情形下会因为解析逻辑不同,得到不一样的处理结果,例如

1
2
3
4
5
6
7
8
set(specialStr "aaa;bbb")

message(${specialStr})
# 等于 message(aaa bbb)
# aaabbb 这里把字符串列表进行了拼接

message("${specialStr}")
# aaa;bbb 这里没有处理,含分隔符原样输出

普通变量

我们可以在 CMakeLists 中使用set()命令定义一个普通变量,赋予它一个值(字符串或字符串数组),例如

1
2
set(Var "value")
set(Var value)

通常使用${Var}访问变量,以字符串替换的形式获取变量的值。

1
2
message("Var=${Var}")
# Var=value

对于字符串列表,可以用很多种等价的定义形式,其中的;被用作字符串的分隔符。

1
2
3
4
5
6
# 对字符串用分号分隔代表列表
set(Var_A "v1;v2;v3")
# 等效于其它几种形式
set(Var_A v1 v2 v3)
set(Var_A v1;v2;v3)
set(Var_A "v1" "v2" "v3")

在变量列表被整体访问时也会得到使用;进行分隔的整体,例如

1
2
message("Var_A=${Var_A}")
# Var_A=v1;v2;v3

下面这种形式不是列表,空格被保存在了 Var_B 的值中,不视作字符串列表的分隔符

1
2
3
4
set(Var_B "v1 v2 v3")

message("Var_B=${Var_B}")
# Var_B=v1 v2 v3

可以使用unset命令撤销变量的定义,或者可以把变量修改为空字符串,效果一样(CMake 对于未定义变量的解析结果就是空字符串)

1
2
3
unset(Var)
set(Var "")
message("Var=${Var}") # Var=

CMake 非常反人类的语法是,如果 Var 这个变量没有定义,那么直接读取${Var}也不会报错,会正常地返回一个空字符串。

特别注意,在后面的 IF 语句等结构中,IF 后面可以直接使用变量名Var,而不是用${Var},这个语法糖是因为历史原因,IF 语句的出现比${}还要早。

可以使用下面的语句来判断是否定义了某个普通变量,如果没有则使用默认值来定义

1
2
3
if(NOT DEFINED ABC)
set(ABC ON)
endif()

普通变量具有独立的作用域,父作用域的普通变量在子作用域中可以访问,但是在子作用域中的修改不会反馈到父作用域中(可以通过 PARENT_SCOPE 选项反馈到上一层作用域),详见函数的普通变量。

缓存变量

缓存变量介绍

CMake 第一次构建时会生成 CMakeCache.txt,基于 CMakeCache.txt 存储缓存变量。 此后的构造则会偷懒跳过很多步骤,直接从 CMakeCache.txt 获取缓存变量。(如果缓存错误,可以直接修改或删除 CMakeCache.txt,也可以删除整个 build 文件夹,让 CMake 一切重新开始)

缓存变量的常见类型:

  • BOOL: 布尔值 ON/OFF
  • FILEPATH: 文件路径
  • PATH: 目录路径
  • STRING: 字符串

缓存变量的创建和修改:

  1. 在生成构建系统的 cmake 命令中,附加的-D可以直接定义或修改缓存变量,或者使用-U撤销缓存变量,包括最常见的两个缓存变量的设置
    1. CMAKE_BUILD_TYPE 编译类型(Debug/Release 等)
    2. CMAKE_INSTALL_PREFIX 安装目录前缀
  2. 在 CMakeLists 中
    1. 使用set(...CACHE...)定义缓存变量
      • 注意只能在缓存变量不存在时定义(相当于提供缓存变量的默认值)
      • 如果缓存变量已经存在于 CMakeCache.txt 中,则这条命令没有修改能力,被直接忽略
    2. 在 CMakeLists 中使用set(...CACHE...FORCE)强制定义或修改缓存变量,此时无论缓存变量是否存在,都会生效
  3. 简单粗暴的方式:直接编辑 CMakeCache.txt 来定义或修改缓存变量(这也是有效的,只需要保证正确的数据格式)

缓存变量的获取:(全局可见)

  1. 使用${Var}获取名为 Var 的普通变量/缓存变量,如果存在恰好同名的普通变量,会优先于缓存变量被读取(因为普通变量是局部的,访问优先级更高)
  2. 使用if(DEFINED Var)也一样,如果定义了名为 Var 的普通变量或缓存变量都可以
  3. 使用$CACHE{Var}强制获取名为 Var 的缓存变量,不会被同名的普通变量替代

缓存变量的意义: cmake 可以记住第一次通过命令行-D选项定义的变量和值(通过命令行定义的变量自动是缓存变量),使得第一次生成之后不需要重复输入繁琐的命令,再次生成时不需要重复定义。 再次生成时使用命令行-D选项可以赋值或修改已存在的缓存变量,赋值和修改都在 CMakeCache,txt 文件中进行。保证 CMakeLists 中的直接赋值比命令行-D有着更低的修改权限(从机制上避免出现虽然用-D修改,又因为 CMake 执行 CMakeLists 中的命令,把-D的修改自动抹掉的情况),但是仍然提供了缓存变量修改的最高权限——通过 CMakeLists 中的 FORCE 修改。

缓存变量还可以进一步分为:

  1. 外部缓存变量(EXTERNAL cache entries)
    • CMake 创建的外部缓存变量
    • 用户创建的外部缓存变量
  2. 内部缓存变量(INTERNAL cache entries)

可以直观地在 CMakeCache.txt 文件中发现,确实是按照上述结构进行的存储, 例如 CMAKE_BUILD_TYPE 就是 CMake 创建的外部缓存变量。

不太了解它们之间的区别,可能只是来源?或者对于外部的缓存变量,CMake 可能更加关注,比如有没有使用等。

命令行方式

在命令行中,使用-D选项创建缓存变量的具体语法和例子如下,可以附带缓存变量的类型

1
2
3
4
5
cmake -Bbuild -D <var>:<type>=<value>, <var>=<value>

cmake -Bbuild -DCMAKE_BUILD_TYPE=Release

cmake -Bbuild -DMY_CACHE_VAR:STRING=1 -DVAR:BOOL=ON

我们可以在 CMakeCache.txt 中看到自定义的条目,例如

1
2
//No help, variable specified on the command line.
MY_CACHE_VAR:STRING=1

可以使用-U选项删除缓存文件中的缓存变量,支持按照匹配模式批量删除(但是得留意 CMakeLists 是不是又自动加进来了)

1
cmake .. -U <globbing_expr>

CMakeLists 方式

在 CMakeLists 中使用set(...CACHE...)创建缓存变量的语法为

1
set(<variable> <value> ... CACHE <type> <docstring> [FORCE])

其中的CACHE是必要的,<docstring>是缓存变量的描述语句,<type>代表缓存变量的类型。

例如

1
2
set(MY_CACHE_VALUE "value" CACHE STRING "Value Created by Me")
message("MY_CACHE_VALUE: ${MY_CACHE_VALUE}")

在 CMakeLists 中使用set(...CACHE...FORCE)命令强制修改缓存变量的值,例如

1
2
set(MY_CACHE_VALUE "value" CACHE STRING "Value Created by Me 2" FORCE)
message("MY_CACHE_VALUE: ${MY_CACHE_VALUE}")

还有一个很常用的 option 命令,它是定义 BOOL 类型的缓存变量的语法糖

1
2
3
4
5
6
7
option(<variable> "<help_text>" value)

# 相当于
set(<variable> value CACHE BOOL "<help_text>")

# 例如
option(TEST_DEBUG "option for debug" ON)

环境变量

cmake 可以使用或修改当前进程中是环境变量,环境变量的修改是临时性的,只在当前的 cmake 进程中有效,建议不要乱用环境变量。

设置环境变量

1
set(ENV{DEMO_VAR} "hello")

获取环境变量

1
$ENV{DEMO_VAR}

和缓存变量不同,无法在缺省 ENV 关键词的情况下获取到环境变量的值。

撤销环境变量

1
unset(ENV{DEMO_VAR})

例如获取一个系统预设的环境变量 HOME,得到的结果为

1
2
3
if(DEFINED ENV{HOME})
message("HOME=$ENV{HOME}") # HOME=/home/username
endif()

常见变量

下面提供一些常见的变量。(很多是缓存变量,可以在 CMakeCache.txt 找到)

版本号

版本号格式为major[.minor[.patch[.tweak]]],一共包括四个数字,不足的部分末尾补 0。

可以在 project 命令中指定项目的版本,在 CMake 中,可以使用如下变量获取当前的版本号

  • PROJECT_VERSION 完整的版本号
  • PROJECT_VERSION_MAJOR 主版本号,第一个数字
  • PROJECT_VERSION_MINOR 第二个数字
  • PROJECT_VERSION_PATCH 第三个数字
  • PROJECT_VERSION_TWEAK 第四个数字

目录相关变量

  • CMAKE_MODULE_PATH: cmake 查找.cmake模块的目录,可以使得 include 命令不需要添加搜索目录
  • CMAKE_INSTALL_PREFIX: cmake 安装位置前缀
  • CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT: 布尔变量,表明当前的安装位置前缀是否被设置,还是仍然为默认值
  • PROJECT_NAME:当前项目名称,CMAKE_PROJECT_NAME: 根项目名称;
  • PROJECT_BINARY_DIR, <projectname>_BINARY_DIR, CMAKE_BINARY_DIR: 项目的编译目录,通常为生成时指定的/build子目录,三者的细微区别由前缀体现。
  • PROJECT_SOURCE_DIR, <projectname>_SOURCE_DIR, CMAKE_SOURCE_DIR: 项目的源文件目录,通常为 project 命令的 CMakeLists.txt 所在目录,三者的细微区别由前缀体现。(建议不要使用 CMAKE_SOURCE_DIR
  • CMAKE_CURRENT_SOURCE_DIR, CMAKE_CURRENT_LIST_DIR:正在处理的 CMakeLists.txt 所在目录,两者可能略有区别,建议使用后者,尤其在依赖管理时的目录

注:上文中不同前缀对应的细微区别如下

  • CMAKE_ 通常指的是根项目的属性,建议不要直接使用,因为这使得根项目无法作为子项目存在。(根项目指的是启动 CMake 时的 CMakeLists.txt 的第一个项目)
  • PROJECT_ 当前项目的属性,如果只有一个项目,则与CMAKE_相同;如果存在子项目则不同。(当前项目指的是最近一次调用的project(...)
  • <projectname>_ 指定某个具体的项目的属性

特性相关变量

  • CMAKE_CXX_COMPILER_ID: 编译器的 ID,例如"MSVC","GNU","Clang"
  • CMAKE_GENERATOR: 构建系统
  • CMAKE_BUILD_TYPE: 构建模式,debug/release 等
  • CMAKE_CXX_STANDARD: c++标准,例如 20 代表 c++20
  • CMAKE_CXX_STANDARD_REQUIRED: 布尔变量,是否严格要求满足 c++标准
  • CMAKE_DEBUG_POSTFIX: debug 模式下会给生成的库赋予额外的后缀,便于区分,例如set(CMAKE_DEBUG_POSTFIX "_d")

鉴于 MSVC 和 Linux 上的构建系统有太多不一样,CMake 直接定义了如下变量,可以直接判断并进入不同的处理分支

1
2
3
4
5
if(MSVC)
# ...
else()
# ...
endif()

输出位置相关变量

见前文。

1
2
3
4
5
6
7
8
9
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/bin/debug")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/bin")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib")

编译选项相关变量

见前文。

1
2
3
4
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wfatal-errors -Wshadow -Wno-unused-parameter -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wfatal-errors -Wno-unused-parameter -Wshadow -O0")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wall -Wextra -Wfatal-errors -Wno-unused-parameter -Wshadow")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall -Wextra -Wfatal-errors -Wno-unused-parameter -Wshadow")

字符串操作

cmake 支持对字符串的简单操作。在这里我们使用<string>表示字符串值,使用<string-var>表示值为字符串的变量名称。

以如下的长字符串为例

1
2
3
4
5
set(S
"Pride and Prejudice is kind of a literary Rosetta Stone, the inspiration, basis, and model for so many modern novels. \
You’re probably more familiar with its plot and characters than you think. \
For a book written in the early 19th century, it’s modernity is surprising only until you realize that this is the novel that in many ways defined what a modern novel is.")
message("S: ${S}")

字符串访问与查找

LENGTH: 获取字符串的长度,结果存在 out-var 中

1
string(LENGTH <string> <out-var>)

注意这里不是给一个字符串变量,而是需要给一个字符串值,例如

1
2
3
4
set(S2 "abc e ")
string(LENGTH ${S2} N)
message("N=${N}")
# N=6

FIND: 在字符串中查找指定的子串,返回子字符串开头在原字符串中的索引,默认查找第一次出现的,也可以反向查找最后一次出现的,没有找到会返回-1

1
2
string(FIND <string> <substring> <out-var> [...])
string(FIND <string> <substring> <output_variable> [REVERSE])

例如

1
2
3
4
5
6
string(FIND ${S} "in" S_index)
string(FIND ${S} "in" S_index1 REVERSE)
message("S_index=${S_index},S_index1=${S_index1}")
# S_index=24,S_index1=339
# 第一个in是单词kind中的
# 最后一个in是单词defined中的

SUBSTRING: 子字符串提取,指定字串的开始索引和长度,结果存入 out-var

1
string(SUBSTRING <string> <begin> <length> <out-var>)

例如

1
2
3
string(SUBSTRING ${S} 0 8 S_HEAD)
message("S_HEAD=${S_HEAD}")
# S_HEAD=Pride an

字符串增加

APPEND: 在字符串变量的尾部添加字符串

1
string(APPEND <string-var> [<input>...])

例如

1
2
3
4
set(S2 "Hello")
string(APPEND S2 " Stone")
message("S2=${S2}")
# S2=Hello Stone

PREPEND: 在字符串变量的头部添加字符串

1
string(PREPEND <string-var> [<input>...])

例如

1
2
3
4
set(S2 "Hello")
string(PREPEND S2 "Stone ")
message("S2=${S2}")
# S2=Stone Hello

字符串替换

REPLACE: 将输入字符串<input>中所有出现的<match-string>替换为<replace_string>,并将修改后的结果存储在<output_var>中。

1
string(REPLACE <match-string> <replace-string> <out-var> <input>...)

例如

1
2
3
4
set(S2 "Hello,world!")
string(REPLACE "!" "?" S2_M ${S2})
message("S2_M=${S2_M}")
# S2_M=Hello,world?

字符串正则表达式替换

速成一下简单的正则表达式语法

  • ^: 匹配输入开头
  • $: 匹配输入结束
  • .: 匹配任意单个字符
  • \<char>: 匹配单字符<char>。使用它来匹配特殊的正则表达式字符,例如 \. 表示点,\\ 表示反斜杠,\a 表示 a
  • [ ]: 匹配任何在括号内的字符
  • [^ ]: 匹配任何不在括号内的字符
  • -: 用在方括号内,指定字符的范围,例如[a-f]表示[abcdef][0-3]表示[0123][+*/-] 表示数学运算符。
  • *: 匹配前面模式的零次或多次
  • +: 匹配前面模式的一次或多次
  • ?: 匹配前面模式的零次或一次
  • |: 匹配 | 两侧的模式
  • (): 保存匹配的子表达式(模式)

REGEX MATCH: 字符串正则匹配,将所有输入字符串<input>在匹配之前都连接在一起,然后根据正则表达式<regular_expression>匹配一次,将匹配的结果存储在<output_variable>

1
string(REGEX MATCH <regular_expression> <output_variable> <input> [<input>...])

例如可以匹配任何含有 in 的单词,但是注意到只会匹配一次

1
2
3
string(REGEX MATCH "[A-Za-z]*in[A-Za-z]*" S_out_var ${S})
message("S_out_var=${S_out_var}")
# S_out_var=kind

REGEX MATCHALL: 字符串正则匹配,和上面的区别就是匹配所有的项,结果以一个列表的形式返回

1
string(REGEX MATCHALL <regular_expression> <output_variable> <input> [<input>...])

例如可以匹配任何含有 in 的所有单词

1
2
3
string(REGEX MATCHALL "[A-Za-z]*in[A-Za-z]*" S_out_var ${S})
message("S_out_var=${S_out_var}")
# S_out_var=kind;inspiration;think;in;surprising;in;defined

REGEX REPLACE: 字符串正则替换,将所有输入字符串<input>在匹配之前都连接在一起,然后尽可能匹配<regular_expression>并替换为 <replacement_expression>,将结果存储在<output_variable>

1
string(REGEX REPLACE <regular_expression> <replacement_expression> <output_variable> <input> [<input>...])

例如把所有匹配到的含有 in 的单词,替换成 hello

1
string(REGEX REPLACE "[A-Za-z]*in[A-Za-z]*" "hello" S_out_var ${S})

字符串大小写转换

TOUPPER,TOLOWER: 修改字符串的大小写形式,结果存入 out-var

1
2
string(TOUPPER <string> <out-var>)
string(TOLOWER <string> <out-var>)

例如

1
2
3
4
5
6
7
set(S2 "aBc")
string(TOUPPER ${S2} S2_U)
string(TOLOWER ${S2} S2_L)
message("S2_U=${S2_U}")
message("S2_L=${S2_L}")
# S2_U=ABC
# S2_L=abc

字符串比较

对两个字符串按照字典序列比较,注意对大小写敏感,结果 true 或 false 存入最后的变量中

1
2
3
4
5
6
string(COMPARE LESS <string1> <string2> <output_variable>)
string(COMPARE GREATER <string1> <string2> <output_variable>)
string(COMPARE EQUAL <string1> <string2> <output_variable>)
string(COMPARE NOTEQUAL <string1> <string2> <output_variable>)
string(COMPARE LESS_EQUAL <string1> <string2> <output_variable>)
string(COMPARE GREATER_EQUAL <string1> <string2> <output_variable>)

列表操作

cmake 支持对值为字符串列表的变量(通常是文件名列表,或目录列表)使用简单的操作,这部分内容主要参考这篇博客

列表访问与查询

LENGTH: 获取列表的长度,会把 list 的长度赋值给 out-var

1
list(LENGTH <list> <out-var>)

例如

1
2
3
list(LENGTH A Alen)
message("Alen=${Alen}")
# Alen=7

GET: 获取列表指定索引的元素,索引从 0 开始,0 代表第一个元素,还支持反向索引,-1 代表最后一个元素

1
list(GET <list> <element index> [<index> ...] <out-var>)

这里既可以只用一个索引,得到的单个值保存在 out-var,也可以使用多个索引,得到的值列表保存在 out-var,例如

1
2
3
4
5
set(A a b c d e)
list(GET A 1 X)
list(GET A -1 -2 Y)
message("X=${X},Y=${Y})
# X=b,Y=e;d

FIND: 在列表中查找指定元素,返回列表指定元素的索引,如果未找到返回 -1

1
list(FIND <list> <value> <out-var>)

例如

1
2
3
4
set(A a b c d e)
list(FIND A d Id)
message("Id=${Id}")
# Id=3

列表增加

APPEND: 在列表尾部增加元素

1
list(APPEND <list> <values>

例如

1
2
3
4
set(A a b c d e)
list(APPEND A f g)
message("A=${A}")
# A=a;b;c;d;e;f;g

PREPEND: 在列表的头部添加元素

1
list(PREPEND <list> [<element>...])

例如

1
2
3
4
set(A a b c d e)
list(PREPEND A f g)
message("A=${A}")
# A=f;g;a;b;c;d;e

IINSERT: 在列表指定索引位置插入元素

1
list(INSERT <list> <index> [<element>...])

例如

1
2
3
4
5
6
7
set(A a b c d e)
message("A=${A}")
list(INSERT A 0 w)
message("A=${A}")

# A=a;b;c;d;e
# A=w;a;b;c;d;e

列表删除

REMOVE_ITEM: 按照值删除,可以同时删除多个值

1
list(REMOVE_ITEM <list> <value>...)

REMOVE_AT: 按照索引删除,可以同时删除多个索引

1
list(REMOVE_AT <list> <index>...)

REMOVE_DUPLICATES: 列表去重,保持相对顺序

1
list(REMOVE_DUPLICATES <list>)

POP_BACK,POP_FRONT: 以栈的形式删除列表的尾部或头部的若干元素(默认只删除一个,但是如果后接 m 个变量,则一次性删除 m 个,并且把值赋给它们)

1
2
list(POP_BACK <list> [<out-var>...])
list(POP_FRONT <list> [<out-var>...])

数学表达式

既然是把 cmake 作为一门语言,那么基本的数学运算也搞一搞吧。

1
math(EXPR <var> "<expression>" [OUTPUT_FORMAT <format>])

计算的表达式 expression 需要以字符串的形式给出,支持数字和运算符(C 语言风格),计算的结果会保存到 var 中,甚至可以加入一些输出格式要求,例如

1
2
3
math(EXPR value "3+2")
message("3+2=${value}")
# 3+2=5

甚至可以加上流程控制,达到更复杂的运算

1
2
3
4
5
6
7
8
9
10
11
set(value 0)
while(${value} LESS 5)
math(EXPR value "${value}+1")
message("value=${value}")
endwhile()

# value=1
# value=2
# value=3
# value=4
# value=5

当然,即使 cmake 作为一门 DSL 语言啥都不缺,但肯定没有人会把复杂的编程内容通过 cmake 语言实现。