CMake语法结构说明

2023-10-22 06:28
文章标签 说明 cmake 语法结构

本文主要是介绍CMake语法结构说明,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一. 组织
    • 1. 目录
    • 2. 脚本
    • 3. 模块
  • 二. 语法
    • 1. 编码
    • 2. 源文件
    • 3. 命令调用
    • 4. 命令参数
      • (1)括号参数
      • (2)带引号的参数
      • (3)未引用的参数
    • 5. 转义序列
    • 6. 变量引用
    • 7. 注释
      • (1)括号注释
      • (2)行注释
  • 三. 控制结构
    • 1. 条件块
    • 2. 循环
    • 3. 命令定义
  • 四. 变量
  • 五. 环境变量
  • 六. 列表

一. 组织

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

  • 目录Directories (CMakeLists.txt),
  • 脚本Scripts (<script>.cmake)
  • 模块Modules (<module>.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语言源文件由零个或多个命令调用组成,这些命令调用由换行符和可选的空格和注释分隔:

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. 命令调用

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

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

4. 命令参数

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

argument ::=  bracket_argument | quoted_argument | unquoted_argument

(1)括号参数

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

bracket_argument ::=  bracket_open bracket_content bracket_close
bracket_open     ::=  '[' '='* '['
bracket_content  ::=  <any text not containing a bracket_close withthe 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.
]=])

(2)带引号的参数

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

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)未引用的参数

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

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

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

foreach(argNoSpaceEscaped\ SpaceThis;Divides;Into;Five;ArgumentsEscaped\;Semicolon)message("${arg}")
endforeach()

5. 转义序列

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

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)括号注释

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

bracket_comment ::=  '#' bracket_argument

例如:

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

(2)行注释

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

line_comment ::=  '#' <any text not starting in a bracket_openand 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.

三. 控制结构

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()命令将多个值作为列表存储到目标变量中:

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()
    
  • 在生成器表达式列表中,使用$<SEMICON>生成器表达式。

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

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

这篇关于CMake语法结构说明的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/259676

相关文章

Redis中哨兵机制和集群的区别及说明

《Redis中哨兵机制和集群的区别及说明》Redis哨兵通过主从复制实现高可用,适用于中小规模数据;集群采用分布式分片,支持动态扩展,适合大规模数据,哨兵管理简单但扩展性弱,集群性能更强但架构复杂,根... 目录一、架构设计与节点角色1. 哨兵机制(Sentinel)2. 集群(Cluster)二、数据分片

Springboot项目构建时各种依赖详细介绍与依赖关系说明详解

《Springboot项目构建时各种依赖详细介绍与依赖关系说明详解》SpringBoot通过spring-boot-dependencies统一依赖版本管理,spring-boot-starter-w... 目录一、spring-boot-dependencies1.简介2. 内容概览3.核心内容结构4.

redis和redission分布式锁原理及区别说明

《redis和redission分布式锁原理及区别说明》文章对比了synchronized、乐观锁、Redis分布式锁及Redission锁的原理与区别,指出在集群环境下synchronized失效,... 目录Redis和redission分布式锁原理及区别1、有的同伴想到了synchronized关键字

MySQL 临时表创建与使用详细说明

《MySQL临时表创建与使用详细说明》MySQL临时表是存储在内存或磁盘的临时数据表,会话结束时自动销毁,适合存储中间计算结果或临时数据集,其名称以#开头(如#TempTable),本文给大家介绍M... 目录mysql 临时表详细说明1.定义2.核心特性3.创建与使用4.典型应用场景5.生命周期管理6.注

Java中数组与栈和堆之间的关系说明

《Java中数组与栈和堆之间的关系说明》文章讲解了Java数组的初始化方式、内存存储机制、引用传递特性及遍历、排序、拷贝技巧,强调引用数据类型方法调用时形参可能修改实参,但需注意引用指向单一对象的特性... 目录Java中数组与栈和堆的关系遍历数组接下来是一些编程小技巧总结Java中数组与栈和堆的关系关于

mybatis-plus QueryWrapper中or,and的使用及说明

《mybatis-plusQueryWrapper中or,and的使用及说明》使用MyBatisPlusQueryWrapper时,因同时添加角色权限固定条件和多字段模糊查询导致数据异常展示,排查发... 目录QueryWrapper中or,and使用列表中还要同时模糊查询多个字段经过排查这就导致只要whe

SpringBoot改造MCP服务器的详细说明(StreamableHTTP 类型)

《SpringBoot改造MCP服务器的详细说明(StreamableHTTP类型)》本文介绍了SpringBoot如何实现MCPStreamableHTTP服务器,并且使用CherryStudio... 目录SpringBoot改造MCP服务器(StreamableHTTP)1 项目说明2 使用说明2.1

JAVA覆盖和重写的区别及说明

《JAVA覆盖和重写的区别及说明》非静态方法的覆盖即重写,具有多态性;静态方法无法被覆盖,但可被重写(仅通过类名调用),二者区别在于绑定时机与引用类型关联性... 目录Java覆盖和重写的区别经常听到两种话认真读完上面两份代码JAVA覆盖和重写的区别经常听到两种话1.覆盖=重写。2.静态方法可andro

zookeeper端口说明及介绍

《zookeeper端口说明及介绍》:本文主要介绍zookeeper端口说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、zookeeper有三个端口(可以修改)aVNMqvZ二、3个端口的作用三、部署时注意总China编程结一、zookeeper有三个端口(可以

Go语言中make和new的区别及说明

《Go语言中make和new的区别及说明》:本文主要介绍Go语言中make和new的区别及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1 概述2 new 函数2.1 功能2.2 语法2.3 初始化案例3 make 函数3.1 功能3.2 语法3.3 初始化