CMake语法结构说明

文章目录

  • [一. 组织](#一. 组织)
    • [1. 目录](#1. 目录)
    • [2. 脚本](#2. 脚本)
    • [3. 模块](#3. 模块)
  • [二. 语法](#二. 语法)
  • [三. 控制结构](#三. 控制结构)
    • [1. 条件块](#1. 条件块)
    • [2. 循环](#2. 循环)
    • [3. 命令定义](#3. 命令定义)
  • [四. 变量](#四. 变量)
  • [五. 环境变量](#五. 环境变量)
  • [六. 列表](#六. 列表)

一. 组织

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

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

1. 目录

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

2. 脚本

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

3. 模块

目录或脚本中的CMake语言代码可以使用include()命令在include上下文的范围内加载<module>.CMake源文件。项目源树也可以提供它们自己的模块,并CMAKE_MODULE_PATH变量中指定它们的位置。

二. 语法

1. 编码

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字节顺序标记。

2. 源文件

CMake语言源文件由零个或多个命令调用组成,这些命令调用由换行符和可选的空格和注释分隔:

bash 复制代码
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'>

请注意,任何不在命令参数或括号注释内的源文件行都可以以行注释结束。

3. 命令调用

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

bash 复制代码
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

例如:

bash 复制代码
add_executable(hello world.c)

命令名不区分大小写。参数中嵌套的无引号括号必须保持平衡。每个(或)都作为未引用的实参提供给命令调用。这可以用于调用if()命令来封闭条件。例如:

bash 复制代码
if(FALSE AND (FALSE OR TRUE)) # evaluates to FALSE

4. 命令参数

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

bash 复制代码
argument ::=  bracket_argument | quoted_argument | unquoted_argument

(1)括号参数

受Lua长括号语法启发,括号自变量将内容包含在相同长度的开头和结尾括号之间:

bash 复制代码
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    ::=  ']' '='* ']'

左括号写为[后接零或更多=后接[。相应的右括号写为]后接相同数量的=后接]。支架不嵌套。可以始终为打开和关闭括号选择唯一的长度,以包含其他长度的关闭括号。

括号参数内容由位于左括号和右括号之间的所有文本组成,除了左括号后面的一行换行符(如果有的话)将被忽略。不执行对所附内容(如转义序列或变量引用)的评估。括号参数总是作为一个参数提供给命令调用。例如:

bash 复制代码
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.
]=])

(2)带引号的参数

带引号的参数包含位于开头和结尾双引号字符之间的内容:

bash 复制代码
quoted_argument     ::=  '"' quoted_element* '"'
quoted_element      ::=  <any character except '\' or '"'> |
                         escape_sequence |
                         quoted_continuation
quoted_continuation ::=  '\' newline

带引号的参数内容包括位于左引号和右引号之间的所有文本。转义序列和变量引用都会进行求值。引用的参数总是作为一个参数提供给命令调用。例如:

bash 复制代码
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.
")

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

bash 复制代码
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)未引用的参数

未引用的参数不包含在任何引用语法中。它不能包含任何空格、(,)、#、"或\,除非用反斜杠转义:

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

未引用的参数内容由允许字符或转义字符的连续块中的所有文本组成。转义序列和变量引用都会进行求值。结果值的划分方式与列表划分为元素的方式相同。每个非空元素都作为参数提供给命令调用。因此,一个未引用的参数可以作为零个或多个参数提供给命令调用。例如:

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

5. 转义序列

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

bash 复制代码
escape_sequence  ::=  escape_identity | escape_encoded | escape_semicolon
escape_identity  ::=  '\' <match '[^A-Za-z0-9;]'>
escape_encoded   ::=  '\t' | '\r' | '\n'
escape_semicolon ::=  '\;'

后面跟着一个非字母数字字符的\只是对文字字符进行编码,而不将其解释为语法。\t、\r或分别对制表符、回车符或换行符进行编码。A \:在任何变量引用之外对其自身进行编码,但可以在未引用的参数中用于对;而不除以其上的参数值。A\:内部变量引用对文本进行编码;

6. 变量引用

变量引用的形式为${<variable>},在带引号的参数或未带引号的自变量中进行求值。变量引用由指定变量或缓存项的值替换,或者如果两者都未设置,则由空字符串替换。变量引用可以嵌套并由内而外进行评估,例如${outer_${inner_variable}_variable}

文字变量引用可以由字母数字字符、字符/_.±、,和转义序列。嵌套引用可用于评估任何名称的变量。

  • Variables部分记录了变量名称的范围以及如何设置它们的值。
  • 环境变量引用的形式为$ENV{<variable>}
  • 缓存变量引用的形式为$cache{<variable>},并由指定ca的值替换
  • if()命令有一个特殊的条件语法,允许使用缩写形式<variable>而不是${<variable>}的变量引用。但是,环境变量总是需要被引用为$ENV{<variable>}

7. 注释

注释以#字符开头,该字符不在括号参数、带引号的参数内,也不作为未带引号参数的一部分以\转义。有两种类型的注释:

  • 括号注释
  • 行注释

(1)括号注释

#后面紧跟着一个括号,形成一个括号注释,由整个括号外壳组成:

bash 复制代码
bracket_comment ::=  '#' bracket_argument

例如:

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

(2)行注释

#后面没有紧跟括号,形成一个行注释,该注释一直持续到行的末尾:

bash 复制代码
line_comment ::=  '#' <any text not starting in a bracket_open
                       and not containing a newline>

例如:

bash 复制代码
# This is a line comment.
message("First Argument\n" # This is a line comment :)
        "Second Argument") # This is a line comment.

三. 控制结构

1. 条件块

if(),eleif(),else(),endif(),命令定义要有条件执行的代码块。

2. 循环

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

3. 命令定义

macro(),endmark(),function(),endfunction(),命令定义了要记录的代码块,以便以后作为命令调用。

四. 变量

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

变量具有动态范围。每个变量setunset在当前作用域中创建一个绑定:

  • 块范围:block()命令可以为变量绑定创建一个新的作用域。
  • 功能范围:function()命令创建的命令定义创建的命令在被调用时处理新变量绑定范围中记录的命令。变量setunset在此范围内绑定,并且对于当前函数及其内的任何嵌套调用都可见,但在函数返回后不可见。
  • 目录作用域:源树中的每个目录都有自己的变量绑定。在处理目录的CMakeLists.txt文件之前,CMake复制当前在父目录中定义的所有变量绑定(如果有的话),以初始化新的目录作用域。当使用CMake -P处理时,CMake脚本将变量绑定到一个"目录"范围中。
    不在函数调用内的变量setunset绑定到当前目录作用域。
  • 永久缓存:CMake存储一组单独的"缓存"变量或"缓存条目",其值在项目构建树中的多个运行中保持不变。缓存条目有一个单独的绑定作用域,该绑定作用域仅通过显式请求进行修改,例如通过set()unset()命令的Cache选项进行修改。

在评估变量引用时,CMake首先在函数调用堆栈(如果有)中搜索绑定,然后返回到当前目录作用域中的绑定(如果有的话)。如果找到set绑定,则使用其值。如果找到"未设置"的绑定,或者没有找到绑定,CMake将搜索缓存条目。如果找到缓存条目,则使用其值。否则,变量引用的计算结果为空字符串。$CACHE{}VAR}语法可用于直接查找缓存条目。

五. 环境变量

环境变量与普通变量一样,有以下区别:

  • 范围:环境变量在CMake过程中具有全局作用域。它们从不缓存。
  • 参考文献:变量引用的形式为$ENV{<Variable>},使用ENV运算符。
  • 初始化:CMake环境变量的初始值是调用进程的初始值。可以使用set()unset()命令更改值。这些命令只影响正在运行的CMake进程,而不会影响整个系统环境。更改后的值不会写回调用进程,后续的构建或测试进程也看不到这些值。

六. 列表

尽管CMake中的所有值都存储为字符串,但在某些上下文中,例如在评估未引用的参数时,字符串可能会被视为列表。在这样的上下文中,字符串通过在上进行拆分而被划分为列表元素;不在[]字符数不相等且前面不紧跟\的字符。序列\:不划分值,但替换为:在得到的元素中。

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

bash 复制代码
set(srcs a.c b.c c.c) # sets "srcs" to "a.c;b.c;c.c"

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

bash 复制代码
set(x a "b;c") # sets "x" to "a;b;c", not "a;b\;c"

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

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

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

    bash 复制代码
    set(mylist a "b|c")
    foreach(entry IN LISTS mylist)
      string(REPLACE "|" ";" entry "${entry}")
      # use "${entry}" normally
    endforeach()
  • 在生成器表达式列表中,使用$<SEMICON>生成器表达式。

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

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

相关推荐
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
机器视觉知识推荐、就业指导8 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.999 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王9 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_10 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀10 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
liujjjiyun10 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥10 小时前
c++中mystring运算符重载
开发语言·c++·算法