CMake 语法快速参考

472 阅读7分钟

CMake 语法快速参考

该文档以 CMake 3.24 版本的语法为准,排除了官方不推荐的语法,仅建议作为快速了解 CMake 语法以及简单参考使用。更为详尽的描述请见:cmake.org/cmake/help/…

文件组织

一个项目中的 CMake 语言源文件被组织成以下三种类型:

目录

CMake 处理一个项目源代码树的入口点为顶级源目录的 CMakeLists.txt 文件。该文件可能包含整个构建规范或使用 add_subdirectory() 命令添加子目录到构建系统当中。

add_subdirectory() 命令添加的所有子目录都须包含一个 CMakeLists.txt 文件作为其入口点。CMake 在构建系统中会根据生成其对应的目录树作为其默认的工作/输出目录。

脚本

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

模块

目录脚本中的 CMake 语言代码可能使用 include() 在命令所在的上下文内加载一个 <module>.cmake 源文件。(见 cmake-modules(7) 文档手册页 CMake 发行版中包含的模块。)

项目源代码树也可以提供自己的模块,在 CMAKE_MODULE_PATH 环境变量上指定。

语法

命令调用

命令名(参数1 参数2 ...)

命令名称不区分大小写。参数中嵌套的不带引号的括号必须平衡。

命令参数

括号参数

括号参数以 xNx \in \N[ + xx= + [ 开头,以] + xx= + ] 结束。

示例:

message([=[
这是括号长度为 1 的括号参数中的第一行。
转义和 ${变量} 不会被评估。
即使它包含一个 ; 字符,这里始终是一个参数。
这段文本不会在长度为 0 的右括号结束上,例如 ]]。
但它会被长度为 1 的右括号结束。
]=])

带引号的参数

带引号的参数包含在开始和结束双引号字符之间的内容:

示例:

message("这是一个包含多行的引用参数。
即使它包含一个 ; 字符,这里始终是一个参数。
转义和 ${变量} 都会被评估。
这段文本不会被转义过的双引号结束,就像:\"。
但它会被未转义的双引号结束。
")

不带引号的参数

该类型的参数不能使用带引号的参数的语法,除非被反斜杠 \ 转义。

示例:

foreach(arg
    无空格
    转义过的\ 空格
    这将;会被;分成;五个;参数
    转义过的\;分号
    )
  message("${arg}")
endforeach()

注意,该类型参数的内容第一个字符不应为括号,这种情况应当使用其他类型的参数。

变量引用

变量引用的格式为 ${<variable>}。变量引用只会在带引号的参数不带引号的参数中被评估。变量引用会被变量的值替换,若变量未设置,则会替换为空字符串。变量引用可以由内到外嵌套,例如 ${outer_${inner_variable}_variable}.

文字变量引用可能由字母、数字、/_.+- 和转义序列组成。嵌套引用可用于评估任何名称的变量。

变量 章节记录了变量名的范围以及它们的值是如何设置的。

一个 环境变量引用 的格式为 $ENV{<variable>}。参阅官方文档的 环境变量 部分以了解更多信息。

一个 缓存变量引用 的格式为 $CACHE{<variable>}。参阅官方文档的 CACHE 部分以了解更多信息。

if() 命令允许以 <variable> 的形式直接引用变量,但环境和缓存变量不能使用这种语法。

注释

括号注释

括号注释由 # + 括号参数 组成。

#[[这是一个括号注释。
它一直延续到右括号。]]
message("第一个参数\n" #[[括号注释]] "第二个参数")

控制结构

条件块

if()/ elseif()/ else()/ endif() 命令分隔要有条件地执行的代码块。

循环

foreach()/ endforeach()while()/ endwhile()命令分隔要在循环中执行的代码块。在该此类的块内可以使用 break()命令终止循环,或使用 continue() 命令立即开始下一次迭代。

变量

变量是 CMake 语言中的基本存储单元,其值总是为字符串类型。set()unset() 显式命令分别用于设置或取消设置变量。变量名区分大小写,可以由几乎任何文本组成,但我们建议坚持使用仅包含的名称字母数字字符加上 _-.

变量具有动态作用域。每个变量的 setunset 会在当前作用域内创建一个绑定:

  • 函数作用域

    function() 命令进行命令定义而创建的命令,调用时会在新变量所绑定作用域内处理记录的命令。变量的 setunset 在此范围内绑定并且对当前函数可见以及其任何嵌套调用中可见,在函数返回之后销毁。

  • 目录作用域

    每个目录都有自己的变量绑定。在 CMakeLists.txt 文件处理前,CMake 会复制父目录中当前定义的所有变量绑定,用于初始化新的目录作用域。 对于 CMake Scripts ,处理 cmake -P 命令时, 也会绑定一个目录作用域的变量。

    不在函数调用内的变量 setunset 将绑定到当前目录范围。

  • 持久缓存

    CMake 存储一组单独的“缓存”变量或“缓存条目”,其值在项目构建树中的多次运行中持续存在。缓存条目有一个独立的绑定范围,仅由显式请求修改,例如由 CACHE的选项 set()unset() 命令。

在评估变量引用时,CMake 首先搜索用于绑定的函数调用堆栈(如果有),然后回退到当前目录范围内的绑定(如果有)。如果找到一个“set”绑定,则使用它的值。如果找到 “unset” 绑定或没有找到绑定,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 环境 显示所有当前环境变量的命令行工具。

这个 cmake-env-variables(7) 手册记录了对 CMake 有特殊意义的变量。

列表

字符串在某些情况下可能会被视为列表,例如在 不带引号的参数 的评估中。在这种情况下,字符串 通过 ; 字符 拆分成列表元素。

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

set(srcs a.c b.c c.c) # 设置 "srcs" 为 "a.c;b.c;c.c"

列表适用于简单的用例,例如源文件列表,不应用于复杂的数据处理任务。大多数构造列表的命令都不会对 ; 进行转义:

set(x a "b;c") # "x" 将设置为 "a;b;c", 而不是 "a;b\;c"

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

  • 许多 CMake 的命令、变量和属性的接口都接收分号分隔的列表,所以应当避免传递包含元素带分号的列表,除非它们的文档说明了会直接支持或某种方式来转义或编码分号。

  • 构造列表时,在各元素上用其他占位符替换 ;。 然后在处理列表元素时再将占位符替换回 ;。例如,以下代码使用 | 代替 ; 字符:

    set(mylist a "b|c")
    foreach(entry IN LISTS mylist)
      string(REPLACE "|" ";" entry "${entry}")
      # 在这之后便可直接正常使用 "${entry}"
    endforeach()
    

    ExternalProject 模块的 LIST_SEPARATOR 选项是一个使用这种方法构建的接口没那 示例。

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

  • 在命令调用中, 尽可能使用带引号的参数语法,因为被调用的命令将接收保留分号的参数内容。而不带引号的参数会被分号所拆分。

  • function() 的实现中,避免使用 ARGVARGN, 因为它不会将值中的分号与那些互相分隔的值区分开来。更推荐使用命名的位置参数和 ARGCARGV# 变量。当使用 cmake_parse_arguments() 解析参数时,推荐使用它的 PARSE_ARGV 签名,因为其使用 ARGV#变量。

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

Footnotes

  1. cmake.org/cmake/help/…

  2. cmake.org/cmake/help/…

  3. cmake.org/cmake/help/…