跨平台工程构建:CMake 的使用 (二)基本语法

组织结构

cmake 文件包括 CMakeLists.txt 和以 .cmake 为后缀的文件。
程序源文件最外层的 CMakeLists.txt 文件是 cmake 的入口文件,这个文件可以定义了整个工程的构建规范。它也可以使用 add_subdirectory() 命令来包含一个子文件夹,每个使用该命令添加的子文件夹中也需要有一个 CMakeLists.txt 文件。

变量

变量的包装与展开

在 cmake 中, 变量都是以字符串的形式存在的。使用 set( ) 包装变量, 使用 ${} 展开变量。${} 可以嵌套使用。 使用未包装的变量会导致空展开:

1
2
3
4
5
6
7
8
9
10
set(var_name_1 var1)
message("var_name is ${var_name_1}")

set(var_name_2 var2)
set(${var_name_2} var) #same as set(var2 var)
set(${${var_name_2}}_x foo) #same as set(var_x foo)
message("var2 is ${var2}")
message("var_name_2 is ${var_name_2}")
message("var_x is ${var_x}")
message("empty var is ${empty_var}")

执行结果如下:

1
2
3
4
5
var_name is var1
var2 is var
var_name_2 is var2
var_x is foo
empty var is

列表

在cmake 中,列表是使用 空格 `` 分隔的多个字符串 或 使用分号 ; 分隔的字符串。如下:

1
2
3
4
5
6
# Creates a list with members a, b, c, and d
set(my_list a b c d)
set(my_list "a;b;c;d")

# Creates a string "a b c d"
set(my_string "a b c d")

嵌套列表

1
2
3
4
5
6
7
8
9
10
set(list_of_lists a b c)
set(a 1 2 3)
set(b 4 5 6)
set(c 7 8 9)

foreach(list_name IN LISTS list_of_lists)
foreach(value IN LISTS ${list_name})
message(${value})
endforeach()
endforeach()

注意到 foreach 里双重展开的。第一次将列表展开为子列表的名称,第二次展开为子列表的内容。

流控制

cmake 和其它语言一样拥有控制功能,不同的是,cmake 中的控制流使用 命令 来实现

条件语句 if, elseif, else

和其它语言不同的是, cmake 中的控制流没有作用域。条件块中设置的变量在endif()之后仍然存在。

1
2
3
4
5
6
7
if(<condition>)
message("do stuff")
elseif(<condition>)
message("do other stuff")
else()
message("do other other stuff")
endif()

循环语句 foreach

常用的循环就是 foreach 了:

1
2
3
foreach(var ...)
message("do stuff")
endforeach()

foreach 的变量参数可以是一个列表字面量,或者列表变量,也可以是二者的混合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
foreach(var foo bar baz)
message(${var})
endforeach()
# prints:
# foo
# bar
# baz

set(my_list 1 2 3)
foreach(var ${my_list})
message(${var})
endforeach()
# prints:
# 1
# 2
# 3

foreach(var ${my_list} out_of_bounds)
message(${var})
endforeach()
# prints:
# 1
# 2
# 3
# out_of_bounds

当然,也有另一种写法,意义更明确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
foreach(var IN ITEMS foo bar baz)
message(${var})
endforeach()
# prints:
# foo
# bar
# baz

set(my_list 1 2 3)
foreach(var IN LISTS my_list)
message(${var})
endforeach()
# prints:
# 1
# 2
# 3

foreach(var IN LISTS my_list ITEMS out_of_bounds)
message(${var})
endforeach()
# prints:
# 1
# 2
# 3
# out_of_bounds

列表变量使用 LISTS 标识 ,列表字面量使用 ITEMS 标识。

同条件语言一样,循环语句没有作用域。

方法与宏

定义一个方法的语法如下:

1
2
3
4
5
function(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endfunction(<name>) # same as endfunction()

如同 C 语言的 varargs 一样,cmake 中可以使用 ${ARGN} 来表示多个参数。方法体中有单独的作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function(printmsg arg)
message(${arg})
foreach(var ${ARGN})
message(${var})
endforeach()
set(var_in_fun "hello")
endfunction(printmsg)

printmsg(a b c d)
message(${var_in_fun}) # takes an error: message called with incorrect number of arguments

# prints:
# a
# b
# c
# d
# message called with incorrect number of arguments

注意一点,cmake 中的方法是没有返回值的。如果需要方法传出值,常用的方法如下:

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.0)

function(fun arg1)
set(${arg1} abc PARENT_SCOPE)
endfunction(fun)

fun(val)
message(${val})

# prints:
# abc

宏看起来和方法很像。但如同在 C 语言中一样,宏只是对某一特定字符串的展开。另外,宏没有作用域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
macro(print_list my_list)
foreach(var IN LISTS my_list)
message(${var})
endforeach()
set(var_in_macro "world")
endmacro()

set(my_list a b c)
set(my_list_of_numbers 1 2 3)
print_list(${my_list_of_numbers})
print_list("X;Y")

set(my_list d e f)
print_list(${my_list})

message(${var_in_macro})

# prints:
# a
# b
# c
# a
# b
# c
# d
# e
# f
# world

注释

注释分为两种:单行注释块注释(括号注释)

单行注释# 开始,直到行尾结束

块注释(括号注释)#[[ ,以 ]] 结束 。块注释可以包含一行或多行注释:

1
2
3
4
5
6
7
8
# 这是一行注释
message("FirstArgument\n" #参数一 这是一行注释
"SecondArgument" #参数二 这是一行注释
)

#[[ 块注释可以是一行
也可以是多行]]
message("FirstArgument\n" #[[参数一 块注释]] "SecondArgument" #[[参数二 块注释]])