005.CMake变量(上)

变量是CMake完成任务的基石,尽管这一基石正越来越多的被属性所取代,但直到现在位置,变量的作用是毋庸置疑的。实际上,在整个CMake的工作逻辑中,CMakeLists.txt文件描述的核心内容就是项目的状态。以变量的形式将这种状态保存下来,是最直观的方式。当然现在CMake采用了更加容易理解的面向对象的组织方式,以各种对象(构建目标、目录、文件等)和对象的属性将状态组织起来,这在后面的文章中会介绍。

普通变量

使用如下命令设置变量:

set(varName value... [PARENT_SCOPE])

当然,就像其他编程语言一样,CMake变量也有作用域的区分,在作用域之外,是无法访问以及修改变量的。(使用PARENT_SCOPE关键字可以改变这一特性,后面再说)。作用域包括目录、函数、缓存等,后面再详说。

所有的CMake变量都是字符串或者字符串数组,尽管在很多情况下,字符串会被识别为不同的类型,比如路径字符串,但归根结底还是字符串。变量的值有以下要点需要注意:如果没有空格,无需引号;如果有空格,需要引号;如果是多个字符串组成的数组,需要适当的引入分号分隔,其意义如下:

set(myVar a b c) # myVar = "a;b;c", string array
set(myVar a;b;c) # myVar = "a;b;c" , string array
set(myVar "a b c") # myVar = "a b c", string 
set(myVar a b;c) # myVar = "a;b;c", string array
set(myVar a "b c") # myVar = "a;b c", string array

省略分号并不会带来多大的效率提升,但有时候会带来一些误会,所以并不推荐省略分号的写法。

引用变量的值使用 ${myVar}符号,可以应用在任何需要字符串的地方。

如果使用cmake3.0或更高版本,一个替代引号的方法是使用lua风格的括号语法,其中内容的开头用**[=[标记,结尾用]=]**标记。方括号之间可以出现任意数量的=字符,包括0个,但必须在开头和结尾使用相同数量的=字符。如果左括号后面紧跟着一个换行符,则忽略第一个换行符,但不忽略后面的换行符。注意,这种形式的字符串,一般不需要也不支持转义符号。

使用如下命令之一取消变量的设置,如果变量不存在,不会有任何错误会警告信息。

set(myVar)
unset(myVar)

每个项目都可以定义自己需要的变量,当然CMake也内置了大量的变量,这些内置变量会影响到特定命令的执行。这是CMake用来控制命令行为的最常用模式。

环境变量

CMake提供了获取和修改环境变量的方法,环境变量的值使用**$ ENV{varName}获得,这可以在任何使用常规${varName}**形式的地方使用。设置环境变量的方法与普通变量相同,只是使用ENV{varName}而不是将varName作为要设置的变量。例如:

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

但是,请一定注意,环境变量只会影响到当前运行的CMake实例。一旦CMake运行完成,对环境变量的更改就会丢失。特别是,对环境变量的更改在构建时将不可见。因此,最好不要依赖环境变量的修改来设计构建的逻辑。

缓存变量

除了上面讨论的普通变量之外,CMake还支持缓存变量。在之前的文章中提到CMakeCache.txt文件,是在构建目录中生成的文件,缓存变量保存在这里面。(与之对应的普通变量在Makefile里)

一旦设置,缓存变量将保持设置状态,直到将它们显式地从文件中删除。缓存变量的值的检索方式与普通变量完全相同(即使用**${myVar}**形式),但是set()命令在用于设置缓存变量时是不同的:

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

当存在CACHE关键字时,set()命令将设置一个名为varName的缓存变量。缓存变量比普通变量附加了更多的信息,包括一个名义上的类型和一个文档字符串。尽管docstring可以为空,但在设置缓存变量时必须提供这两者。名义类型和文档字符串都不会影响CMake处理变量的方式,它们仅由CMake-GUI工具用于以更合适的形式将变量呈现给用户。在处理过程中,CMake总是将变量作为字符串处理,这种类型只是为了改善GUI工具中的用户体验。类型必须是以下类型之一:

BOOL,cmake-gui使用一个复选框表示之。
FILEPATH,代表一个文件的路径,cmake-gui会提供一个文件选择对话框。
PATH,代表一个目录,cmake-gui提供一个目录选择对话框。
STRING,cmake-gui使用一个编辑控件表示之。
INTERNAL,内部信息,不显示在cmake-gui上。

设置布尔缓存变量是一种常见的需要,因此CMake提供了一个单独的命令完成设置,可以使用如下命令:

option(optVar helpString [initialValue])

initialValue表示初始化值,如果不提供,则为OFF。

实际上上面的命令和下面是相等的:

set(optVar initialValue CACHE BOOL helpString)

还可以使用如下命令标记为高级缓存变量:

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

其中CLEAR为清除高级标记,FORCE为添加高级标记。这在cmake-gui上有更直观的意义,后面将演示之。

有一点要特别注意,当一个缓存变量已经存在,再使用set()或者option()设置的时候是无效的,除非加上FORCE关键字,这表示强制覆盖之前存在的缓存变量。这一特性是普通变量与缓存变量最大的不同之处。我将在本文最后演示这一特性。

普通变量和缓存变量不是一种东西

普通变量和缓存变量是两个独立的东西,或者说是两个作用域的东西。一个普通变量和一个高速缓存变量可以具有相同的名称。

而且在不同情况下,其优先级还是不同的。

当使用${myVar}读取变量的值的时候,CMake将检索普通变量的值而不是缓存变量,也就是说此时普通变量的优先级要高于缓存变量。

但是当设置缓存变量的值时,如果在调用set()之前该缓存变量不存在,或者使用了FORCE选项,则当前作用域内的任何普通变量都将被移除掉。

这意味着,有可能存在这么一种情况,即第一次和后续的CMake运行时,存在不同的行为,因为在第一次运行时,CMakeCache.txt文件还没有生成,缓存变量还不存在,在设置的缓存变量的时候,同名的普通变量会被隐藏起来从而根本没法读取或者设置。但在后续的运行中缓存变量已经存在,普通变量就不会被隐藏起来了。这一特性非常特殊,我将在文末演示之。

修改缓存变量

缓存变量的设计是为了不改动CMakeLists.txt文件情况下,修改控制构建的行为。所以尽管修改CMakeLists.txt文件可以改变缓存变量,但那并不是设计的初衷。修改缓存变量有四种方法。

命令行
cmake -D myVar:type=someValue ...

但命令行修改的前提是对缓存变量非常熟悉,起码得知道有哪些变量。

修改CMakeCache.txt

使用任何编辑器直接修改此文件,可以实现修改的目的,但不推荐,因为有可能改的乱七八糟,没法恢复。

cmake-gui工具

这是最推荐的方法,能比较直观的看到可以修改的缓存变量,并修改之。

ccmake工具

没用过,不扯淡。

演示1

新建一个目录,在其中建立一个CMakeLists.txt文件,其内容为:

cmake_minimum_required(VERSION 3.10)
project(HelloWorld)
set(myVar ON CACHE BOOL "测试用的缓存变量")
mark_as_advanced(FORCE myVar)

使用CMakeGui打开之,并配置好一个构建目录,点击Configure按钮,结果如下:
005.CMake变量(上)选中Advanced复选框,显示如下:

005.CMake变量(上)此时刚才定义的高级缓存变量便可以显示出来了。

演示2

修改此CMakeLists.txt内容如下:

cmake_minimum_required(VERSION 3.10)

project(HelloWorld)

set(myVar OFF) #普通变量
set(myVar ON CACHE BOOL "测试用的缓存变量")
message(变量的值:${myVar})

使用如下命令生成MinGW项目文件:

mkdir newbuild
cd newbuild
cmake -G "MinGW Makefiles" ../src

第一次运行的时候,显示如下
005.CMake变量(上)此时变量的值为ON,而不是OFF,也就是说普通的myVar根本访问不到,被隐藏了。访问到的是缓存变量myVar。

当第二次运行的时候,显示如下:
005.CMake变量(上)此时变量的值为OFF,也就是引用到了普通变量myVar。

设想如果基于myVar做一个条件判断,则第一次运行和后面的运行,其行为是不同的。这种特性可能很有用处,比如需要在第一次运行的时候生成辅助文件或者做一些一次性的配置之类的东西。自行斟酌即可。

总结

本文介绍了变量的重要部分,下半截将补充一点东西。CMake的内置变量有很多,文档当中可以挨个查看其意义,就不再这里扯淡了。

上一篇:Flink实战(八十七):flink-sql使用(十四)Flink 与 hive 结合使用(六)Hive 函数


下一篇:【005期】了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?应对措施是什么?...