4. CMake语法基础:注释、变量、列表、命令调用、条件判断、循环
各位同学,欢迎来到第四章。说实话,很多嵌入式工程师第一次接触CMake,都会被它的语法吓到——跟C语言长得像,但又不完全一样。我当年刚转过来的时候,也花了点时间适应。不过你放心,CMake的语法其实比C语言简单多了,核心就那么几个东西。
这一章,咱们就把CMake的语法地基打牢。你想想看,盖房子最重要的是什么?地基。CMake项目也一样,语法基础不牢,后面写复杂脚本的时候就会各种踩坑。来,咱们一个一个过。
4.1 注释:给代码加点说明
CMake的注释分两种:单行注释和块注释。单行注释用 #,跟shell脚本一样。块注释用 #[[ ]],这是CMake 3.0之后才有的特性。
# 这是单行注释
set(MY_VAR "hello") # 行尾也可以加注释
#[[
这是块注释
可以跨多行
适合临时屏蔽一大段代码
]]
我个人习惯,在CMakeLists.txt里多用单行注释。块注释用得少,除非是调试时临时屏蔽一大段逻辑。嗯,这里要注意:块注释不能嵌套,如果你在注释块里又写了一个 #[[ ]],CMake会报错。
set(BAUD_RATE 115200) # 串口波特率。半年后你回来看代码,会感谢自己的。
4.2 变量:CMake里的"容器"
CMake里的变量,说白了就是一个字符串容器。你用 set() 命令来赋值,用 ${} 来取值。就这么简单。
set(MY_VAR "hello world")
message("${MY_VAR}") # 输出:hello world
变量名是大小写敏感的。MY_VAR 和 my_var 是两个不同的变量。我在项目中遇到过有人因为大小写写错,排查了半天找不到问题——最后发现是变量名拼错了。
变量还可以用 unset() 来删除:
unset(MY_VAR) # 删除变量
另外,CMake里有一些内置变量,比如 CMAKE_CURRENT_SOURCE_DIR(当前源码目录)、CMAKE_BINARY_DIR(构建目录)。这些变量你不用自己定义,直接用就行。
${VAR} 会展开为空字符串,不会报错。这一点跟C语言不一样,要小心。
4.3 列表:其实就是分号分隔的字符串
CMake里的列表,本质上就是一个字符串,只不过用分号 ; 来分隔各个元素。你想想看,是不是很简单?
set(MY_LIST "a" "b" "c") # 等价于 set(MY_LIST "a;b;c")
message("${MY_LIST}") # 输出:a;b;c
列表的常用操作包括:
list(APPEND)— 追加元素list(LENGTH)— 获取长度list(GET)— 获取指定索引的元素list(REMOVE_ITEM)— 删除指定元素
set(SOURCES "main.c" "uart.c" "spi.c")
list(APPEND SOURCES "gpio.c") # 现在有4个元素
list(LENGTH SOURCES LEN) # LEN = 4
list(GET SOURCES 0 FIRST_FILE) # FIRST_FILE = "main.c"
我在嵌入式项目里,最常用列表来管理源文件。比如把某个模块的所有.c文件放在一个列表里,然后统一加到 add_executable() 里。这样代码干净多了。
list(GET SOURCES 4 ...) 而列表只有4个元素(索引0-3),CMake会报错。我曾经踩过这个坑,索引越界了。
4.4 命令调用:函数调用的基本姿势
CMake里的命令调用,语法是:命令名(参数1 参数2 ...)。参数之间用空格分隔,如果参数本身包含空格,就用双引号包起来。
message("Hello, CMake!") # 输出一条消息
set(MY_VAR "value with spaces") # 带空格的参数
add_executable(my_app main.c) # 添加可执行文件
命令名不区分大小写。你可以写 MESSAGE(),也可以写 message(),效果一样。但我个人建议统一用小写,看着舒服,也符合大多数人的习惯。
命令的参数可以是:
- 普通字符串:
set(VAR hello) - 带引号的字符串:
set(VAR "hello world") - 变量引用:
set(VAR ${OTHER_VAR}) - 列表:
set(VAR a b c)
说白了,CMake的命令调用就是"命令名 + 参数列表"。没有C语言那种括号嵌套的复杂语法,简单直接。
4.5 条件判断:让CMake"动脑子"
条件判断用 if()、elseif()、else()、endif()。语法跟C语言有点像,但注意:endif() 后面要跟条件名(可选),我建议加上,方便阅读。
set(USE_FREERTOS ON)
if(USE_FREERTOS)
message("FreeRTOS is enabled")
add_subdirectory(freertos)
else()
message("FreeRTOS is disabled")
endif()
条件判断支持的操作符:
| 操作符 | 说明 | 示例 |
|---|---|---|
STREQUAL | 字符串相等 | if(VAR STREQUAL "hello") |
EQUAL | 数值相等 | if(COUNT EQUAL 10) |
LESS | 数值小于 | if(COUNT LESS 5) |
DEFINED | 变量是否已定义 | if(DEFINED VAR) |
NOT | 逻辑非 | if(NOT USE_FREERTOS) |
AND / OR | 逻辑与/或 | if(A AND B) |
我在项目中遇到过最典型的场景:根据芯片型号选择不同的编译选项。比如STM32F4和STM32F7,启动文件不一样,链接脚本也不一样。用条件判断,一个CMakeLists.txt就能搞定多个芯片。
if() 里直接写 if(VAR) 来判断变量是否非空。但CMake里,如果VAR的值是 OFF、NO、FALSE、0、空字符串,都会被当作假。所以最好用 if(DEFINED VAR) 来判断变量是否存在,用 if(VAR STREQUAL "xxx") 来判断具体值。
4.6 循环:foreach 和 while
CMake的循环主要有两种:foreach() 和 while()。foreach用得最多,while偶尔用。
4.6.1 foreach 循环
遍历列表里的每个元素:
set(SOURCES "main.c" "uart.c" "spi.c")
foreach(FILE ${SOURCES})
message("Processing ${FILE}")
endforeach()
还可以用 RANGE 来生成数字序列:
foreach(I RANGE 1 5)
message("I = ${I}") # 输出 1 2 3 4 5
endforeach()
或者指定步长:
foreach(I RANGE 0 10 2)
message("I = ${I}") # 输出 0 2 4 6 8 10
endforeach()
4.6.2 while 循环
while循环用得少,但有时候也管用:
set(COUNT 0)
while(COUNT LESS 5)
message("COUNT = ${COUNT}")
math(EXPR COUNT "${COUNT} + 1")
endwhile()
注意:CMake没有 break 和 continue(至少在旧版本里没有)。如果你需要提前退出循环,得用 if() 包一下逻辑。嗯,这一点确实不太方便,但实际项目中很少需要复杂的循环控制。
set_source_files_properties() 多累啊。一个foreach循环就搞定了。
4.7 本章知识体系
下面这张图,把CMake语法的核心知识点串起来了。你可以把它当作一个快速参考图,写CMakeLists.txt的时候瞄一眼。
这张图把注释、变量、列表、命令调用、条件判断、循环这六个核心知识点串在了一起。你写CMakeLists.txt的时候,脑子里过一遍这张图,基本就不会跑偏。
好了,这一章的内容就到这里。语法基础打牢了,后面咱们就可以开始写真正的嵌入式构建脚本了。记住:CMake语法不复杂,复杂的是你用它来解决什么问题。多写、多练、多踩坑,慢慢就熟练了。
公众号:蓝海资料掘金营,微信deep3321