cmake-language(7)

组织结构

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

项目中的 CMake 语言源文件按以下方式组织:

目录

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

脚本

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

模块

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

语法

编码

CMake 语言源文件可以使用 7 位 ASCII 文本编写,以在所有受支持平台上实现最大程度的移植性。换行符可以编码为 \n\r\n,但在读取输入文件时将转换为 \n

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

源文件

CMake 语言源文件由零个或多个 命令调用组成,命令调用之间用换行符分隔,并可选择使用空格和注释

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_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)

命令名称不区分大小写。参数中嵌套的未加引号的括号必须平衡。每个 () 都作为字面量不带引号参数传递给命令调用。这可以用于调用 if() 命令以括起条件。例如

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

注意

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

2.8.12 之前的 CMake 版本会静默接受紧跟在带引号参数之后的不带引号参数带引号参数,并且它们之间没有任何空格。为了兼容性,CMake 2.8.12 及更高版本接受此类代码,但会生成警告。

命令参数

命令调用中有三种类型的参数:

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 版本不支持方括号参数。它们将开始方括号解释为不带引号参数的开始。

带引号参数

带引号参数将内容括在开始和结束双引号字符之间

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

带引号参数内容由开始和结束引号之间的所有文本组成。转义序列变量引用都会被评估。带引号参数始终作为完全一个参数传递给命令调用。

例如

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_element+ | unquoted_legacy
unquoted_element  ::=  <any character except whitespace or one of '()#"\'> |
                       escape_sequence
unquoted_legacy   ::=  <see note in text>

不带引号参数内容由允许或转义字符的连续块中的所有文本组成。转义序列变量引用都会被评估。结果值以与列表划分为元素相同的方式划分。每个非空元素都作为参数传递给命令调用。因此,不带引号参数可以作为零个或多个参数传递给命令调用。

例如

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 样式引用被视为内容的一部分,并按字面意思处理,不进行变量扩展。它们被视为单个参数的一部分(而不是单独的 $(MAKEVAR) 参数)。

上面的“unquoted_legacy”产生式表示此类参数。我们不建议在新代码中使用旧版不带引号的参数。而是使用带引号参数方括号参数来表示内容。

转义序列

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

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 分别编码制表符、回车符或换行符。\; 在任何变量引用之外编码自身,但可以在不带引号参数中使用以编码 ; 而不在其上划分参数值。\;变量引用内部编码字面 ; 字符。(另请参阅策略 CMP0053 文档以了解历史考虑因素。)

变量引用

变量引用具有 ${<variable>} 形式,并在带引号参数不带引号参数内部进行评估。变量引用被指定变量或缓存条目的值替换,如果两者都未设置,则替换为空字符串。变量引用可以嵌套,并从内到外进行评估,例如 ${outer_${inner_variable}_variable}

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

变量部分记录了变量名称的范围以及如何设置其值。

环境变量引用具有 $ENV{<variable>} 形式。有关更多信息,请参阅环境变量部分。

缓存变量引用具有 $CACHE{<variable>} 形式,并被指定缓存条目的值替换,而不检查是否具有相同名称的普通变量。如果缓存条目不存在,则替换为空字符串。有关更多信息,请参阅 CACHE

if() 命令具有特殊的条件语法,允许使用简写形式 <variable> 而不是 ${<variable>} 进行变量引用。但是,环境变量始终需要引用为 $ENV{<variable>}

注释

注释以 # 字符开头,该字符不在方括号参数带引号参数内,或在不带引号参数中用 \ 转义。注释有两种类型:方括号注释行注释

方括号注释

紧跟在 # 后面的 bracket_open 形成方括号注释,该注释由整个方括号封闭组成

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 版本不支持方括号注释。它们将开头的 # 解释为行注释的开始。

行注释

不是紧跟在 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() 命令可以为变量绑定创建一个新作用域。

函数作用域

function() 命令创建的命令定义创建的命令在调用时,会在新的变量绑定作用域中处理记录的命令。“set”或“unset”变量在此作用域中绑定,并且对于当前函数及其中的任何嵌套调用都可见,但在函数返回后不可见。

目录作用域

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

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

持久缓存

CMake 存储一组单独的“缓存”变量或“缓存条目”,这些变量或条目的值在项目构建树中的多次运行中持续存在。缓存条目具有隔离的绑定作用域,仅通过显式请求进行修改,例如通过 set()unset() 命令的 CACHE 选项。

在评估变量引用时,CMake 首先搜索函数调用堆栈(如果有)以查找绑定,然后回退到当前目录作用域中的绑定(如果有)。如果找到“set”绑定,则使用其值。如果找到“unset”绑定,或者未找到绑定,则 CMake 搜索缓存条目。如果找到缓存条目,则使用其值。否则,变量引用评估为空字符串。可以使用 $CACHE{VAR} 语法执行直接缓存条目查找。

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

注意

CMake 保留以下标识符:

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

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

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

环境变量

环境变量类似于普通变量,但有以下区别:

作用域

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

引用

变量引用具有 $ENV{<variable>} 形式,使用 ENV 运算符。

初始化

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

请参阅 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> 生成器表达式。

  • 在命令调用中,尽可能使用 带引号的参数 语法。被调用的命令将接收到保留分号的参数内容。一个 不带引号的参数 将在分号处分割。

  • function() 实现中,避免使用 ARGVARGN,因为它们无法区分值中的分号和分隔值的分号。相反,建议使用命名的位置参数以及 ARGCARGV# 变量。当使用 cmake_parse_arguments() 解析参数时,首选其 PARSE_ARGV 签名,该签名使用 ARGV# 变量。

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