cmake-language(7)

组织

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

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

目录

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

脚本

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

模块

无论是 目录 还是 脚本 中的 CMake 语言代码都可以使用 include() 命令来加载 <module>.cmake 源文件,该命令位于关联上下文的范围内。请参阅 cmake-modules(7) 手册页,以了解 CMake 发行版中包含的模块文档。项目源树还可以提供自己的模块,并在 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 样式引用被逐字视为内容的一部分,而不进行变量展开。它们被视为单个参数的一部分(而不是单独的 $(\span>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 文档,以了解历史原因和出于何种原因从技术上允许 $ 但不鼓励使用。

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

环境变量引用的格式为 $ENV{<variable>}。有关详细信息,请参见 Environment Variables 部分。

缓存变量引用的格式为 $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() 实现,因为它们使用占位符引用参数,而不是使用真实变量。