cmake-language(7)

组织结构

CMake 输入文件使用“CMake 语言”编写,保存在名为 CMakeLists.txt 的源文件中,或者扩展名为 .cmake 的文件名中。列表文件 是包含 CMake 命令并由工具处理的任何此类源文件的通用名称。

项目中的 CMake 语言源文件被组织成

  • 目录CMakeLists.txt),

  • 脚本<script>.cmake),和

  • 模块<module>.cmake)。

目录

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

脚本

单个 <script>.cmake 源文件可以通过使用 cmake(1) 命令行工具并加上 -P 选项以脚本模式处理。脚本模式仅运行给定 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-16 调用系统 API)编码为 UTF-8 的源文件。此外,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

注意

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

CMake 2.8.12 之前的版本会默默接受紧跟在 带引号参数 之后且未被任何空格分隔的 不带引号参数带引号参数。为了兼容性,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.
]=])

注意

CMake 3.0 之前的版本不支持括号参数。它们会将开头的 [ 解释为 不带引号参数 的开始。

带引号参数

带引号参数 将内容括在开头和结尾的双引号字符之间。

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

注意

CMake 3.0 之前的版本不支持使用 \ 进行续接。它们会在包含以奇数个 \ 字符结尾的行的带引号参数中报告错误。

不带引号参数

不带引号参数 未被任何引用语法括起来。它不能包含任何空格、()#"\,除非被反斜杠转义。

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

注意

CMake 3.0 之前的版本不支持括号注释。它们会将开头的 # 解释为 行注释 的开始。

行注释

一个未紧跟 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() 命令显式设置或取消设置变量,但其他命令的语义也会修改变量。变量名称区分大小写,并且可以包含几乎任何文本,但我们建议坚持使用仅包含字母数字字符加上 _- 的名称。

变量具有动态作用域。每个“设置”或“取消设置”变量都会在当前作用域中创建一个绑定。

块作用域

block() 命令可以为变量绑定创建一个新作用域。

函数作用域

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

目录作用域

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

不在函数调用中的“设置”或“取消设置”变量会绑定到当前目录作用域。

持久缓存

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

在评估 变量引用 时,CMake 首先搜索函数调用堆栈(如果存在)以查找绑定,然后回退到当前目录作用域中的绑定(如果存在)。如果找到“设置”绑定,则使用其值。如果找到“取消设置”绑定,或未找到任何绑定,CMake 接着搜索缓存条目。如果找到缓存条目,则使用其值。否则,变量引用将求值为一个空字符串。可以使用 $CACHE{VAR} 语法来直接查找缓存条目。

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

注意

CMake 保留的标识符是

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

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

  • _ 开头,后面跟着任何 CMake Command 的名称。

环境变量

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

作用域

环境变量在 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 选项是一个使用此方法构建的接口示例。

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

  • 在命令调用中,尽可能使用 带引号参数 语法。被调用的命令将接收参数内容,并保留分号。 不带引号参数 将根据分号进行分割。

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

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