4. CMake语法基础:注释、命令、变量、列表、控制结构
好,咱们正式开始啃CMake的语法。说实话,很多初学者一上来就被CMakeLists.txt里那些奇怪的写法搞懵了。我当年也是,看着一堆set()、if()、foreach(),心想这玩意儿到底是脚本语言还是配置文件?
答案是:它既是脚本,也是配置。CMake有自己的语法规则,虽然不像C++那么复杂,但坑也不少。今天我就带你把这些基础语法捋一遍。
4.1 注释:给代码加点说明
注释这东西,写的时候觉得没必要,三个月后回来看——嗯,真香。CMake支持两种注释方式:
# 这是单行注释,用井号开头
# 我习惯在关键逻辑前加一行注释
# 多行注释?CMake没有原生支持
# 但你可以用连续的单行注释凑合
# 或者用 IF(FALSE) 块来临时屏蔽代码
IF(FALSE)
message("这行不会被执行")
ENDIF()
小技巧: 我个人习惯在变量定义和函数调用前加注释,说明这个变量是干什么的。比如 # 这个变量控制是否启用日志。别嫌麻烦,项目大了你就知道好处了。
4.2 命令:CMake的基本操作单元
CMake里的命令,说白了就是函数调用。写法是:命令名(参数1 参数2 ...)。命令名不区分大小写,但我建议全小写,看着清爽。
常用的命令就那么几个:
message()— 打印信息,调试利器set()— 设置变量add_executable()— 添加可执行文件target_link_libraries()— 链接库
# 打印一条信息
message("Hello from CMake!")
# 设置变量
set(MY_VAR "hello")
# 注意:参数之间用空格分隔,不是逗号
# 如果参数本身包含空格,要用引号包起来
set(MY_PATH "C:/Program Files/MyApp")
注意: 命令的参数之间不要加逗号!我见过有人写 set(MY_VAR, "hello"),结果CMake把逗号也当成参数的一部分了。嗯,这种bug找起来挺头疼的。
4.3 变量:存东西的盒子
CMake的变量,其实全是字符串。你没看错,全是字符串。数字、路径、开关,底层都是字符串。定义变量用set(),读取变量用${}。
# 定义变量
set(NAME "CMake入门")
set(VERSION 1.0) # 1.0 其实也是字符串
# 读取变量
message("项目名称: ${NAME}")
message("版本号: ${VERSION}")
# 变量嵌套?可以,但容易晕
set(FOO "BAR")
set(BAR "hello")
message("${${FOO}}") # 输出 hello
变量作用域这块,我踩过坑。CMake的变量作用域是函数/宏级别的,子目录里改变量不会影响父目录,除非你用set(... PARENT_SCOPE)。
核心要点: 变量名用大写还是小写?社区习惯是大写,但这不是强制要求。我个人建议:全局变量用大写,局部变量用小写,这样一眼就能分清。
4.4 列表:其实就是分号分隔的字符串
CMake没有真正的数组类型,列表就是用分号分隔的字符串。你想想看,这设计是不是挺朴素的?
# 定义一个列表
set(SOURCES main.cpp utils.cpp network.cpp)
# 等价于
set(SOURCES "main.cpp;utils.cpp;network.cpp")
# 访问列表元素
message("第一个源文件: ${SOURCES}") # 输出所有元素
# 获取列表长度
list(LENGTH SOURCES len)
message("源文件数量: ${len}")
# 遍历列表
foreach(src ${SOURCES})
message("处理文件: ${src}")
endforeach()
列表操作最常用的就是list()命令,支持追加、删除、查找、排序等操作。我在项目中经常用list(APPEND)来动态添加源文件。
# 动态追加
list(APPEND SOURCES new_file.cpp)
# 查找元素
list(FIND SOURCES "main.cpp" index)
if(index GREATER -1)
message("main.cpp 在位置 ${index}")
endif()
4.5 控制结构:让CMake有逻辑
CMake支持if/else、foreach、while这些控制结构。语法和C++有点像,但结尾要加ENDIF()、ENDFOREACH()这些。
4.5.1 if/else:条件判断
set(OPTION_ENABLE_LOGGING ON)
if(OPTION_ENABLE_LOGGING)
message("日志已启用")
add_definitions(-DENABLE_LOGGING)
else()
message("日志未启用")
endif()
# 判断变量是否定义
if(DEFINED MY_VAR)
message("MY_VAR 已定义")
endif()
# 判断字符串是否相等
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
message("这是Debug构建")
endif()
避坑指南: 我曾经在if()里直接写变量名忘了加${},结果CMake把它当成字符串字面量比较了。记住:要比较变量的值,必须用${}展开。
4.5.2 foreach:循环遍历
# 遍历列表
set(LANGUAGES C CXX Python)
foreach(lang ${LANGUAGES})
message("支持的语言: ${lang}")
endforeach()
# 遍历数字范围
foreach(i RANGE 1 5)
message("计数: ${i}")
endforeach()
# 带步长的遍历
foreach(i RANGE 0 10 2) # 0, 2, 4, 6, 8, 10
message("偶数: ${i}")
endforeach()
4.5.3 while:条件循环
set(COUNTER 0)
while(COUNTER LESS 5)
message("循环次数: ${COUNTER}")
math(EXPR COUNTER "${COUNTER} + 1")
endwhile()
while用得不多,因为foreach基本能覆盖大部分场景。但如果你需要根据条件动态终止循环,while就派上用场了。
知识体系总览
下面这张图把CMake语法的核心脉络梳理了一下,你可以对照着看:
这张图把CMake语法的五个核心模块串起来了。注释是给代码做标记,命令是执行动作,变量和列表是存数据,控制结构是让代码有逻辑分支。说白了,CMake语法就是这五样东西的组合。
我的建议: 刚开始学的时候,别急着背语法。先写一个简单的CMakeLists.txt,把set()、message()、if()、foreach()这四个命令玩熟。其他的遇到再查,完全来得及。
嗯,CMake语法基础就这些。看起来东西不多,但组合起来能写出很复杂的构建逻辑。下一节我们会把这些语法用在实际的CMakeLists.txt里,到时候你就知道它们怎么配合了。
公众号:蓝海资料掘金营,微信deep3321