CMake 语言 15 分钟入门教程

cmake 是一个跨平台的编译安装工具,可以用简单的语句来描述所有平台的编译安装过程。

本文介绍 cmake 的基础语法。

第一个例子

使用任意的文本编辑器,输入:

1
message("Hello world!") 

然后保存为 hello.txt 文本文件,执行:

1
cmake -P hello.txt

输出:

Hello world!

所有变量都是字符串

在 cmake 中,所有变量都是字符串。可以使用 ${} 来引用一个变量。例如,修改 hello.txt

1
message("Hello ${NAME}!") 

然后在执行 cmake 命令时对变量 NAME 进行定义:

1
cmake -DNAME=Leo -P hello.txt

输出:

Hello Leo!

如果直接执行:

1
cmake -P hello.txt

输出:

Hello !

也可以直接在脚本中定义变量:

1
2
set(URL "leehao.me")
message("Please visit ${URL}!")

执行 cmake 命令后,输出:

Please visit leehao.me!

使用前缀模拟数据结构

cmake 并没有类,但是我们可以通过定义一组变量,该组变量以相同的前缀来模拟类,并使用嵌套的 ${} 来引用这些变量。例如:

1
2
3
4
set(LEO_NAME "Leo Lee")
set(LEO_ADDRESS "Guangzhou City")
set(PERSON "LEO")
message("${${PERSON}_NAME} lives at ${${PERSON}_ADDRESS}.")

输出:

Leo Lee lives at Guangzhou City.

每个语句都是命令

在 cmake 中,每个语句都是一个命令,这个命令使用字符串参数列表(参数可采用空格分隔),并且没有返回值。

例如,可以使用 math 来执行算术运算。math 接受三个参数,第一个参数是 EXPR,第二个参数是变量名称,第三个参数是需要计算的算术表达式,其运算结果会被赋予第二个变量。

1
2
3
4
math(EXPR MY_SUM "1 + 1")                   
message("The sum is ${MY_SUM}.")
math(EXPR DOUBLE_SUM "${MY_SUM} * 2")
message("Double that is ${DOUBLE_SUM}.")

输出:

The sum is 2.
Double that is 4.

流程控制命令

流程控制命令包括 if/endifwhile/endwhile

例如,判断当前环境是否为 win32:

1
2
3
if(WIN32)
message("You're running CMake on Windows.")
endif()

例如,使用 while/endwhile 循环打印出所有小于 20 的斐波那契数列:

1
2
3
4
5
6
7
8
set(A "1")
set(B "1")
while(${A} LESS "20")
message("${A}") # 打印 A
math(EXPR T "${A} + ${B}") # 计算 A + B 的值,并存储在变量 T
set(A "${B}") # 设置变量 A 的值为 B
set(B "${T}") # 设置变量 B 的值为 T
endwhile()

在 cmake 中,变量与数字进行比较的语法与其他语言不同,例如上面的小于比较,就使用了 LESS,其他比较条件的使用,可以参考官方说明:链接

输出:

1
1
2
3
5
8
13

列表:分号分隔的字符串

直接以例子说明。以下例子中,将三个参数传递给 math

1
2
3
set(ARGS "EXPR;T;1 + 1")
math(${ARGS}) # 等价于 math(EXPR T "1 + 1")
message(${T})

如果引用变量 ${} 外部添加双引号,则 cmake 会将整个字符串作为一个参数,并保留分号:

1
2
set(ARGS "EXPR;T;1 + 1")
message("${ARGS}")

输出:

EXPR;T;1 + 1

如果 ${} 不带双引号,例如:

1
2
set(ARGS "EXPR;T;1 + 1")
message(${ARGS})

则输出:

EXPRT1 + 1

如果有两个以上的参数传递给 set 命令,则它们会被分号连接,然后传递给指定的变量:

1
2
set(MY_LIST Please visit leehao.me)
message("${MY_LIST}")

输出:

Please;visit;leehao.me

可以使用 list 命令处理列表:

1
2
3
set(MY_LIST Please visit url leehao.me)
list(REMOVE_ITEM MY_LIST "url")
message("${MY_LIST}")

输出:

Please;visit;leehao.me

可以使用 foreach/endforeach 处理列表,迭代除第一个参数外的列表的所有项,并将每项赋值给第一个参数变量:

1
2
3
foreach(ARG Please visit url leehao.me)
message("${ARG}")
endforeach()

输出:

Please
visit
url
leehao.me

定义函数

在 cmake 中,可以使用 function/endfunction 来定义一个函数,例如下面的函数将参数值 * 2 后输出:

1
2
3
4
5
6
function(doubleIt VALUE)
math(EXPR RESULT "${VALUE} * 2")
message("${RESULT}")
endfunction()

doubleIt("4")

输出:

8

函数中定义的变量不会影响调用方的作用域,如果需要返回值,可以将变量传递给函数,然后使用 set 命令,并指定 PARENT_SCOPE 参数:

1
2
3
4
5
6
7
function(doubleIt VARNAME VALUE)
math(EXPR RESULT "${VALUE} * 2")
set(${VARNAME} "${RESULT}" PARENT_SCOPE) # 设置返回值
endfunction()

doubleIt(RESULT "4") # RESULT 变量存储函数的返回值
message("${RESULT}") # 输出:8

cmake 中,使用 macro/endmacro 定义宏。与函数不同,宏内改变变量的值会影响调用方的作用域:

1
2
3
4
5
6
macro(doubleIt VARNAME VALUE)
math(EXPR ${VARNAME} "${VALUE} * 2")
endmacro()

doubleIt(RESULT "4")
message("${RESULT}")

设置和获取属性

cmake 可以使用 add_executableadd_libraryadd_custom_target 等命令来定义目标(target)。与变量不同,目标在每个作用域都可见,且可以使用 get_propertyset_property 获取或设置其属性。

与上面例子不同,直接使用 cmake -P 执行脚本会导致报错:

Command add_executable() is not scriptable

故这里改用 cmake 来编译单个源文件的方式来展示目标属性的获取。

源文件 leehao.cpp 如下:

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

CMakeLists.txt 如下,获取目标 testSOURCES 属性,并赋值给 MYAPP_SOURCES

1
2
3
4
5
6
7
cmake_minimum_required (VERSION 2.8)

add_executable(test leehao.cpp)

get_property(MYAPP_SOURCES TARGET test PROPERTY SOURCES)

message("${MYAPP_SOURCES}")

执行 cmake . ,输出:

– The C compiler identification is GNU 4.8.5
– The CXX compiler identification is GNU 4.8.5
– Check for working C compiler: /usr/bin/cc
– Check for working C compiler: /usr/bin/cc – works
– Detecting C compiler ABI info
– Detecting C compiler ABI info - done
– Check for working CXX compiler: /usr/bin/c++
– Check for working CXX compiler: /usr/bin/c++ – works
– Detecting CXX compiler ABI info
– Detecting CXX compiler ABI info - done
leehao.cpp
– Configuring done
– Generating done
– Build files have been written to: /home/lihao/code/cpp/cmake

可以看到输出 leehao.cpp,也就是说正确获取了目标 testSOURCES 属性。

上面的命令会输出 Makefile 文件,接下来还可以执行 make 命令,以进一步编译生成可执行文件 test

参考资料