CMake 程序分类
根据用途可以分为三类:
- 目录程序 CMakeLists.txt
我们开发者绝大部分情况下接触的是这里文件,即 CMake 被用于构建时,需要处理项目的源程序。处理的入口,就是项目顶层目录下的 CMakeLists.txt。CMakeLists.txt 构成了源程序逻辑上的目录结构。
- 脚本 script.cmake
这里程序我们很少接触,是指在行以 -P 参数运行 CMake 命令行工具执行脚本类型的 CMake 程序。这种 CMake 程序不会配置生成任何构建系统,因此一些与构建相关的 CMake 命令是不允许出现在脚本中的。
【注:绝大部分情况下,可以忽略这种用法】
- 模块 module.cmake
CMake 的目录程序和脚本均可以通过 include 等命令引用 CMake 模块程序。CMake 提供了很多预制模块供用户使用,多数与环境检测、搜索使用第三方库有关。
【注:这种模块使用的不多,但是有必要了解】
命令调用
CMake 程序几乎完全由命令调用构成。是因为除此之外,只剩下注释和空白符。CMake 程序中的 if 条件分支、for 循环等程序结构统一采用命令调用形式。
命令调用的形式即先写命令名称,其后跟括号括起来的命令参数。注意,CMake 的命令一般不区分大小写,但是一般使用小写。
最简单的命令调用示例如下:
命令参数
它出现在命令的括号中,可以由以下三种类型:
- 引号参数
即用引号括起来的参数,而且 CMake 规定它必须使用双引号。引号参数作为一个整体传递给命令,引号中间的空白符也会作为这个整体的一部分。那么引号参数中不仅可以包含空格,也可以包含换行符。
引号参数示例
- 非引号参数
非引号参数即没有被引号包裹的参数。这种参数不能包含任何空白符,也不能包括圆括号、#符号、双引号和反斜杠,除非经过转移。非引号参数也支持变量引用和转义字符。
非引号参数不总是作为一个整体传递给命令,它有可能被拆分成若干参数传递。实际上,非引号参数在被传递前,会被当做CMake列表来处理,而列表中的每一个元素都会作为一个单独的参数传递个命令。
以下是一个非引号参数的示例:
- 括号参数
与引号参数一样,CMake 的括号参数也会作为一个整体传递给命令。通过自定义的特殊括号将原始文本包括在其中,它不处理文本中的任何特殊字符(包括转义字符)或变量引号语法,直接保留原始文本。
一个简单的括号参数示例
变量
CMake 变量与其他编程语言的不同之处在于:其数据类型总是文本类型的,只不过在使用时,文本型的变量可能被一些命令解释成数值、列表等。
变量分类
CMake 变量有三种:
- 普通变量
大多数变量都是普通变量,它们具有特定的作用于。
- 缓存变量
被缓存起来的变量,被持久化到缓存文件 CMakeCache.txt 中。 CMake 程序每次执行时都会从缓存文件中读取缓存变量的值。这样可以避免每次都执行一些耗时的过程来获取数据。
缓存变量具有全局作用域。
【题外话】说到缓存变量,都是泪啊。第一次迁移公司项目到 CMake + GCC 编译环境,把SDK 拆分成了各个模块module1/module2/module3等等,应用文件夹下不同的应用 app1/app2/app3 依赖不同的模块,所以不是所有的模块都需要参与编译,而且某些模块有互斥关系。我第一次写 CMake 缺少经验,发现 appX 中设置的变量 MODULE_<x>_CFG怎么在模块文件夹下总是初始值,又或者在某个地方修改了 MODULE_<x>_CFG 变量的值但是别的地方没生效,这个问题把我带坑里好长时间。后来查资料解决办法是用缓存变量,SDK 需要配置两次,第一次配置会修改 MODULE_<x>_CFG 变量,第二次才把修改后的 MODULE_<x>_CFG 作用域整个工程,所有的模块都可以正确识别这个变量。虽说这种用法可以解决问题,但是不够优雅。
定义缓存变量的方法
set(<变量名> <值> … CACHE <变量类型> <变量描述> [FORCE])
变量类型有以下几种:
- BOOL 布尔类型;
- FILEPATH 文件路径类型;
- PATH 目录路径类型;
- STRING 文本类型
- INTERNAL 内部使用(隐含设置 FORCE 参数)
- 环境变量
即操作系统中的变量,因此它对于 CMake 进程来说具有全局的作用域。
定义环境变量的语法如下:
set (ENV${<变量名>} <值>)
通过 CMake 的 set 命令定义的环境变量只会影响当前的 CMake 进程,不会影响到父进程或者系统的环境变量设置。
列表(变量)
列表即分号隔开的字符串。
定义列表变量没有什么特别的,可以用 set 命令定义列表变量。首先可以利用引号参数直接定义一个包含分号的字符串,这就是一个列表。其次 set 命令还支持指定多个作为变量值的参数,这样引号参数和非引号参数都可以使用。
条件语法
在 CMake 中几乎一切都是命令,没有表达式语句,即条件语法也是命令中特定的参数形式。
常量、变量和字符串条件
常量、变量和字符串条件这3种条件在形式上完全一致,需要根据上下文及量的值来判断具体是哪一种条件。
常量条件
常量条件仅有一个常量组成,分为真值常量和假值常量。
if 命令中使用的常量条件形式如下:
常量类型 |
常量值 |
条件结果 |
真值常量 |
1/ON/YES/TRUE/Y/非零数值(不区分大小写) |
真 |
假值常量 |
0/OFF/NO/FALSE/N/IGNORE/空字符串/NOTFOUND/或以-NOTFOUND结尾的字符串(不区分大小写) |
假 |
变量和字符串
如果条件中仅包含一个字符串,且这个字符串不是真值常量或者假值常量,那么它还有可能是一个变量的名称。如果以这个字符串为名的变量确实存在,则它是一个变量条件,否则是一个字符串条件。它们的形式如下:
if (<字符串|变量>)
endif()
- 如果条件中的字符串是一个变量的名称,且这个变量的值不是一个假值常量,那么条件为真。
- 在其他情况下(如果指定的变量的值为假或者变量为定义),条件为假。
作者在此处举了一个奇怪的变量的示例,如下所示:
代码中(1)处 if(on) 遇到了 on ,它是一个字符串常量,为真值,所以调用了第6行代码打印 ON。
代码中(2)处 if(${on}) 先取出变量 on 的值,即 OFF 字符串常量,而在 if() 条件中 OFF 是假值,故调用了第14行代码。
有点绕,注意 if() 条件先判断字符串字面量是否是常量,然后再判断是否是变量。
逻辑运算
条件语法中可以包含 AND/OR/NOT 三种逻辑运算,参与运算的也是符合条件语法的参数:
if (条件1 AND 条件2)
endif()
if (条件2 OR 条件2)
endif()
if (NOT 条件)
endif()
有编程经验的人一看就懂,无需多言。
单参数条件
即条件中调用命令,命令接收一个参数,以命令的结果作为条件的判断参数。一般用于存在性判断和类型判断。
条件语法 |
条件判断类型 |
描述 |
If(COMMAND <命令名称>) |
命令判断 |
当<命令名称>指代一个可被调用的命令、宏、或函数时,条件为真,否则为假 |
If(POLICY <策略名称>) |
策略判断 |
当<策略名称>指代一个已定义的策略时,条件为真,否则为假 |
If(TARGET <目标名称>) |
目标判断 |
当<目标名称>指代一个在任意目录用 add_executable/add_library或者add_custom_target 命令创建的目标时,条件为真,否则为假 |
If(TEST <测试名称>) |
测试判断 |
当<测试名称>指代一个用add_test命令创建的测试时,条件为真,否则为假 |
If(DEFINED <变量名称>) |
变量定义判断 |
当<变量名称>指代一个变量时,条件为真,否则为假 |
If(CACHE{<缓存变量名称>}) |
缓存变量定义判断 |
当<缓存变量名称>指代一个缓存变量时,条件为真,否则为假 |
If(ENV{环境变量名称}) |
环境变量定义判断 |
当<环境变量名称>指代一个环境变量时,条件为真,否则为假 |
If(EXISTS <文件或目录路径>) |
文件或目录存在判断 |
当指定的<文件或目录路径>确实存在时,条件为真,否则为假。该条件要求路径为绝对路径。另外,如果路径指向一个符号链接,那么仅当符号链接对应的文件或目录存在时,条件为真。 |
If(IS_DIRECTORY <目录路径>) |
目录判断 |
当指定的<目录路径>确实存在且是一个目录时,条件为真,否则为假。该条件要求路径为绝对路径。 |
If(IS_SYMLINK <文件路径>) |
符号链接判断 |
当指定的<文件路径>确实存在且是一个符号链接时,条件为真,否则为假。该条件要求路径为绝对路径。 |
If(IS_ABSOLUTE <路径>) |
绝对路径判断 |
当指定的<路径>是一个绝对路径时,条件为真,否则为假。 |
双参数条件
双参数条件通过两个参数的取值来决定条件是否为真,一般用于比较关系的判断。
数值比较
数值比较有 “小于”,“小于等于”,“等于”,“大于等于”,“大于” 这5中比较关系。当关系成立时,条件为真,否则为假。
if (<字符串 | 变量> LESS <字符串 | 变量>) # 小于
if (<字符串 | 变量> LESS_EQUAL <字符串 | 变量>) # 小于等于
if (<字符串 | 变量> EQUAL <字符串 | 变量>) # 等于
if (<字符串 | 变量> GREATER_EQUAL <字符串 | 变量>) # 大于等于
if (<字符串 | 变量> GREATER <字符串 | 变量>) # 大于
对于 <字符串> 或 <变量> 的取值规则:如果它是一个存在的变量名,则变量的值,否则取字符串本身。由于这一组条件仅用于数值比较,取值会被转换为数值类型后再进行比较。
字符串比较
字符串同样也有5种关系,比较时会根据字典序决定两个字符串取值的大小:
if (<字符串 | 变量> STRLESS <字符串 | 变量>) # 小于
if (<字符串 | 变量> STRLESS_EQUAL <字符串 | 变量>) # 小于等于
if (<字符串 | 变量> STREQUAL <字符串 | 变量>) # 等于
if (<字符串 | 变量> STRGREATER_EQUAL <字符串 | 变量>) # 大于等于
if (<字符串 | 变量> STRGREATER <字符串 | 变量>) # 大于
字符串匹配
用于判断字符串是否匹配特定的正则表达式,仅当匹配成功时,条件为真,否则为假。
if (<字符串 | 变量> MATCHES <正则表达式>)
版本号比较
版本号的格式为: 主版本号[.此版本号[.补丁版本号[.修订版本号]]]
下面的双参数条件用于比较版本号:
if (<字符串 | 变量> VERSION_LESS <字符串 | 变量>) # 小于
if (<字符串 | 变量> VERSION_GREATER <字符串 | 变量>) # 大于
if (<字符串 | 变量> VERSION_EQUAL <字符串 | 变量>) # 等于
if (<字符串 | 变量> VERSION_LESS_EQUAL <字符串 | 变量>) # 小于或等于
if (<字符串 | 变量> VERSION_GREATER_EQUAL <字符串 | 变量>) # 大于或等于
列表元素判断
下面这个条件语法用于判断列表中的元素是否存在。当第二个参数<列表变量>的元素中存在第一个参数的取值时,条件为真,否则为假。
if (<字符串 | 变量> IN_LIST <列表变量>)
命令定义
- 宏定义
形式为:
macro(<宏名> [<参数1>…])
<命令>…
endmacro()
宏所包含的命令序列仅在宏被调用时执行,且执行时不会产生额外的作用域。
在 CMake 中,命令的名称不区分大小写,宏作为一种命令,其名称也不例外。
- 函数定义
函数定义的形式如下:
function(<函数名> [<参数1>…])
<命令>…
endfunction()
函数会产生一个新的作用域,仅在函数内部生效。因此函数内部直接使用 set() 命令定义的变量是不能被外部所访问的。为了实现外部可访问的目的,必须为 set() 命令指定 PARENT_SCOPE 参数,是的变量定义到外部作用域。
在本示例中定义了一个函数 my_func 有两个形参 x1 和 x2. 函数内部的 loc_result 是局部变量,global_result 因为添加 PARENT_SCOPE 选项,所以它的作用域是调用者可见的。
截图中(1)处打印 loc_result 是一个空字符串,因为主代码中没有定义,在 my_func() 定义的局部变量 loc_result 不可见,所以是空字符串。而 global_result 是以对调用者可见的,所以结果不为空。
截图中(2)处因为现在主代码中设置了 loc_result 和 global_result,所以 loc_result 值不为空,只为 14 行设定的字符串。而 global_result 被 my_func() 修改了,所以15行设定的内容改变了。
本文来自论坛,点击查看完整帖子内容。