cmake 语言(7)

组织

CMake 输入文件以“CMake 语言”编写在名为“CMakeLists.txt”或以“.cmake”文件扩展名结尾的源文件中。

项目中的 CMake 语言源文件组织为:

  • 目录`_(``CMakeLists.txt`),

  • Scripts (<script>.cmake),以及

  • 模块`_(``<模块>.cmake`)。

目录

当 CMake 处理项目源代码树时,入口点是顶级源目录中名为“CMakeLists.txt”的源文件。此文件可能包含整个构建规范或使用 add_subdirectory() 命令将子目录添加到构建中。该命令添加的每个子目录还必须包含一个“CMakeLists.txt”文件作为该目录的入口点。对于处理“CMakeLists.txt”文件的每个源目录,CMake 在构建树中生成一个相应的目录作为默认工作和输出目录。

脚本

一个单独的 <script>.cmake 源文件可以在*脚本模式*下使用 cmake(1) 命令行工具和 :option:`-P <cmake -P> `选项。脚本模式只是运行给定 CMake 语言源文件中的命令,并不生成构建系统。它不允许定义构建目标或操作的 CMake 命令。

模块

DirectoriesScripts 中的 CMake 语言代码可以使用 include() 命令在包含上下文的范围内加载 <module>.cmake 源文件。请参阅 cmake-modules(7) 手册页,了解 CMake 发行版中包含的模块文档。项目源代码树也可以提供自己的模块并在 CMAKE_MODULE_PATH 变量中指定它们的位置。

句法

编码

CMake 语言源文件可以用 7 位 ASCII 文本编写,以实现跨所有支持平台的最大可移植性。换行符可以编码为“n”或“rn”,但在读取输入文件时将被转换为“n”。

请注意,实现是 8 位干净的,因此在具有支持此编码的系统 API 的平台上,源文件可以编码为 UTF-8。此外,CMake 3.2 及以上版本支持在 Windows 上以 UTF-8 编码的源文件(使用 UTF-16 调用系统 API)。此外,CMake 3.0 及更高版本允许在源文件中使用前导 UTF-8 字节顺序标记。

源文件

CMake 语言源文件由零个或多个 Command Invocations 组成,由换行符和可选的空格和 Comments 分隔:

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

请注意,任何不在 Command ArgumentsBracket Comment 内的源文件行都可以以 Line Comment 结尾。

命令调用

命令调用 是一个名称,后跟用空格分隔的括号括起来的参数:

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

例如:

add_executable(hello world.c)

命令名称不区分大小写。参数中嵌套的不带引号的括号必须平衡。每个 () 都作为文字 Unquoted Argument 提供给命令调用。这可以用于调用 if() 命令来包含条件。例如:

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

备注

3.0 之前的 CMake 版本要求命令名称标识符至少为 2 个字符。

2.8.12 之前的 CMake 版本默默地接受一个 Unquoted Argument 或一个 Quoted Argument 紧跟在 Quoted Argument 之后并且不被任何空格分隔。为了兼容性,CMake 2.8.12 及更高版本接受此类代码但会产生警告。

命令参数

Command Invocations 中有三种类型的参数:

argument ::=  bracket_argument | quoted_argument | unquoted_argument

括号参数

括号参数,受 Lua 长括号语法的启发,将内容包含在相同长度的开始和结束“括号”之间:

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    ::=  ']' '='* ']'

左括号写成 [ 后跟零个或多个 = 后跟 [。相应的右括号写为 ] 后跟相同数量的 = 后跟 ]。括号不嵌套。可以始终为左括号和右括号选择唯一的长度以包含其他长度的右括号。

括号参数内容由左括号和右括号之间的所有文本组成,除了紧跟在左括号之后的一个换行符(如果有的话)将被忽略。不执行封闭内容的评估,例如“转义序列”或“变量引用”。括号参数始终作为一个参数提供给命令调用。

例如::

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.
]=])

备注

3.0 之前的 CMake 版本不支持方括号参数。他们将左括号解释为 Unquoted Argument 的开始。

引用的参数

一个 quoted argument 在开始和结束双引号字符之间包含内容:

quoted_argument     ::=  '"' quoted_element* '"'
quoted_element      ::=  <any character except '\' or '"'> |
                         escape_sequence |
                         quoted_continuation
quoted_continuation ::=  '\' newline

引用的参数内容由开始和结束引号之间的所有文本组成。 Escape SequencesVariable References 都被求值。带引号的参数总是作为一个参数提供给命令调用。

例如:

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.
")

以奇数个反斜杠结尾的任何行上的最后一个 \ 被视为续行,并与紧随其后的换行符一起被忽略。例如:

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.\
")

备注

3.0 之前的 CMake 版本不支持使用 \ 继续。他们报告引用参数中包含以奇数个 \ 字符结尾的行的错误。

未引用的论点

unquoted argument 不包含在任何引用语法中。它可能不包含任何空格、()#"\,除非用反斜杠转义:

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

未加引号的参数内容由允许或转义字符的连续块中的所有文本组成。 Escape SequencesVariable References 都被求值。结果值的划分方式与“列表”划分为元素的方式相同。每个非空元素都作为参数提供给命令调用。因此,可以将未加引号的参数作为零个或多个参数提供给命令调用。

例如:

foreach(arg
    NoSpace
    Escaped\ Space
    This;Divides;Into;Five;Arguments
    Escaped\;Semicolon
    )
  message("${arg}")
endforeach()

备注

为了支持旧版 CMake 代码,未加引号的参数还可以包含双引号字符串("...",可能包含水平空格)和 make 样式变量引用($(MAKEVAR))。

未转义的双引号必须平衡,不能出现在未引用的参数的开头,并被视为内容的一部分。例如,未加引号的参数``-Da="b c"-Da=$(v)`` 和``a" "b"c"d`` 都按字面解释。它们可以写成带引号的参数 "-Da=\"b c\"""-Da=$(v)""a\" \"b\"c \"d",分别。

Make-style 引用按字面意思被视为内容的一部分,不会进行变量扩展。它们被视为单个参数的一部分(而不是单独的“$”、“(”、“MAKEVAR”和“)”参数)。

上面的“unquoted_legacy”作品代表了这样的论点。我们不建议在新代码中使用遗留的不带引号的参数。而是使用 Quoted ArgumentBracket Argument 来表示内容。

转义序列

*转义序列*是一个````后跟一个字符:

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 之外的 \; 都会对自身进行编码,但可以在 Unquoted Argument 中使用以对 ; 进行编码,而不会将参数值除​​以它。 Variable References`_ 中的;`` 编码文字``;`` 字符。 (另请参阅 policy CMP0053 文档了解历史注意事项。)

变量引用

*变量引用*的形式为``${<variable>}``,并在`Quoted Argument`_ 或`Unquoted Argument`_ 中进行计算。变量引用由指定变量或缓存条目的值替换,或者如果两者均未设置,则由空字符串替换。变量引用可以嵌套并从内向外求值,例如``${outer_${inner_variable}_variable}``。

文字变量引用可能由字母数字字符、字符“/_.+-”和“转义序列”组成。嵌套引用可用于评估任何名称的变量。另请参阅策略 CMP0053 文档,了解历史考虑因素以及为什么 $ 在技术上也是允许但不鼓励的原因。

Variables 部分记录了变量名称的范围及其值的设置方式。

*环境变量引用*的形式为“$ENV{<variable>}”。有关详细信息,请参阅“环境变量”部分。

*缓存变量引用*的形式为``$CACHE{<variable>}``,并被指定的缓存条目的值替换,而不检查同名的普通变量。如果缓存条目不存在,则将其替换为空字符串。有关详细信息,请参阅 CACHE

if() 命令有一个特殊的条件语法,允许以缩写形式``<variable>`` 代替``${<variable>}`` 的变量引用。然而,环境变量总是需要被引用为``$ENV{<variable>}``。

评论

注释以 # 字符开头,该字符不在 Bracket ArgumentQuoted Argument 内,或作为 Unquoted Argument 的一部分以 \ 转义。有两种类型的注释:Bracket CommentLine Comment

括号注释

# 紧跟一个 bracket_open 形成一个 bracket comment 由整个括号包围:

bracket_comment ::=  '#' bracket_argument

例如:

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

备注

3.0 之前的 CMake 版本不支持括号注释。他们将开头的 # 解释为 Line Comment 的开始。

行注释

# 后面没有紧跟着 bracket_open 形成一个*行注释*,一直运行到行尾:

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

例如:

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

控制结构

条件块

if()/elseif()/else()/endif() 命令分隔要有条件执行的代码块。

循环

foreach()/endforeach()while()/endwhile() 命令分隔要在循环中执行的代码块。在这样的块中, break() 命令可用于提前终止循环,而 continue() 命令可用于立即开始下一次迭代。

命令定义

macro()/endmacro()function()/endfunction() 命令分隔要记录的代码块,以便以后作为命令调用。

变量

变量是 CMake 语言中的基本存储单元。它们的值始终是字符串类型,尽管某些命令可能会将字符串解释为其他类型的值。 set()unset() 命令显式设置或取消设置变量,但其他命令也具有修改变量的语义。变量名称区分大小写,几乎可以包含任何文本,但我们建议坚持使用仅由字母数字字符加上“_”和“-”组成的名称。

变量具有动态范围。每个变量“set”或“unset”在当前范围内创建一个绑定:

块作用域

block() 命令可以为变量绑定创建一个新的范围。

功能范围

Command Definitionsfunction() 命令创建,创建命令,当调用这些命令时,在新的变量绑定范围内处理记录的命令。变量“set”或“unset”在此范围内绑定,并且对当前函数和其中的任何嵌套调用可见,但在函数返回后不可见。

目录范围

源代码树中的每个“目录”都有自己的变量绑定。在处理目录的 CMakeLists.txt 文件之前,CMake 复制当前在父目录中定义的所有变量绑定(如果有)以初始化新的目录范围。 CMake Scripts,当使用 cmake -P 处理时,在一个“目录”范围内绑定变量。

不在函数调用内的变量“set”或“unset”绑定到当前目录范围。

持久缓存

CMake 存储一组单独的“缓存”变量或“缓存条目”,其值在项目构建树中的多次运行中保持不变。缓存条目有一个独立的绑定范围,只能通过显式请求修改,例如通过 set()unset() 命令的 CACHE 选项。

在评估 Variable References 时,CMake 首先搜索函数调用堆栈(如果有)以查找绑定,然后回退到当前目录范围内的绑定(如果有)。如果找到“set”绑定,则使用它的值。如果找到“未设置”绑定,或未找到绑定,CMake 会搜索缓存条目。如果找到缓存条目,则使用它的值。否则,变量引用的计算结果为空字符串。 $CACHE{VAR} 语法可用于直接查找缓存条目。

cmake-variables(7) 手册记录了 CMake 提供的许多变量,或者在项目代码设置时对 CMake 有意义的变量。

备注

CMake 保留标识符:

  • ``CMAKE_``(大写、小写或混合大小写)开头,或

  • ``_CMAKE_``(大写、小写或混合大小写)开头,或

  • _ 开头,后跟任何 CMake 命令 的名称。

环境变量

环境变量就像普通的`Variables`_,有以下区别:

范围

环境变量在 CMake 进程中具有全局范围。它们永远不会被缓存。

参考

Variable References 具有``$ENV{<variable>}`` 的形式,使用 ENV 运算符。

初始化

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

请参阅:option:cmake -E env <cmake-E env> 命令行工具以在修改后的环境中运行命令。

检查

请参阅 cmake -E environment 命令行工具以显示所有当前环境变量。

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

列表

尽管 CMake 中的所有值都存储为字符串,但在某些情况下,字符串可能会被视为列表,例如在评估“未引用的参数”期间。在这样的上下文中,一个字符串被分成列表元素,方法是在 ; 字符之后不跟随不等数量的 [] 字符,并且前面没有紧跟 \。序列 \; 不划分值,但在结果元素中被 ; 替换。

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

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

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

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

通常,列表不支持包含“;”字符的元素。为避免出现问题,请考虑以下建议:

  • 许多 CMake 命令、变量和属性的接口都接受以分号分隔的列表。避免将包含分号的元素的列表传递给这些接口,除非它们记录了直接支持或某种转义或编码分号的方法。

  • 构造列表时,将元素中的 ; 替换为其他未使用的占位符。然后在处理列表元素时用 ; 代替占位符。例如,以下代码使用 | 代替 ; 字符:

    set(mylist a "b|c")
    foreach(entry IN LISTS mylist)
      string(REPLACE "|" ";" entry "${entry}")
      # use "${entry}" normally
    endforeach()
    

    ExternalProject 模块的 LIST_SEPARATOR 选项是使用这种方法构建的接口的示例。

  • generator expressions 列表中,使用 $<SEMICOLON> 生成器表达式。

  • 在命令调用中,尽可能使用 Quoted Argument 语法。被调用的命令将接收保留分号的参数内容。 Unquoted Argument 将以分号分隔。

  • function() 实现中,避免使用 ARGVARGN,它们不区分值中的分号和分隔值。相反,更喜欢使用命名位置参数和 ARGCARGV# 变量。使用 cmake_parse_arguments() 解析参数时,更喜欢它的 PARSE_ARGV 签名,它使用 ARGV# 变量。

    请注意,此方法不适用于 macro() 实现,因为它们使用占位符引用参数,而不是实际变量。