CMake语言

翻译自cmake-language. 注意:翻译不一定准确,内容仅供参考!

组织(Organization)

CMake输入文件是用CMake Language写的源文件,包括名叫CMakeLists.txt的文件和以.cmake作为扩展名的文件。

一个项目中的CMake源文件被组织成三部分:

  • Directories (CMakeLists.txt),
  • Scripts (<script>.cmake),
  • Modules (<module>.cmake).

目录(Directories)

当CMake处理一个项目的源代码树时,入口点是最上层源目录里的CMakeLists.txt文件,这个文件可能包含整个构建规范(build specification),或者通过使用add_subdirectory()命令来添加需要构建的子目录,每个通过该命令添加的子目录中也必须含有一个CMakeLists.txt文件来作为该路径的入口点。对于CMakeLists.txt文件处理的每一个源目录都会在构建树(build tree)中生成一个对应的目录,来作为默认的工作和输出目录。

脚本(Scripts)

一个单独的<script>.cmake源文件可以在script模式下通过使用cmake(1))命令行工具中的-P选项来被处理,Script模式只是简单地运行该源文件中用CMake Language写的命令,不会生成一个构建系统。它不允许包含有定义构建目标或行为的CMake命令。

模块(Modules)

Directories或者Scripts中的CMake Language代码可以在包含环境中使用include()命令来载入一个<script>.cmake源文件,可以通过查看cmake-modules(7))来了解CMake包含的所有模块。
项目源代码树可能也提供它们自己的模块,并在CMAKE_MODULE_PATH变量中指定它们的位置。

语法(Syntax)

编码(Encoding)

为了最大化在所有支持平台的可移植性,一个CMake语言的源文件由7-bit ASCII文本写成,换行可以被编码成\n\r\n,但在读入输入文件时会被转化为\n

注意实现是8-bit clean,所以源文件在系统API支持UTF-8编码的平台上可能会使用UTF-8编码。此外,CMake版本在 3.2及以上支持在Windows上使用UTF-8编码源文件(使用UTF-16调用系统API)。CMake版本在3.0及以上允许在源文件中使用UTF-8自己顺序标记(UTF-8 Byte-Order Mark)。

源文件(Source Files)

一个CMake语言的源文件由零个或更多的由换行和可选的空格分隔开的命令调用(Command Invocations)和注释(Comments)组成:

1
2
3
4
5
6
file         ::=  file_element*
file_element ::= command_invocation line_ending |
(bracket_comment|space)* line_ending
line_ending ::= line_comment? newline
space ::= <match '[ \t]+'>
newline ::= <match '\n'>

Note that any source file line not inside Command Arguments or a Bracket Comment can end in a Line Comment.

命令调用(Command Invocations)

一个命令调用是一个名字后面紧跟封闭括号中的被空格分开的多个参数:

1
2
3
4
5
6
command_invocation  ::=  space* identifier space* '(' arguments ')'
identifier ::= <match '[A-Za-z_][A-Za-z0-9_]*'>
arguments ::= argument? separated_arguments*
separated_arguments ::= separation+ argument? |
separation* '(' arguments ')'
separation ::= space | line_ending

例如:

1
add_executable(hello world.c)

命令的名字对大小写不敏感。参数中嵌套的圆括号必须对称,每个(或者)作为一个字面上未引用的参数(literal Unquoted Argument)传递给命令调用。这可以在调用if()命令时包围条件使用。例如:

1
if(FALSE AND (FALSE OR TRUE)) # evaluates to FALSE

注意:CMake版本在3.0之前的命令名至少要是2个字符。
CMake版本在2.8.12之前接受在一个未引用的或引用的参数后面紧跟着一个引用的参数Quoted Argument,并不能被任何空格分开。为了兼容,CMake版本高于2.8.12接受这样的代码,但会产生一个警告。

命令参数(Command Arguments)

命令调用中有三种形式的参数:

1
argument ::=  bracket_argument | quoted_argument | unquoted_argument

Bracket Argument

括号参数,受启发于Lua的长括号语法,将内容装入拥有同样长度的开始和结束括号:

1
2
3
4
5
bracket_argument ::=  bracket_open bracket_content bracket_close
bracket_open ::= '[' '='* '['
bracket_content ::= <any text not containing a bracket_close with
the same number of '=' as the bracket_open>
bracket_close ::= ']' '='* ']'

开始括号首先是一个[,后面跟着0个或多个=,最后再跟着],与此对应的结束括号也以[开头,后面跟着同样数量的=,然后再跟着]。括号不嵌套,
Brackets do not nest. A unique length may always be chosen for the opening and closing brackets to contain closing brackets of other lengths.

括号参数内容包括开闭括号之间的所有文本,除了一个新行紧跟着一个开括号,如果有会被忽略。括号内的内容不会被进行任何计算,包括转义序列或者变量引用。一个括号参数总是作为一个参数传递给命令调用。
例如:

1
2
3
4
5
6
7
message([=[
This is the first line in a bracket argument with bracket length 1.
No \-escape sequences or ${variable} references are evaluated.
This is always one argument even though it contains a ; character.
The text does not end on a closing bracket of length 0 like ]].
It does end in a closing bracket of length 1.
]=])

注意:CMake版本在3.0之前不支持括号参数,它会将开括号当作一个Unquoted Argument的开始。

引号参数(Quoted Argument)

一个引号参数包含开闭双引号之间的内容

1
2
3
4
5
quoted_argument     ::=  '"' quoted_element* '"'
quoted_element ::= <any character except '\' or '"'> |
escape_sequence |
quoted_continuation
quoted_continuation ::= '\' newline

引号参数内容包含开闭引号之间的所有内容,转义序列和变量引用都会被计算。一个引号参数总是作为一个参数传递给命令调用。
例如:

1
2
3
4
5
6
message("This is a quoted argument containing multiple lines.
This is always one argument even though it contains a ; character.
Both \\-escape sequences and ${variable} references are evaluated.
The text does not end on an escaped double-quote like \".
It does end in an unescaped double quote.
")

每一行的最后以奇数个反斜线\结尾会被当作一个续行,后面紧跟的换行符会被忽略:

1
2
3
4
5
message("\
This is the first line of a quoted argument. \
In fact it is the only line but since it is long \
the source code uses line continuation.\
")

主要:CMake版本在3.0之前不支持用\续行,会报告一个在引号参数中包含某些行以奇数个\字符结束的错误。

非引号参数(Unquoted Argument)

一个非引号参数不会被任何引号语法包围,不能包含任何空格、()#"或者\,除非被反斜线转义:

1
2
3
4
unquoted_argument ::=  unquoted_element+ | unquoted_legacy
unquoted_element ::= <any character except whitespace or one of '()#"\'> |
escape_sequence
unquoted_legacy ::= <see note in text>

非引号参数内容由所有连续或转义的字符组成,转义序列和变量引用会被计算。得到的值会像Lists划分元素一样划分,每一个非空的元素作为一个参数传递给命令调用。所以非引用参数可以作为0个或多个参数传递给命令调用。
例如:

1
2
3
4
5
6
7
8
foreach(arg
NoSpace
Escaped\ Space
This;Divides;Into;Five;Arguments
Escaped\;Semicolon
)
message("${arg}")
endforeach()

注意:为了支持传统的CMake代码,非引用参数也可能包含双引号包围的字符串("...",可能包含水平空格)和make-style变量引用(${MAKEVAR})。
非转义的双引号必须平衡,可能不会在非引号参数的开始出现,此时将作为内容的一部分对待。例如,非引用参数-Da="b c", -Da=$(v)a" "b"c"d都按字面值解析,可以作为引号参数分别写作"-Da=\"b c\"", "-Da=$(v)", and "a\" \"b\"c\"d"
Make-style引用被当作内容的一部分,不会经过变量展开。它们被当作一个参数的一部分(而不是作为独立的$(MAKEVAR)参数)。
上面的unquoted_legacy表示这样的参数。现在不推荐使用传统的非引用参数,应该用引用参数或者括号参数来表示内容。

转义序列(Escape Sequences)

一个转义序列是\后面跟着一个字符:

1
2
3
4
escape_sequence  ::=  escape_identity | escape_encoded | escape_semicolon
escape_identity ::= '\' <match '[^A-Za-z0-9;]'>
escape_encoded ::= '\t' | '\r' | '\n'
escape_semicolon ::= '\;'

一个\跟着一个非字母数字字符简单地编码了语义字符而不将其解释为语法。\t\r或则\n分别编码了制表符、回车符和换行符。变量引用外面的\;编码了其本身,但可能被用于非引用参数来编码为;且不划分其参数值。变量引用中的\;编码了;字符。

变量引用(Variable References)

一个变量引用形如${variable_name},在一个引用参数或非引用参数中被计算。一个变量引用会被替换为变量的值,或者如果变量未定义则表示一个空字符串。变量引用可以嵌套,从内到外被计算,如 ${outer_${inner_variable}_variable}
变量引用可以包含字母数字字符、/_.+-字符、转义序列。嵌套的引用可以用于计算任何名字的变量。
环境变量引用形如$ENV{VAR},跟普通变量引用一样在上下文中被求值。

注释(Comments)

注释从一个#字符开始,但#不能位于括号参数、引号参数或非引号参数中或者作为非引号参数的一部分的转义字符\中。
注释有两种类型:括号注释和行注释。

括号注释(Bracket Comment)

如果#后面紧跟着一个括号参数,则括号中包含的内容构成一个括号注释:

1
bracket_comment ::=  '#' bracket_argument

例如:

1
2
3
#[[This is a bracket comment.
It runs until the close bracket.]]
message("First Argument\n" #[[Bracket Comment]] "Second Argument")

注意CMake版本在3.0之前不支持括号注释,这时将#解释为一个行注释的开头。

行注释(Line Comment)

如果#后面没有紧跟着一个括号参数,则构成一个行注释,直到行尾结束:

1
2
line_comment ::=  '#' <any text not starting in a bracket_argument
and not containing a newline>

例如:

1
2
3
# This is a line comment.
message("First Argument\n" # This is a line comment :)
"Second Argument") # This is a line comment.

控制结构(Control Structures)

条件块(Conditional Blocks)

if()/elseif()/else()/endif()命令使得代码块有条件地执行。

循环(Loops)

foreach()/endforeach() and while()/endwhile()命令界定代码块循环执行。在每个这样的快中,break()命令可以终止循环,continue()命令可以让下一次迭代马上进行。

命令定义(Command Definitions)

macro()/endmacro()function()/endfunction()命令界定代码块可以在后面作为命令被调用。

变量(Variables)

变量是CMake语言中存储的基本单元,它们的值永远是==字符串类型==,虽然有些命令会将字符串解释为其他类型的值。set()unset()命令显式地设置或取消设置一个变量,但是其他命令同样也有修改变量的语义。变量名是大小写敏感的,几乎可以由任意文本组成,但是一般推荐使用只包含字母数字外加_-的变量名。

变量有动态的作用域,每个变量“set”或者“unset”在当前的作用域创建一个绑定:

  • 函数作用域(Function Scope)
    通过function()命令创建的命令定义生成一个命令,当被调用时,在新的变量绑定域内处理记录的命令,一个在该域内“set”或者“unset”的变量,只对当前函数和其内部嵌套的调用可见,直到函数返回。
  • 路径作用域(Directory Scope)
    源文件树中的每一个路径都有自己的变量绑定,在处理一个路径中的CMakeLists.txt文件之前,CMake拷贝在父目录中定义的所有变量绑定,来初始化当前的路径作用域。CMake脚本文件,当被cmake -P命令处理后,会在路径作用域中被绑定。
    一个变量不在函数内“set”或者“unset”会在当前路径作用域中绑定。
  • 持久缓存(Persistent Cache)
    CMake存储了一些独立的缓存变量或者缓存条目集合,它们的值在一个项目构建树的多次运行中一直保持存在。缓存条目有独立的绑定域,只有通过显式请求才能修改,如通过set()unset()命令的CACHE选项。

当计算变量引用的值时,CMake首先搜索函数调用栈以获取绑定,然后回退到当前目录作用域中的绑定。如果找到set绑定,则使用其值。 如果找到unset绑定,或者未找到绑定,则CMake将搜索缓存条目,如果找到缓存条目,则使用其值。 否则,变量引用将计算为空字符串。$CACHE{VAR}语法可用于执行直接查找缓存条目。
cmake-variables(7))手册记录了CMake提供的,或者在项目代码设置时对CMake有意义的许多变量。

环境变量(Environment Variables)

环境变量就像普通变量一样,但是有如下不同:

  • 作用域(Scope)

    环境变量在CMake处理过程中拥有全局作用域,它们永远不会被缓存。

  • 引用(References)

    变量引用形式为 $ENV{<variable>}

  • 初始化(Initialization)

    CMake环境变量的初始值是调用进程的初始值。 可以使用set()unset()命令更改值。 这些命令仅影响正在运行的CMake进程,而不影响整个系统环境。 更改的值不会写回调用进程,后续构建或测试进程也不会看到它们。

    cmake-env-variables(7)) 手册记录了对CMake具有特殊意义的环境变量。

列表(Lists)

尽管CMake中的所有值都存储为字符串,但在某些上下文中可以将字符串视为列表,例如在评估非引号参数期间。 在这种情况下,通过拆分将字符串分成列表元素,不紧跟着不等数量的[]且不紧跟在\前面的;字符作为拆分符。 序列\;不拆分值但被替换为在结果元素中替换为;

元素列表通过连接由;分隔的元素并被表示为字符串。 例如,set()命令将多个值作为列表存储到目标变量中:

1
set(srcs a.c b.c c.c) # sets "srcs" to "a.c;b.c;c.c"

列表适用于简单的用例,例如源文件列表,不应用于复杂的数据处理任务。 大多数列表元素在构造列表的命令时都不会转义;字符,因此展平嵌套的列表:

1
set(x a "b;c") # sets "x" to "a;b;c", not "a;b\;c"

Reference