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/elseforeachwhile这些控制结构。语法和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语法基础 注释 # 命令调用 变量 ${} 列表 list() 控制结构 if / elseif / else foreach 循环 while 循环 所有变量本质都是字符串,列表用分号分隔

这张图把CMake语法的五个核心模块串起来了。注释是给代码做标记,命令是执行动作,变量和列表是存数据,控制结构是让代码有逻辑分支。说白了,CMake语法就是这五样东西的组合。

我的建议: 刚开始学的时候,别急着背语法。先写一个简单的CMakeLists.txt,把set()message()if()foreach()这四个命令玩熟。其他的遇到再查,完全来得及。

嗯,CMake语法基础就这些。看起来东西不多,但组合起来能写出很复杂的构建逻辑。下一节我们会把这些语法用在实际的CMakeLists.txt里,到时候你就知道它们怎么配合了。


公众号:蓝海资料掘金营,微信deep3321