注释

单行注释使用#,多行注释使用[=[开始,使用]=]结束,其中等号可以是任意数量的,但是开始和结束的等号数量必须一致。

指令

执行指令是CMake列表文件的基本功能,提供它的名称,后面跟着小括号,在小括号中可以包含一个以空格为分隔的参数列表,例如message("hello" world)
指令名不区分大小写,但一般约定使用小写并以下划线连接。

指令参数

在底层,CMake识别的唯一数据类型只有字符串,但是静态的字符串并不好用,所以CMake提供了三种类型的参数。

类型 说明
方括号参数 方括号参数不会求值,只用于将多行字符串作为单个参数传递给命令,和多行注释一样,使用两层方括号标识。
引号参数 类似于C++中的常规字符串,同时可以支持将变量引用包装进字符串中,比如${target}这种。
非引号参数 和引号参数差不多,都可以当作常规字符串使用,但是要小心使用;,因为分号会被当作参数间的分隔符。

变量

CMake中有三类变量——普通变量、缓存变量和环境变量。

  • 变量名区分大小写,可以包括任何字符
  • 变量在内部都是作为字符串存储,有些时候又可以解释为其他数据类型的值
  • 基本的变量操作指令是set()unset(),分别表示设置变量和取消变量。但也可以被其他指令影响,比如string()list()

变量引用

通过$符号和大括号来使用变量引用。比如

1
2
3
set(name "xiaoming")

message(STATUS ${name})

打印出来的就是字符串"xiaoming"。
CMake会将${}包裹的内容查找其对应的字符串,然后做简单的替换,并且可以支持嵌套,例如

1
2
3
4
5
set(MyInner "hello")

set(MyOuter "${My")

message("${MyOuter}Inner} World")

输出的是"Hello World",首先将MyOuter替换成"${My",然后"${My"和"Inner}“组成了”${MyInner}",又被替换成了Hello,所以最终打印的是Hello World。

当涉及到变量类型时,

  • ${}用于引用普通变量和缓存变量。
  • $ENV{}用于引用环境变量
  • $CACHE{}用于引用缓存变量。

设置环境变量时也需要添加"ENV"关键字,比如set(ENV{CXX} "clang++")。设置的环境变量只会影响CMake的项目配置,不会真正改变系统变量。
缓存变量会保存在CMakeCache.txt文件中,包含在项目配置阶段收集的信息,比如编译器的路径、链接器的路径、GUI中用户设置的信息。
缓存变量的设置有些复杂,

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

缓存变量因为需要能够配置,GUI需要知道如何显示它,所以要求提供值,其中类型包括

  • BOOL: 布尔值,GUI上显示为一个复选框
  • FILEPATH: 磁盘上的文件路径,GUI将打开一个文件对话框
  • STRING: 字符串,GUI通过一个下拉菜单选择替换
  • INTERNAL: 一行字符串,GUI会跳过这个条目。

只是一个标签,GUI会显示在字段旁边,用于解释说明。

变量的作用域

CMake有两个作用域

  • 函数作用域:用于执行function()定义的自定义函数。
  • 目录作用域:当从add_subdirectory()指令执行嵌套目录中的CMakeLists.txt文件时。

当创建嵌套作用域时,CMake只用当前作用域的所有变量的副本填充,然后在退出嵌套的作用域时会删除副本,而原始的父作用域恢复。也就是说,在子作用域中即使使用unset取消了父作用域中的变量,其实也只是取消了其副本,父作用域中的变量不会受到影响。如果确实想要取消或设置父作用域中的变量,需要在后面加上关键字,set(parent_value "new value" PARENT_SCOPE),不过需要注意的是这样设置的是父作用域中的变量,而当前作用域中的变量不会被修改。

列表

set的变量后有多个参数时,这个变量就是一个列表了,例如set(MyList 1 2 3 4 5)就是一个有5个元素的列表。
CMake还提供了list指令来操作列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 部分用法
list(LENGTH <list> <out_var>)
list(GET <list> <element index> [<index> ...] <out_var>)
list(JOIN <list> <glue> <out_var>)
list(SUBLIST <list> <begin> <length> <out_var>)
list(FIND <list> <value> <out-var>)
list(APPEND <list> [<element> ...])
list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>)
list(INSERT <list> <index> [<element> ...])
list(POP_BACK <list> [<out-var> ...])
list(POP_FRONT <list> [<out-var> ...])
list(PREPEND <list> [<element> ...])
list(REMOVE_ITEM <list> <value>...)
list(REMOVE_AT <list> <index>...)
list(REMOVE_DUPLICATES <list>)
list(TRANSFORM <list> <ACTION> [...])
list(REVERSE <list>)
list(SORT <list> [...])

控制结构

CMake中也有条件块、循环和自定义指令。

条件块

CMake中的条件块必须使用endif()来关闭,可以有任意数量的elseif()和一个可选的else(),顺序如下

1
2
3
4
5
6
7
if(<condition>)
<commands>
elseif(<condition>)
<commands>
else()
<commands>
endif()

判断的条件支持NOT,AND和OR逻辑运算符,当然条件也可以是一个变量,不过如果是变量引用的话,其值为ON、Y、YES、TRUE或者一个非零的数才会判断为真。而如果是一个没有加引用的裸变量,将会判断其值为OFF,NO,FALSE,N,IGNORE,NOTFOUND,以-NOTFOUND结尾的字符串,空字符串或者零的其中一个时(不区分大小写),条件才为false。
另外还可以通过EQUAL,LESS,LESS_EQUAL,GREATER和GREATER_EQUAL来进行比较判断。

循环

CMake支持while()foreach()两种循环指令。break()可以停止循环并跳出,continue()会跳过当前循环执行下次循环。
同样的,也需要end来指明循环的范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
while(<condition>)
<commands>
endwhile()

foreach(<loop_var> RANGE <max>)
<commands>
endforeach()

foreach(VAR 1 2 3 4 5)
<commands>
endforeach()

set(L1 "one;two;three;four")
set(L2 "1;2;3;4;5")
foreach(num IN ZIP_LISTS L1 L2)
message("num_0=${num_0}, num_1=${num_1}")
endforeach()

foreach(word num IN ZIP_LISTS L1 L2)
message("word=${word}, num=${num}")
endforeach()

CMake 将为每个提供的列表创建一个 num_<N> 变量,用每个列表中的项填充该变量。可以传递多个 <loop_var> 变量名 (每个列表一个),每个列表将使用单独的变量来存储。

定义指令

CMake可以通过macro()function()来自定义指令,类似于写一个自己的函数,其中macro()类似于宏,只是执行简单的替换,而fuction()类似于调用一个函数,并会创建一个自身的作用域。
这两个指令都允许传递参数,CMake中允许通过下列引用来访问命令调用中传递的参数:

  • ${ARGC}: 参数的数量
  • ${ARGV}: 所有参数的列表
  • ${ARG0}, ${ARG1}: 特定索引处的实参值
1
2
3
4
5
6
7
macro(<name> [<argument>...])
<commands>
endmacro()

function(<name> [<argument>...])
<commands>
endfunction()