目录

CMake变量

变量基础

和任何其他计算机语言一样,变量是CMake的基石。定义变量的最基本方法是使用set命令。其形式如下:

1
set(varName value... [PARENT_SCOPE])

变量的名称可以包含字母、数字和下划线,字母是区分大小写的。名称中还可以包含字符./-+,但在实践中很少见到。像其他语言一样,在CMake中一个变量有一个特定的作用域。变量不能在其作用域之外被读取或修改。

CMake将所有变量视为字符串。当设置一个变量的值时,CMake不要求这些值加引号,除非该值包含空格。如果给出了多个值,这些值将被连接在一起,每个值之间用分号隔开。举例如下:

1
2
3
4
5
set(myVar a b c) # myVar = "a;b;c"
set(myVar a;b;c) # myVar = "a;b;c"
set(myVar "a b c") # myVar = "a b c"
set(myVar a b;c) # myVar = "a;b;c"
set(myVar a "b c") # myVar = "a;b c"

变量的值用${myVar}获得,它可以用在任何字符串或变量可以出现的地方。CMake特别灵活,因为它还可以递归使用这种形式,或者指定要设置的另一个变量的名称。此外,CMake不要求在使用变量之前对其进行定义。使用未定义的变量只会得到一个空字符串,这与Unix shell脚本很相似。

1
2
3
4
5
6
7
set(foo ab) # foo = "ab"
set(bar ${foo}cd) # bar = "abcd"
set(baz ${foo} cd) # baz = "ab;cd"
set(myVar ba) # myVar = "ba"
set(big "${${myVar}r}ef") # big = "${bar}ef" = "abcdef"
set(${foo} xyz) # ab = "xyz"
set(bar ${notSetVar}) # bar = ""

字符串可以有多行,也可以包含引号,这需要用反斜线来转义。

1
2
3
set(myVar "goes here")
set(multiLine "First line ${myVar}
Second line with a \"quoted\" word")

如果使用CMake 3.0或更高版本,可以使用方括号语法包含多行字符串。其中内容的开始用[=[,结束用]=]标记。方括号之间可以出现任意数量=,只要开始和结束使用相同数量的=。如果开头的方括号后面紧跟着一个换行符,那么第一个换行符会被忽略,但后面的换行符不会被忽略。此外,对括号内的内容不做进一步的转换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Simple multi-line content with bracket syntax,
# no = needed between the square bracket markers
set(multiLine [[
First line
Second line
]])

# Bracket syntax prevents unwanted substitution
set(shellScript [=[
#!/bin/bash

[[ -n "${USER}" ]] && echo "Have USER"
]=])

# Equivalent code without bracket syntax
set(shellScript
"#!/bin/bash

[[ -n \"\${USER}\" ]] && echo \"Have USER\"
")

取消一个变量的值有2种方式,一是通过unset命令,一是调用set命令而不给变量值。下面的方法是等价的:

1
2
set(myVar)
unset(myVar)

环境变量

CMake使用$ENV{varName}获得环境变量的值,可以在任何可以使用常规变量的地方使用。设置环境变量的方法与设置普通变量的方法相同,只不过是设置ENV{varName}。

1
set(ENV{PATH} "$ENV{PATH}:/opt/myDir")

然而像这样设置环境变量只会影响到当前运行的CMake实例。一旦CMake运行结束,对环境变量的改变就会消失。特别是对环境变量的改变在构建时不可见。因此在CMakeLists.txt文件中设置环境变量很少有用。

缓存变量

与普通变量生命周期仅限于CMakeLists.txt文件的处理过程不同的是,缓存变量存储在构建目录下名为CMakeCache.txt的特殊文件中,它们在CMake运行期间持续存在。一旦被设置,缓存变量就会一直存在,直到有人明确地将它们从缓存中移除。缓存变量的值与普通变量的获取方式完全相同,但set命令在设置缓存变量时有所不同。

1
set(varName value... CACHE type "docstring" [FORCE])

当CACHE关键字存在时,set命令将设置一个名为varName的缓存变量。缓存变量比普通变量有更多的附加信息,包括一个名义类型和一个文档字符串。在设置缓存变量时必须提供这两个信息,尽管文档字符串可以为空。CMake在处理过程中总是将变量视为字符串,类型只是为了改善GUI工具的用户体验。类型必须是以下之一:

BOOL

缓存变量是一个布尔型。GUI工具使用一个复选框或类似的东西来表示这个变量。变量值为CMake支持的布尔字符串值。

FILEPATH

缓存变量代表磁盘上文件的路径。GUI工具向用户提供一个文件对话框来修改该变量的值。

PATH

像FILEPATH,但GUI工具呈现的是一个选择目录而不是文件的对话框。

STRING

该变量被视为一个任意的字符串。默认情况下,GUI工具使用一个单行文本编辑部件来操作变量的值。工程可以使用缓存变量属性,为GUI工具提供一组预定义的值,以组合框或类似的方式呈现。

INTERNAL

内部缓存变量不打算提供给用户使用,用于持久地记录项目的内部信息,例如缓存密集查询或计算的结果。GUI工具不显示INTERNAL变量。

设置一个布尔型缓存变量是一个非常普遍的需求,为此目的CMake提供了一个单独的命令option。

1
option(optVar helpString [initialValue])

如果省略了initialValue,将使用默认值OFF。上面的内容等同于:

1
set(optVar initialValue CACHE BOOL helpString)

普通变量和缓存变量之间的一个重要区别是,set命令只有在FORCE关键字存在的情况下才会覆盖缓存变量,而不像普通变量那样,set命令会一直覆盖一个已经存在的值。

假如一个普通变量和一个缓存变量的名字相同,在这种情况下,${myVar}检索到的值是最后分配给myVar的值,不管它是普通变量还是缓存变量。

1
2
3
4
5
6
7
set(myVar foo) # Local myVar
set(result ${myVar}) # result = foo
set(myVar bar CACHE STRING "") # Cache myVar
set(result ${myVar}) # First run: result = bar
                     # Subsequent runs: result = foo
set(myVar fred)
set(result ${myVar}) # result = fred

操作缓存变量

使用set和option,一个项目可以建立一套有用的定制点,打开或关闭构建的不同部分,设置外部包的路径,修改编译器和链接器的标志等等。

在命令行中设置缓存值

CMake允许通过命令行选项直接操作缓存变量。

1
cmake -D myVar:type=someValue ...

和使用带有CACHE和FORCE选项的set命令是一样的。这个命令行选项只需要给出一次,因为它被保存在缓存中供以后的运行使用,因此不需要每次运行cmake都提供。可以提供多个-D选项在cmake命令行中一次设置多个变量。

在命令行上定义的缓存变量docstring为空,类型也可以省略,在这种情况下,变量会被赋予一个与INTERNAL类似的特殊类型。

1
2
3
4
5
cmake -D foo:BOOL=ON ...
cmake -D "bar:STRING=This contains spaces" ...
cmake -D hideMe=mysteryValue ...
cmake -D helpers:FILEPATH=subdir/helpers.txt ...
cmake -D helpDir:PATH=/opt/helpThings ...

如果设置含有空格的缓存变量,-D选项给出的整个值都要加引号。

使用-U选项可以从缓存中删除变量,可以根据需要删除多个变量。-U选项支持*和?通配符,但需要注意不要误删其他缓存变量。

1
cmake -U 'help*' -U foo ...

调试和诊断变量

CMake可以使用message命令打印信息或变量值。

1
message([mode] msg1 [msg2]...)

如果指定了一个以上的msg,它们将被连接成一个没有任何分隔符的字符串。这通常不是开发者的本意,所以更常见的用法是单个msg。消息中可以使用变量值,比如:

1
2
set(myVar HiThere)
message("The value of myVar = ${myVar}")

message命令接受一个可选的mode关键字,可识别的模式有:

STATUS

偶然的信息,信息前面通常会有两个连字符。

WARNING

CMake警告,通常以红色显示,处理继续。

AUTHOR_WARNING

和WARNING一样,但只有在启用了开发者警告(-Wdev)时才会显示。

SEND_ERROR

表示一个错误信息,以红色高亮显示。继续处理,直到配置阶段完成,但生成阶段不会执行。

FATAL_ERROR

表示一个严重错误,打印出错信息,处理过程立即停止。日志通常也会记录致命的message命令的位置。

DEPRECATION

用于记录废弃信息的特殊类别。如果CMAKE_ERROR_DEPRECATED为true,则该消息将被视为一个错误。如果CMAKE_WARN_DEPRECATED为true,那么该消息将被视为一个警告。如果这两个变量都没有定义,该消息将不会被显示。

如果没有提供模式关键字,那么该消息被认为是重要的信息,不做任何修改就被记录下来。需要注意的是,用STATUS模式与完全没有任何模式的消息是不同的。

CMake提供的另一个帮助调试变量的命令是variable_watch。当一个变量被监视时,所有试图读取或修改它的行为都会被记录下来。

1
variable_watch(myVar [command])

大多数情况下,监视变量就足够了。也可以提供一个命令,它应该是CMake函数或宏的名称,将被传递以下参数:变量名称、访问类型、变量的值、当前列表文件的名称和列表文件栈。

字符串处理

string命令提供了很多有用的字符串处理功能,包括查找和替换操作、正则表达式匹配、大/小写转换、去除空白和其他常见任务。string的第一个参数定义了要执行的操作,随后的参数取决于被请求的操作。这些参数通常需要至少一个输入字符串,由于CMake命令不能返回一个值,所以需要一个输出变量来表示操作的结果。

查找

FIND用法如下:

1
string(FIND inputString subString outVar [REVERSE])

FIND在inputString中搜索subString,并在outVar中存储找到的subString的索引。如果subString没有出现在inputString中,那么outVar将被赋值为-1。

1
2
3
4
5
set(longStr abcdefabcdef)
set(shortBit def)
string(FIND ${longStr} ${shortBit} fwdIndex)
string(FIND ${longStr} ${shortBit} revIndex REVERSE)
message("fwdIndex = ${fwdIndex}, revIndex = ${revIndex}") # fwdIndex = 3, revIndex = 9

替换

替换一个子串也遵循类似的模式。

1
string(REPLACE matchString replaceWith outVar input [input...])

REPLACE操作将用replaceWith替换输入字符串中出现的每一个matchString,并将结果存入outVar。当给出多个输入字符串时,在搜索替换之前,它们会被连接在一起,每个字符串之间没有任何分隔符。

正则表达式

REGEX操作支持正则表达式,有以下几种形式:

1
2
3
string(REGEX MATCH regex outVar input [input...])
string(REGEX MATCHALL regex outVar input [input...])
string(REGEX REPLACE regex replaceWith outVar input [input...])
  • MATCH操作只找到第一个匹配项并将其存储在outVar中。
  • MATCHALL找到所有的匹配,并以列表形式存储在outVar中。
  • REPLACE将返回整个输入字符串,每个匹配项都被replaceWith替换。在replaceWith中可以使用\1\2等来引用匹配项,但要注意反斜线必须转义。

以下举例说明:

1
2
3
4
5
set(longStr abcdefabcdef)
string(REGEX MATCHALL "[ace]" matchVar ${longStr})
string(REGEX REPLACE "([de])" "X\\1Y" replVar ${longStr})
message("matchVar = ${matchVar}") # matchVar = a;c;e;a;c;e
message("replVar = ${replVar}") # replVar = abcXdYXeYfabcXdYXeYf

子串

提取子串用SUBSTRING操作。

1
string(SUBSTRING input index length outVar)

index是一个整数,表示从哪开始提取子串,length表示提取字符的长度。如果length是-1,返回的子串将包含所有的字符,直到输入字符串的末端。在CMake 3.1和更早的版本中,如果length超过输入字符串末尾,会报错。

一些常用操作

以下是几个常用的操作,语法一样:

1
2
3
4
string(LENGTH input outVar)
string(TOLOWER input outVar)
string(TOUPPER input outVar)
string(STRIP input outVar)

CMake还提供了其他操作,如字符串比较、散列、时间戳等,但它们在日常的CMake项目中的使用并不常见。有兴趣可以查阅CMake参考文档中string命令了解更多细节。

列表

列表在CMake中被大量使用,它只是一个由分号分隔的列表项组成的字符串,这使得操作单个列表项变得不太方便。CMake提供了list命令以方便操作列表。与string命令一样,list命令第一个参数也是操作,第二个参数是要操作的列表,必须是一个变量。

获取列表项

最基本的列表操作是计算列表项数量和从列表中检索一个或多个项目。

1
2
list(LENGTH listVar outVar)
list(GET listVar index [index...] outVar)

示例如下:

1
2
3
4
5
6
7
# Example
set(myList a b c) # Creates the list "a;b;c"
list(LENGTH myList len)
message("length = ${len}") # length = 3

list(GET myList 2 1 letters)
message("letters = ${letters}") # letters = c;b

插入列表项

APPEND和INSERT操作向列表插入元素。

1
2
list(APPEND listVar item [item...])
list(INSERT listVar index item [item...])

示例如下:

1
2
3
4
5
set(myList a b c)
list(APPEND myList d e f)
message("myList (first) = ${myList}") # myList (first) = a;b;c;d;e;f
list(INSERT myList 2 X Y Z)
message("myList (second) = ${myList}") # myList (second) = a;b;X;Y;Z;c;d;e;f

查找列表项

FIND操作查找列表中的特定元素。

1
list(FIND myList value outVar)

示例如下:

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

删除列表项

删除列表元素使用以下3个操作,所有这些操作都直接修改列表。

1
2
3
list(REMOVE_ITEM myList value [value...])
list(REMOVE_AT myList index [index...])
list(REMOVE_DUPLICATES myList)
  • REMOVE_ITEM操作可以删除一个或多个列表元素。如果元素不在列表中,并不会报错。
  • REMOVE_AT指定了一个或多个要删除的索引,如果任何指定的索引超过了列表末尾,CMake将停止处理并报错。
  • REMOVE_DUPLICATES确保列表只包含唯一的元素。

列表反转和排序

列表中的元素可以用REVERSE或SORT操作按字母顺序重新排序。

1
2
list(REVERSE myList)
list(SORT myList)

列表的索引可以是负数,表示从列表的末尾开始计数。列表中最后一项的索引为-1,第二项为-2,以此类推。

数学表达式

CMake提供了math命令用于执行基本的数学计算。

1
math(EXPR outVar mathExpr)

第一个参数必须是关键字EXPR,而mathExpr定义了要计算的表达式,其结果将被存储在outVar中。表达式可以使用以下任何一个运算符,它们的含义与C代码中的含义相同:+ - * / % | & ^ ~ << >> * / %。 表达式中也可以使用括号,并且可以引用CMake普通变量。

1
2
3
4
set(x 3)
set(y 7)
math(EXPR z "(${x}+${y}) / 2")
message("result = ${z}") # result = 5