# 第5章：变量

前面的章节展示了如何定义目标和构建，不过CMake还附带了一整套其他特性，这些特性具有极大的灵活性和便利性。这一章来聊一聊CMake中变量的使用。

## 5.1. 基本变量

像任何计算机语言一样，变量是CMake的基础。定义变量的基本方法是使用`set()`。可以在CMakeLists.txt中定义一个普通变量:

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

变量的名称`varName`可以包含字母、数字和下划线，字母是区分大小写的。名称也可能包含字符./-+，但这些很罕见。

CMake变量有的作用域，就像其他语言中变量的作用域会限制在特定的函数或文件中一样，变量不能在其作用域外进行读取或修改。相比其他语言，CMake中的变量作用域更灵活一些，本章的简单例子中，我们将变量的作用域看作是全局的。第7章和第8章会介绍局部作用域的情况，并展示`PARENT_SCOPE`关键字是如何提高变量在作用域中的可见性。

CMake将所有变量都作为字符串处理。不同的上下文中，变量可能解释为不同的类型，但最终只是字符串。当设置一个变量时，CMake不要求这些值用引号括起来，除非值中包含空格。如果给定多个值，这些值将用分号连接在一起，生成的字符串就是CMake列表：

```
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脚本非常类似。

```
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 = ""
```

字符串不限于单行，可以包含换行字符，还可以包含引号，不过需要使用反斜杠进行转义。

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

如果使用CMake 3.0或更高版本，可以使用lua的括号语法，内容的开始用`[=[`标记，结束用`]=]`标记。方括号之间可以出现任意数量的`=`字符，但开始和结束必须使用相同数量的`=`字符。如果开始的方括号后面紧接着一个换行符，第一个换行符将忽略，但是随后的换行符不会忽略。此外，括号内的内容不会进行进一步的转换(即没有变量替换或转义)。

```
# 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\"
")
```

如上所示，括号语法特别适合于定义Unix shell脚本之类的内容。这些内容使用`${…}`语法来实现自己的目的，并经常包含引号，但是使用括号语法意味着这些内容不需要转义，这与CMake内容的传统引号风格不同。在`[`和`]`标记之间使用任意数量的`=`字符的灵活性也意味着嵌入的方括号不会误解为标记。第18章包括了更多的例子，这些例子强调了括号语法是一个更好的选择的情况。

可以调用`unset()`或`set()`来解除变量的设置。下面两行是等价的，如果`myVar`不存在，也不会有错误或警告:

```
set(myVar)
unset(myVar)
```

除了自己定义的变量外，许多CMake命令的行为会受到特定变量的影响。这是CMake用来定制命令或修改默认值的常见模式，这样就不必为每个命令、目标定义进行重复执行。CMake参考文档通常会列出可能影响该命令行为的变量。本书后面的章节也展示了一些变量，以及影响构建的方式或提供关于构建的信息。

## 5.2. 环境变量

CMake允许使用普通变量符号进行检索，以及设置环境变量的值。环境变量通过`$ENV{varName}`获得，可以在任何使用`${varName}`的地方使用。设置环境变量的方法与普通变量相同，要设置的变量不是`varName`，而是`ENV{varName}`:

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

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

## 5.3. 缓存变量

除了上面讨论的常规变量之外，CMake还支持缓存变量。与一般的变量不同，缓存变量的生命周期仅限于CMakeLists.txt文件的处理，缓存变量存储在名为CMakeCache.txt的特殊文件中，并且在CMake运行期间持久存在。一旦设置好了，缓存变量就会保持不变，直到显式地将它们从缓存中删除。缓存变量的值与普通变量的检索方法完全相同(即`${myVar}`形式)，但`set()`命令在设置缓存变量时有所不同:

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

当`CACHE`关键字出现时，`set()`命令将其当做一个名为`varName`的缓存变量，而不是普通的变量。缓存变量比普通变量附带更多的信息，包括类型和文档字符串。设置缓存变量时，必须同时提供这两个参数，文档字符串可以为空。类型和文档字符串都不会影响CMake如何处理变量，只会让GUI工具以更合适的形式将变量呈现给用户。CMake在处理过程中将变量作为字符串处理，类型只是为了改善GUI工具中的用户体验。类型必须是下列之一:

_BOOL_

缓存变量是一个布尔值。GUI工具使用复选框或类似的工具来表示变量。变量所包含的底层字符串值，遵循CMake将布尔值表示为字符串的一种方式(ON/OFF, TRUE/FALSE, 1/0，等等——参见6.1.1节获得详细信息)。

_FILEPATH_

缓存变量表示为某个文件的路径。GUI工具为用户提供一个对话框来修改变量的值。

_PATH_

与`FILEPATH`类似，但GUI工具会提供了目录选择对话框。

_STRING_

变量会当作字符串处理。默认情况下，GUI使用单行文本编辑工具来操作变量的值。可以使用缓存变量属性，为GUI提供一组预定义值，以组合框或类似的形式呈现。

_INTERNAL_

该变量对用户不可用。内部缓存变量有时用于持久地记录项目的内部信息，例如：缓存密集查询或计算的结果。GUI不显示内部变量。

GUI通常使用文档字符串作为缓存变量的工具提示，或者在选择变量时作为简要的描述。文档字符串应该简短，并且由纯文本组成(例如，没有HTML标记等)。后续将进一步讨论`FORCE`关键字。

设置布尔缓存变量非常常见，CMake为此提供了单独的命令。开发人员可以使用`option()`代替冗长的`set()`:

```
option(optVar helpString [initialValue])
```

如果省略`initialValue`，将使用默认值`OFF`。如果提供，`initialValue`必须符合`set()`接受的布尔值。以上内容等价于:

```
set(optVar initialValue CACHE BOOL helpString)
```

与`set()`相比，`option()`命令更清楚地表达了布尔缓存变量的行为，因此是首选命令。

普通变量和缓存变量之间的区别是，`set()`只会在`FORCE`关键字存在的情况下覆盖缓存变量。定义缓存变量时，`set()`更类似于`set-if-not-set`，与`option()`命令的作用类似(没有`FORCE`功能)。这样做的主要原因是缓存变量为开发人员定制。可以将CMakeLists.txt文件中的值硬编码为普通变量，也可以使用缓存变量，以便开发人员不必编辑CMakeLists.txt文件就可以覆盖。变量可以通过交互式GUI工具或脚本进行修改，而不需要更改项目本身中的内容。

有一点不好理解，就是普通变量和缓存变量是两个独立的东西。可能普通变量和缓存变量同名不同值的情况。这种情况下，使用`${myVar}`时，CMake将检索普通变量的值而不是缓存变量，或者换一种方式，普通变量的优先级高于缓存变量。例外情况是，在设置缓存变量的值时，如果在调用`set()`之前缓存变量不存在，或者使用了`FORCE`，那么当前范围内的任何普通变量都会删除。注意，这意味着在第一次运行和随后的CMake运行中可能会得到不同的行为，因为在第一次运行中，缓存变量不存在，但在随后的运行中会存在。因此，第一次运行时，将会隐藏普通变量，但在随后的运行中不会这样做。举个例子说明这个问题：

```
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
```

简单地说，是`${myVar}`将检索分配给`myVar`的最后一个值，不管它是普通变量还是缓存变量。在第7章和第8章会解释这种行为，进一步说明变量的作用域如何影响`${myVar}`返回的值。

## 5.4. 控制缓存变量

项目可以使用`set()`和`option()`为开发人员构建有用的定制选项。通过这些选项可以打开或关闭不同的构建部分，可以设置外部包的路径，可以修改编译器和链接器的标志等等。后面的章节将介绍缓存变量的用法，但先要了解操作这些变量的方法。开发人员可以通过两种方式来完成这项工作，一种是通过cmake命令行，另一种是使用GUI工具。

### 5.4.1. 使用命令行设置缓存变量

CMake允许通过传递给CMake的命令行选项操作缓存变量。主要是`-D`选项，用于定义缓存变量的值。

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

`someValue`将替换之前`myVar`缓存变量的值。这种行为就像使用`set()`命令的`CACHE`和`FORCE`选项设置一样。命令行选项只需要一次，可以存储在缓存中以便后续运行，因此不需要每次运行cmake时提供。可以在cmake命令行中提供多个`-D`选项来设置多个变量。

以这种方式定义缓存变量时，就不必在CMakeLists.txt文件中进行设置(即不需要相应的`set()`命令)。命令行上定义的缓存变量的说明字符串为空。类型也可以省略，可以给变量类似于`INTERNAL`的特殊类型。下面展示了通过命令行设置缓存变量的示例：

```
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`选项给出的整个值就应该用引号括起来。

有一种特殊情况是处理cmake命令行上没有声明类型的值。如果项目的CMakeLists.txt文件试图设置相同的缓存变量，并将类型指定为`FILEPATH`或路径，当缓存变量的值是一个相对路径，CMake会将它作为相对于调用CMake的目录，并自动将其转换成一个绝对路径。这种方式并不是没有缺点，因为cmake可以从任何目录调用，而不仅仅是构建目录。因此，建议开发人员在cmake命令行上为表示某种路径的变量指定变量时，始终包含一个类型。在命令行上总定变量的类型是个好习惯，这样能以最合适的形式显示在GUI中。

还可以使用`-U`选项从缓存中删除变量，必要时可以重复该选项以删除多个变量。`-U`选项支持`*`和`?`通配符，但是需要避免删除超出预期的内容并使缓存处于不可构建状态。一般来说，除非确定使用通配符的安全性，否则建议只删除不带通配符的变量。

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

### 5.4.2. CMake GUI

通过命令行设置缓存变量，是自动构建脚本和通CMake操作的基本部分。对于日常开发来说，CMake提供的GUI可提供更好的用户体验。CMake提供了两个GUI：cmake-gui和ccmake，其允许开发人员交互地操作缓存变量。cmake-gui需要桌面平台支持GUI显示，而ccmake使用一个文本的接口，可以在只有文本的环境中使用，比如：ssh连接。两者都包含在官方的平台CMake发布包中。

cmake-gui用户界面如下图所示。顶部允许定义项目的源目录和构建目录，中间部分是可以查看和编辑缓存变量的地方，而在底部是配置和生成按钮及其日志区域。

![](https://211235224-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MJHzSm7gYyKXsrbfeFZ%2Fsync%2F1945705ddde9f715efe7bc5a0a68388d2ccbf5ee.png?generation=1602341597317388\&alt=media)

源目录必须设置为包含CMakeLists.txt文件的根目录。CMake将在构建目录中生成所有构建输出(推荐的目录布局在第2章中讨论过)。对于新项目，这两个目录都必须设置，对于现有项目，设置构建目录将更新源目录，因为源位置会存储在构建目录的缓存中。

第2.3节中介绍了CMake的设置过程。第一阶段，读取CMakeLists.txt文件，并在内存中构建项目，这称为配置阶段。如果配置阶段成功，则可以执行生成阶段，在build目录中创建构建工具的项目文件。从命令行运行cmake时，这两个阶段都是自动执行的，在GUI中分别由`Configure`和`Generate`按钮触发。每次启动配置步骤时，UI中间显示的缓存变量都会更新。任何新添加的变量或在上次运行中更改的值都将以红色高亮显示(当项目首次加载时，所有变量都将高亮显示)。良好的习惯是重新运行`Configure`，直到没有任何更改，这可以确保复杂项目的配置正确。有些项目中，启用某些选项可能会添加更多的选项。当所有缓存变量都没有显示红色，就可以进行`Generate`。上一个示例显示了在`Configure`阶段，没有对任何缓存变量进行更改后的日志输出。

将鼠标悬停在任何缓存变量上都将显示包含该变量的描述字符串提示。还可以使用`Add Entry`按钮手动添加新的缓存变量，这相当于使用空文档字符串发出`set()`命令。缓存变量可以通过`Remove Entry`按钮删除，CMake很可能会在下一次运行时重新创建该变量。

单击变量允许编辑其值，布尔值显示为选框，文件和路径有一个浏览文件系统按钮，字符串通常显示为文本行编辑。作为一种特殊情况，可以为`STRING`类型的缓存变量提供一组值，以便在CMake GUI的组合框中显示，而不是显示为简单的文本输入。这是通过设置缓存变量的`STRINGS`属性来实现的(详细内容见9.6节，这里只是为了显示方便):

```
set(trafficLight Green CACHE STRING "Status of something")
set_property(CACHE trafficLight PROPERTY STRINGS Red Orange Green)
```

`trafficLight`缓存变量初始值为`Green`。当用户尝试在cmake-gui中修改`trafficLight`时，将出现一个包含三个值红色、橙色和绿色的组合框。在变量上设置`STRINGS`属性并不会阻止为该变量分配其他值。该变量可以通过CMakeLists.txt文件中的`set()`命令或通过手动编辑CMakeCache.txt文件等其他方法获得。

缓存变量可以将属性标记为高级或非高级。这也只会影响变量在cmake-gui中的显示方式，而不会影响CMake在处理过程中如何使用变量。默认情况下，cmake-gui只显示非高级变量，通常只显示开发人员感兴趣的主要变量。启用`Advanced`选项可以显示除那些标记为内部的缓存变量外的所有缓存变量(查看内部变量的唯一方法是使用文本编辑器编辑CMakeCache.txt文件，因为不打算由开发人员直接操作)。CMakeLists.txt文件中，可以使用`mark_as_advanced()`命令将变量标记为高级:

```
mark_as_advanced([CLEAR|FORCE] var1 [var2...])
```

`CLEAR`关键字确保变量不标记为高级，而`FORCE`关键字确保变量标记为高级。如果没有这两个关键字，只有当变量还没有设置高级/非高级状态时，才会标记为高级。

通过选择分组选项，可以根据变量名的首字符到第一个下划线将变量分在一起，从而使查看高级变量变得更容易。过滤显示的变量列表的另一种方法是在搜索区域中输入文本，这样就只显示名称或值中包含指定文本的变量。

`Configure`在新项目上第一次运行时，开发人员会看到类似于下图所示的对话框:

![](https://github.com/xiaoweiChen/Professional-CMake/tree/c83252d3ccc82874a9f46f757b2da596f1cdb9e6/images/part-1/5.png)

这个对话框是指定CMake生成器和工具链的地方。生成器的选择通常取决于开发人员的喜好，组合框中提供了可用的选项。如果项目依赖于特定生成器的功能，根据项目不同，生成器的选择可能更受限制。由于Apple平台的独特特性，比如：签名和iOS/tvOS/watchOS支持，需要Xcode生成器。一旦为项目选择了生成器，就需要删除缓存进行更改。如果需要，可以从File菜单中进行更改。

对于工具链选项，每个选项都需要开发人员提供。使用默认的本机编译器是普通桌面开发的常见选择，该选项不需要进一步的信息。如果需要更多的控制，开发人员可以重选编译器，并在后续对话框中给出编译器的路径。如果有单独的工具链文件可用，不仅可用于定制编译器，还可用于定制目标环境、编译器标志和其他东西。最后，开发人员可以指定交叉编译的全部选项，但不建议这样做。工具链文件可以提供相同的信息，优点是可以根据需要进行重用。

ccmake工具提供了与cmake-gui应用程序相同的功能。

![](https://github.com/xiaoweiChen/Professional-CMake/tree/c83252d3ccc82874a9f46f757b2da596f1cdb9e6/images/part-1/6.png)

与cmake-gui选择源目录和构建目录不同，必须在ccmake命令行中指定源目录或构建目录，就像cmake命令一样。

ccmake接口的主要缺点是无法像cmake-gui版本那样方便地捕获日志输出。这里也没有提供筛选显示变量的能力，而且编辑变量的方法也不像cmake-gui那样丰富。除此之外，当完整的cmake-gui不可用时，例如：终端连接上不能支持UI转发时，ccmake工具是一个替代选择。

## 5.5. 调试变量和诊断

当项目变得复杂或在调试意外行为时，CMake在运行时打印的诊断消息和变量值可能会很有用。通常使用`message()`命令来实现：

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

如果指定了多个`msg`，会连接在一起成为一个字符串，没有任何分隔符。这通常不是开发人员想要的，因此更常见的用法是使用单个`message`。打印结果之前，可以使用变量值对其求值。

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

这将会输出：

```
The value of myVar = HiThere
```

`message()`命令接受可选的`mode`关键字，该关键字会影响消息的输出方式，在某些情况下会在出现错误时停止构建。有效的`mode`值为:

_STATUS_

附加信息。消息前面通常有两个连字符

_WARNING_

CMake警告，通常在支持的地方(CMake命令行控制台或cmake-gui日志区域)以红色突出显示。处理将继续下去。

_AUTHOR\_WARNING_

与警告类似，但只在启用开发模式时的警告显示(需要cmake命令行上的-Wdev选项)。通常不使用这种特殊类型的消息。

_SEND\_ERROR_

指示将以红色突出显示的错误消息。处理将继续，直到配置阶段完成，但不会执行生成。这就像允许进一步尝试处理的错误，但最终仍然为失败。

_FATAL\_ERROR_

表示一个错误。打印消息，处理将立即停止。日志通常还会记录`message()`命令的位置。

_DEPRECATION_

用于记录弃用消息。如果`CMAKE_ERROR_DEPRECATED`变量定义为true，则该消息将视为错误。如果`CMAKE_WARN_DEPRECATED`定义为true，则该消息将视为警告。如果这两个变量都没有定义，则消息将不会显示。

如果没有提供`mode`关键字，则认为该消息是重要信息，不需要进行任何修改就可以记录。但是，需要注意的是，使用`STATUS`模式进行日志记录与不使用`mode`关键字进行日志记录完全不同。使用`STATUS`模式，消息将打印在命令与其他CMake消息之前，还有两个连字符前缀。当没有任何`mode`关键字时，没有连字符前缀。

CMake提供的另一种帮助调试变量使用的机制是`variable_watch()`命令。这适用于更复杂的项目，其中可能不清楚变量如何以特定值结束。监视一个变量时，所有读取或修改它的尝试都会记录下来。

```
variable_watch(myVar [command])
```

绝大多数情况下，列出要监视的变量就足够了，因为它会记录对指定变量的所有访问。然而，为了实现最终的控制，可以给出一个命令，该命令将在每次读取或修改变量时执行。CMake的函数或宏，可以传递以下参数：变量名、类型的访问、变量的值、当前列表文件的名称和文件列表堆栈(列表文件第7章中讨论)。但是，实际中很少使用`variable_watch()`。

## 5.6. 处理字符串

随着项目复杂性的增加，也需要实现有关如何管理变量的逻辑。CMake为此提供了`string()`命令，提供了有用的字符串处理功能。此命令能够查找和替换操作、正则表达式匹配、大小写转换、删除空格和其他常见字符串操作。下面给出了一些更常用的功能，CMake参考文档应该是所有可用操作及其行为的规范。

`string()`的第一个参数定义了要执行的操作，后续参数取决于请求的操作。这些参数通常需要(至少)一个输入字符串，因为CMake命令不能有返回值，所以也要输入一个输出变量。在下面的示例中，这个输出变量为`outVar`。

```
string(FIND inputString subString outVar [REVERSE])
```

`FIND`在`inputString`中搜索子字符串，并在`outVar`中存储找到的子字符串的索引(第一个字符为索引0)，除非指定反向，否则以第一个出现的为准。如果`subString`没有出现在`inputString`中，则`outVar`的值将为-1。

```
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
```

替换子字符串遵循以下的模式:

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

`REPLACE`操作将使用`replaceWith`替换输入字符串中每个`matchString`，并将结果存储在`outVar`。当给定多个输入字符串时，在搜索替换之前，它们将连接在一起，每个字符串之间没有任何分隔符。有时这会导致意外的匹配，大多数情况下，开发人员提供一个输入字符串就好。

正则表达式`REGEX`也可以支持，有几个不同的变体可用，并由第二个参数决定:

```
string(REGEX MATCH regex outVar input [input...])
string(REGEX MATCHALL regex outVar input [input...])
string(REGEX REPLACE regex replaceWith outVar input [input...])
```

要匹配的正则表达式`REGEX`可以使用基本正则表达式语法(有关完整规范，请参阅CMake参考文档)，不支持一些常见特性，比如：逻辑非。替换之前，将输入字符串连接起来。匹配操作只查找第一个匹配项并将其存储在`outVar`中。`MATCHALL`查找所有匹配项并将它们作为列表存储在`outVar`中。`REPLACE`将返回整个输入字符串，并将匹配项全替换为`replaceWith`。可以使用符号`\1`、`\2`等来代替引用匹配，但请注意，反斜杠本身必须在CMake中进行转义。

```
set(longStr abcdefabcdef)
string(REGEX MATCHALL "[ace]" matchVar ${longStr})
string(REGEX REPLACE "([de])" "X\\1Y" replVar ${longStr})
message("matchVar = ${matchVar}")
message("replVar = ${replVar}")
```

输出：

```
matchVar = a;c;e;a;c;e
replVar = abcXdYXeYfabcXdYXeYf
```

提取子字符串也可以:

```
string(SUBSTRING input index length outVar)
```

索引是一个整数，定义要从输入中提取的子字符串开始。提取最大长度的字符，或者如果长度为-1，则返回的子字符串将包含所有字符。注意，在CMake 3.1和更早的版本中，如果长度超过了字符串的末尾就会报错。

字符串的长度可以很容易地得到，字符串可以很容易地转换为大写或小写。从字符串的开头和结尾去掉空格也很简单。这些操作的语法都采用相同的形式:

```
string(LENGTH input outVar)
string(TOLOWER input outVar)
string(TOUPPER input outVar)
string(STRIP input outVar)
```

CMake还提供其他操作，如字符串比较、哈希、时间戳等，但在日常CMake项目中并不常见。感兴趣的读者可以查阅CMake参考文档中的`string()`命令以获得详细信息。

## 5.7. 列表

列表在CMake中广泛使用。列表只是一个用分号分隔的列表项的单个字符串，这使得操作单个列表项变得不太方便。CMake提供了`list()`命令来简化这些任务。与`string()`命令一样，`list()`第一个将作为执行参数，第二个参数是要操作的列表，必须是一个变量(例如，不允许传递一个原始列表，比如a;b;c)。

列表操作是计算目标的数量，并从列表中检索一个或多个目标:

```
list(LENGTH listVar outVar)
list(GET listVar index [index...] outVar)
```

```
# Example
set(myList a b c) # Creates the list "a;b;c"
list(LENGTH myList len)
message("length = ${len}")
list(GET myList 2 1 letters)
message("letters = ${letters}")
```

输出为：

```
length = 3
letters = c;b
```

追加和插入也很简单:

```
list(APPEND listVar item [item...])
list(INSERT listVar index item [item...])
```

与`LENGTH`和`GET`不同，`APPEND`和`INSERT`直接作用于`listVar`，并在适当的地方修改它:

```
set(myList a b c)
list(APPEND myList d e f)
message("myList (first) = ${myList}")

list(INSERT myList 2 X Y Z)
message("myList (second) = ${myList}")
```

输出:

```
myList (first) = a;b;c;d;e;f
myList (second) = a;b;X;Y;Z;c;d;e;f
```

列表中查找特定目标:

```
list(FIND myList value outVar)
```

```
# Example
set(myList a b c d e)
list(FIND myList d index)
message("index = ${index}")
```

输出为：

```
index = 3
```

删除供了三种操作，所有这些操作都直接对列表进行修改:

```
list(REMOVE_ITEM myList value [value...])
list(REMOVE_AT myList index [index...])
list(REMOVE_DUPLICATES myList)
```

`REMOVE_ITEM`可用于从列表中删除一个或多个目标项。如果目标项不在列表中，也不会出错。另外，`REMOVE_AT`指定一个或多个要删除的索引，如果指定的任何索引超过列表的末尾，CMake将停止并报错。`REMOVE_DUPLICATES`将确保列表不包含重复项。

列表项也可以重新排序与反向排序(按字母顺序):

```
list(REVERSE myList)
list(SORT myList)
```

对于所有接受索引作为输入的列表操作，索引可以是负数，表示计数从列表的末尾开始。这样使用时，列表中的最后一项的索引为-1，倒数第二项为-2，依此类推。

上面描述了大多数可用的`list()`子命令。以上提到的都是在CMake 3.0之后就支持了。要获得子命令的完整支持列表，包括在以后CMake版本中添加的子命令，读者应该去参考CMake文档。

## 5.8. 数学表达式

另一种常见的变量操作是数学计算。CMake提供了`math()`命令来执行基本的数学计算:

```
math(EXPR outVar mathExpr)
```

第一个参数必须使用关键字`EXPR`，而`mathExpr`定义要计算的表达式，结果将存储在`outVar`中。表达式可以使用以下操作符中的任何一个，这些操作符在C代码中的含义相同：`+ - * / % | & ^ ~ << >> * / %`。也支持括号，并且具有常规的数学意义。`mathExpr`中变量可以使用`${myVar}`进行定义后引用。

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

期望输出为：

```
result = 5
```

## 5.9. 总结

CMake GUI是一种高效的方法，可以快速、轻松地理解项目的构建选项，并在开发过程中根据需要修改它们。花一点时间来熟悉它将简化处理更复杂项目的工作。还为开发人员提供了很好的工作基础，如果需要试验，诸如编译器设置之类的东西(因为这些很容易在GUI环境中找到和修改)。

使用缓存变量来控制是否启用构建的可选部分，而不是在CMake之外的构建脚本中编码逻辑。这使得在CMake GUI和解析CMakeLists.txt文件的工具中变得非常简单(越来越多的IDE环境正在获得这种能力)。

尽量避免依赖已定义的环境变量，除了`PATH`或类似的操作系统的变量。构建应该是可预测的、可靠的和容易修改的，但是如果依赖于环境变量，这可能会成为新手开发人员在构建环境时的痛点。此外，与调用构建本身时相比，CMake运行时的环境可能会发生变化。因此，最好通过缓存变量直接将信息传递给CMake。

作为构建的一部分的日志消息，请使用`message()`命令。如果消息是一般的信息，最好使用`STATUS`，这样在构建日志中不会出现混乱。为了方便，临时调试信息通常不使用`mode`关键字，如果可能在任何时间内都作为项目的一部分，最好能指定模式(通常是`STATUS`)。

CMake提供了大量的预定义变量，这些变量提供了系统的细节或影响了某些方面的行为。其中一些变量在项目大量使用，比如仅为特定平台(WIN32、APPLE、UNIX等)构建时定义的变量。因此，建议开发人员快速浏览一下CMake文档页面上列出的预定义变量，以熟悉这些变量。
