0.概述
本文主要介绍有关CMake的基本概念,包括源文件,注释,参数,定义并引用变量,控制结构与条件控制,以及如何自定义自定义命令等基础内容。并不涉及项目构建以及常用命令。
1.cmake程序
根据文件名字,可分为以下两种类型:
- 名字为CMakeLists.txt的文件
- 扩展名为.cmake的程序
cmake处理项目的入口是项目顶层目录下的CMakeLists.txt文件。在CMakeLists.txt中,可以通过add_subdirectory命令将一些子目录追加到构建目录中,但是每个目录中都必须有一个CMakeLists.txt来作为入口,并依据不同目录下CMakeLists.txt文件来构建目录结构。
以.cmake结尾的文件是为脚本(script)文件与模块(module)文件。CMake命令行工具指定参数-P可以执行脚本类型的cmake程序。而通过include等命令则可以引用CMake模块程序。
CMake提供了很多预制模块供给用户使用,多数与环境监测、搜索使用第三方库有关。
关键词:目录(CMakeLists.txt) 脚本(<script>.cmake) 模块(<module.cmake>)
2.注释
-
单行注释
-
多行注释(括号注释)
单行注释:#
多行注释:#[=[……]=];
可以有若干等号
。
3.命令参数
- 引号参数(quoted argument)
- 非引号参数(unquoted argument)
- 括号参数(bracket argument)
引号参数"",被引号包裹在内,且CMake规定必须为双引号。其参数或作为一个整体传递给命令,包括换行等特殊符号。'\'可以避免参数内容出现换行。
非引号参数,在被传递前,会被当作列表来进行处理,列表中的每一个元素都会作为一个单独的参数传递给命令。参数之间使用逗号或者分号进行分割。
括号参数[[]],类似于字符串,不处理文本中的任何特殊字符与变量引用。当第一个字符为换行符时会省略。
4.变量
- 普通变量
- 缓存变量
- 环境变量
缓存变量:会被持久化到缓存文件CMakeCache.txt中,具有全局作用域。
环境变量:即操作系统的环境变量,对于CMake进程而言具有全局作用域。
变量的作用域:变量的作用域分为函数作用域
与目录作用域
,函数作用域作用于函数内部,每一个目录层级,都有一个作用域,但目录作用域有父子级别的区别。子目录可以访问父目录的变量但是无法修改。
预定义变量:见官方文档。
5.定义变量
定义普通变量:
set(<变量> <值> …… [PARENT_SCOPE])
PARENT_SCOPE指定将变量定义到父级目录作用域中。
定义缓存变量:
set(<变量> <值> …… CACHE <变量类型> <变量描述> [FORCE])
<变量类型>具有五种取值,布尔型(BOOL)
、文件路径类型(FILEPATH)
、目录路径类型(PATH)
、文本型(STRING)
、内部使用(INTERNAL)
。
<变量描述>,即一段用于描述变量的文字。
FORCE
可选参数用于强制刷新缓存变量的值,因为一般缓存变量赋值之后的set默认不修改变量的值。
布尔类型的缓存变量还可以使用option定义(较为常用),语法格式:
option(<变量> <变量描述> [<ON|OFF>])
注意:当同名的普通变量与缓存变量同时存在时,普通变量引用语法优先匹配普通变量。
定义环境变量:
set(ENV{<环境变量>} [<值>])
注意:CMake中的set命令仅仅对当前CMake进程有效。
6.引用变量
- 引用普通变量
- 引用环境变量
- 引用缓存变量
${<变量>}
$ENV{<变量>}
$CACHE{<变量>}
7.列表
CMake中的列表即为用分号分割的字符串,利用set即可定义列表变量。
set(a "a;b;c")
set(b a;b;c)
set(c a b c)
#以上效果是一样的
message(${a} \n ${b} \n ${c})
#区别引号参数的分号
message("a;b;c")
定义变量时的分号:列表中的每一个元素都是用分号隔开的,但是并不是所有的分号都用于分割元素。
- 当分号前具有一个转义字符时,该分号不会用做分隔符。
- 若一个分号前存在未闭合的方括号,则该分号不作为分隔符。如
[;
、[;]
、[[];
等。
8.控制结构
- if条件分支
- while判断循环
- foreach遍历循环(简单列表遍历、区间遍历、高级列表遍历、打包遍历)
- 跳出与跳过循环:break与continue
if条件分支:
if(<条件>)
<命令> ...
elseif(<条件>)
<命令> ...
else()
<命令> ...
endif()
while判断循环:
while(<条件>)
<命令> ...
endwhile()
foreach简单遍历循环:
foreach(<循环变量> <循环变量列表>)
<命令> ...
endforeach()
# eg
set(list X;Y;Z)
foreach(x ${list})
message("x: ${list}")
endforeach()
foreach区间遍历:
foreach(<循环变量> RANGE [<起始值>] <终止值> [<步进>])
# eg
foreach(x RANGE 2 99 5)
message("x: ${x}")
endforech()
foreach高级列表遍历:
foreach(<循环变量> IN [LISTS [<列表变量名的列表>]] [ITEMS [<循环项的列表>]])
LISTS后跟着的是列表名,而ITEMS后跟着的是元素。
set(a A;B)
set(b C D)
set(c "E F")
set(d G;H I)
set(e "")
foreach(x IN LISTS a b c d e ITEMS a b c d e)
message("x: ${x}")
endforeach()
输出结果:
x: A
x: B
x: C
x: D
x: E F
x: G
x: H
x: I
x: a
x: b
x: c
x: d
x: e
foreach打包遍历:
foreach(<循环变量>... IN ZIP_LISTS <列表变量名列表>)
其执行规律如下:
- 如果只指定了一个<循环变量>,则<循环变量>_N可以依次取到各个列表此次循环取到的数。
- 如果指定了多个<循环变量>,则其数目应该与列表数目相同。
- 遍历循环次数以列表最长的<变量名列表>为准,元素不存在即为空字符串。
break与continue:
break()
continue()
break会使得循环立即终止,而continue则是跳过本次循环。
9.条件语法
9.1 常量、变量和字符串条件
常量条件仅由一个常量组成,常量分为真常量值与假常量值。常见形式如下:
常量类型 | 常量值 | 条件结果 |
---|---|---|
真值常量 | 1、ON、YES、TRUE、Y、非零数值(不区分大小写) | 真 |
假值常量 | 0、OFF、NO、FALSE、N、IGNORE、空字符串、NOTFOUND或以_NOTFOUND结尾的字符串 | 假 |
如果条件中的字符串是一个变量名,且变量的值不是一个假值常量,则为真,否则为假。
9.2 逻辑运算
包括三种逻辑运算:与(AND)、或(OR)、非(NOT)
,参与运算的是符合条件语法的参数。
9.3 单参数条件
单参数条件是根据单个参数进行判断,一般为存在性判断和类型判断。
条件语法 | 条件判断类型 | 描述 |
---|---|---|
if(COMMAND<命令名称>) | 命令判断 | 当命令名称是一个可被调用的命令、宏或函数时,条件为真,否则为假。 |
if(POLICY<策略名称> | 策略判断 | 当<策略名称>指代一个已定义的策略时,条件为真,否则为假。 |
if(TARGET<目标名称>) | 目标判断 | 当<目标名称>指代一个在任意目录用add_executable、add_library、add_custom_target命令创建目标时,条件为真,否则为假。 |
if(TEST<测试名称>) | 测试判断 | 当<测试名称>指代一个用add_test命令创建的测试时,条件为真,否则为假。 |
if(DEFIEND<变量名称>) | 变量定义判断 | 当<变量名称>指代一个变量时,条件为真,否则为假。 |
if(CACHE<变量名称>) | 缓存变量定义判断 | 当<变量名称>指代一个缓存变量时,条件为真,否则为假。 |
if(ENV<变量名称> | 环境变量判断 | 当<变量名称>指代一个环境变量时,条件为真,否则为假。 |
if(EXISTS<文件或目录路径>) | 文件或目录存在判断 | 当指定的<文件或目录路径>存在时,条件为真,否则为假。该路径要求为绝对路径,此外当路径指向一个符号链接时,仅当链接指向的文件存在才为真。 |
if(IS_DIRECTORY<目录路径>) | 目录判断 | 当指定的<目录路径>确实存在且确为一个目录,条件为真,否则为假。 |
if(IS_SYMLINK<文件路径>) | 符号链接判断 | 当指定的<文件路径>确实存在且是一个符号链接时,条件为真,否则为假。 |
if(IS_ABSOLUTE<路径>) | 绝对路径判断 | 当指定的<路径>为一个绝对路径时,条件为真,否则为假。 |
9.4 双参数条件
双参数条件通过两个参数的取值来决定条件是否为真,一般用于比较关系的判断。
- 数值比较
- 字符串匹配
- 版本号比较
- 列表元素判断
数值比较:
# 五种关系
LESS # 小于
GREATER # 大于
EQUAL # 等于
LESS_EQUAL # 小于或等于
GREATER_EQUAL # 大于或等于
字符串比较:
# 五种关系
STRLESS # 小于
STRGREATER # 大于
STREQUAL # 等于
STRLESS_EQUAL # 小于或等于
STRGREATER_EQUAL # 大于或等于
字符串匹配:
if(<字符串|变量> MATCHES <正则表达式>)
匹配成功为真,否则为假。
版本号比较:
# 五种关系
VERSION_LESS # 小于
VERSION_GREATER # 大于
VERSION_EQUAL # 等于
VERSION_LESS_EQUAL # 小于或等于
VERSION_GREATER_EQUAL # 大于或等于
# 版本号格式 每一部分都是整数,被省略的部分会被当作0来处理
主版本号.[.版本号[.补丁版本号[.修订版本号]]]
列表元素判断:
if(<字符串|变量> IN_LIST <列表变量>)
当列表中存在第一个参数时为真,否则为假。
9.5 括号与条件优先级
CMake中条件语法求值的优先级由高到低依次为:
- 括号
- 单参数条件
- 双参数条件
- 逻辑运算条件NOT
- 逻辑运算条件AND
- 逻辑运算条件OR
10.自定义命令
10.1 宏与函数
CMake中一切皆命令,宏与函数为两种自定义命令的方式。
- 宏定义
- 函数定义
- 宏和函数的区别
宏定义:
macro(<宏名> [<参数1>...])
<命令>...
endmacro()
# eg
macro(my_macro a b)
set(result "参数a:${a}, 参数b:${b}")
endmacro()
my_macro(x y)
message(${result})
宏就是把所定义的代码直接复制到它被调用的地方来运行,并不拥有作用域,而是与调用上下文共享作用域。宏名不区分大小写,但是习惯上使用全小写加下划线的命名法。
函数定义:
function(<函数名> [<参数1>...])
<命令>...
endfunction()
宏与函数的区别:
- 执行上下文和作用域:函数拥有独立的作用域和独立的执行上下文。
- 定义外部可见的变量:函数内部定义的变量外部并不可见,除非指定了PARENT_SCOPE参数,且CMake命令return只用于结束上下文,并没有返回值这一功能。
- 预定义变量:函数体中可以访问CMake预定义的变量,获取关于当前执行中的函数的一些信息。
- 参数访问:在函数中,包括形式参数、ARGC、ARGV、ARGN都是真正的CMake变量,定义在当前函数的作用域内。但是在宏中,CMake展开宏时,会对宏的命令序列进行预处理,对引用这些符号的地方直接进行文本替换。故在宏中不能将这些参数直接用于条件或通过嵌套引用来访问这些符号。
10.2 参数
- 参数的访问(引用形式参数、列表或索引访问参数)
- 参数的设计与解析
参数的访问:直接使用变量引用语法即可。
列表或索引访问参数:
- ${ARGC}表示参数的个数
- ${ARGV}表示完整的实际参数列表,其元素为传递的每一个参数
- ${ARGN}表示无对应形式参数的实际参数列表
- ${ARGV0}、${ARGV1}、${ARGV2}依次表示第1个、第2个、第3个实际参数的值
function(my_function p)
message("ARGC: ${ARGC}")
message("ARGV: ${ARGV}")
message("ARGN: ${ARGN}")
endfunction()
set(v x y z)
my_function(${v})
运行结果:
ARGC: 3
ARGV: x;y;z
ARGN: y;z
参数的设计与解析:
Cmake的命令参数通常由两部组成:一部分是用户提供的参数值,另一部分是一些关键字。关键字可以分为以下三种类型:
- 开关选项(option):调用者可以指定该参数来启用某个选项。
- 单值参数关键字(one-value keyword):一个关键字对应一个参数值。
- 多值参数关键字(mutil-value keyword):相当于接收一个列表参数。
cmake_parse_arguments:
用于解析符合上述规范的命令。该命令有两种形式:
- 在函数与宏中均可以使用的通用形式,但它无法解析一些包含特殊符号的单值参数。
- 不存在上述缺陷,但只支持在函数中使用。
通用形式:
cmake_parse_arguments(
<结果变量前缀名>
<开关选项关键字列表> <单值参数关键字列表> <多值参数关键字列表>
<将被解析的参数>...
)
通过指定的列表,解析所传递的参数,并将每一种关键字对应的参数值存放到一些结果变量中,这些结果变量以<结果变量名前缀>加"_"为前缀,后面则是对应的关键字的名称。
function(abc_f)
cmake_parse_arguments(abc "ENABLE" "VALUE" "" ${ARGN})
message("abc_ENABLE: ${abc_ENABLE}")
message("abc_ENABLE: ${abc_VALUE}")
endfunction()
abc_f(VALUE a ENABLE)
#执行结果
abc_ENABLE: TRUE
abc_ENABLE: a
三个<关键字列表>参数为三个列表类型的参数,如果多个参数属于同一类型,则应当使用分号隔开它们,并通过引号参数或者是括号参数来指定它们。
function(abc_f)
cmake_parse_arguments(abc "A0;A1" "B0;B1" [=[C0;C1]=] ${ARGN})
#[[
执行结果:
A0: TRUE A1: TRUE
B0: a B1: b
C1: x;y C1: c;d
若不用括号参数与引号参数加以指定,
cmake_parse_arguments(abc A0 A1 B0 B1 C0 C1 ${ARGN})
其执行结果如下:
A0: TRUE A1:
B0: a;B1;b;C0;x;y;C1;c;d B1:
C0: C1:
]]
message("A0: ${abc_A0} \t A1: ${abc_A1}")
message("B0: ${abc_B0} \t B1: ${abc_B1}")
message("C1: ${abc_C0} \t C1: ${abc_C1}")
endfunction()
abc_f(A0 A1 B0 a B1 b C0 x y C1 c d)
出现上述现象的原因是,若不指明分界,cmake_parse_arguments无法知晓三种关键字列表的分界,则将第一个关键字A0作为选项关键字的唯一元素,而A1则作为单值关键字的唯一元素,B0作为多值关键字,后续全部赋值给abc_B0。
争对函数的优化形式:
cmake_parse_arguments(
PARSE_ARGV
<N>
<结果变量前缀名>
<开关选项关键字列表>
<单值参数关键字列表>
<多值参数关键字列表>
)
该命令形式只能在函数中使用,不支持在宏中使用。<N>是一个从0开始的整数,表示从第几个实际参数开始解析。
另外,cmake_parse_arguments除了将解析的参数存放到对应的关键字的结果变量中,还会将一些未能解析的参数与未提供值的关键字等信息存入两个特殊的结果变量中。
- <结果变量前缀名>_UNPARSED_ARGUMENTS存放所有未能解析到某一关键字中的实际参数值。
- <结果变量前缀名>_KEYWORDS_MISSING_VALUES存放所有未提供实际值的关键字的名称。
实例:复制文件
function(my_copy_func)
message("ARGN: ${ARGN}")
set(options OVERWRITE MOVE)
set(oneValueArgs DESTINATION)
set(mutilValueArgs PATHS)
cmake_parse_arguments(
PARSE_ARGV 0
my
"${options}" "${oneValueArgs}" "${mutilValueArgs}"
)
message("OVERWRITE: ${my_OVERWRITE}")
message("MOVE: ${my_MOVE}")
message("DESTINATION: ${my_DESTINATION}")
message("PATHS: ${my_PATHS}")
endfunction()
my_copy_func(DESTINATION ".." PATHS "1.txt" "2.txt")
my_copy_func(MOVE DESTINATION "../.." PATHS "3.txt" "4.txt" OVERWRITE)
my_copy_func(DESTINATION "../floder;name" PATHS "1.txt" "2.txt")
# 若此处使用的是通用写法,则无法识别;name,因为其会被识别为一个列表,其中../floder赋值给my_DESTINATION,而name则被忽略