3. CMake基础语法:注释、变量、列表、命令调用、条件判断、循环

好,咱们直接进入正题。CMake 的语法其实不复杂,但有几个坑,我当年刚接触时没少往里跳。今天把这六个核心语法点捋一遍,你以后写 CMakeLists.txt 会顺手很多。

3.1 注释:给未来的自己留个话

注释这事儿,说白了就是写给下一个人看的——那个下一个人,往往就是三个月后的你自己。

CMake 支持两种注释:

  • 单行注释:用 # 开头,行内有效
  • 块注释:用 #[[#]] 包裹,可以跨行
# 这是单行注释,我习惯在复杂逻辑前加一行说明

#[[
  这是块注释
  适合临时屏蔽一大段代码
  或者写详细的文档说明
#]]
我的习惯:关键变量定义、条件分支、复杂表达式前,一定加一行注释。别偷懒,你省下的打字时间,会在调试时加倍还回来。

3.2 变量:CMake 里的“小盒子”

CMake 的变量,其实全是字符串。你没看错,数字、路径、开关,底层都是字符串。这一点和 C++ 很不一样,我刚转过来时很不适应。

set(MY_VAR "hello")          # 普通字符串
set(MY_NUM 42)               # 数字也是字符串
set(MY_PATH /usr/local/lib)  # 路径也是字符串

引用变量用 ${}

message("MY_VAR = ${MY_VAR}")  # 输出:MY_VAR = hello
我曾经踩过的坑:变量名拼写错误时,CMake 不会报错,只会展开成空字符串。比如你写了 ${MY_VAR} 但实际定义的是 ${MY_VARR},结果就是静默失败。排查起来非常痛苦。我的建议是:变量名尽量短且有规律,必要时用 message() 打印出来确认。

3.3 列表:其实就是分号分隔的字符串

CMake 的列表,底层仍然是字符串,只不过用分号 ; 分隔元素。你想想看,这设计是不是很“CMake”?

set(MY_LIST a b c)            # 等价于 set(MY_LIST "a;b;c")
set(MY_LIST "a;b;c")          # 同上
set(MY_LIST a;b;c)            # 同上

列表操作很常用:

list(LENGTH MY_LIST len)      # 获取长度
list(GET MY_LIST 0 first)     # 获取第0个元素
list(APPEND MY_LIST d e)      # 追加元素
list(REMOVE_ITEM MY_LIST b)   # 删除元素
重点:列表元素之间用空格或分号分隔都可以,但建议统一用空格,可读性更好。我在项目中一直用空格,团队里没人抱怨过。

3.4 命令调用:函数名 + 括号 + 参数

CMake 的命令调用,格式是:命令名(参数1 参数2 ...)。参数之间用空格分隔,参数本身可以包含空格,但需要用引号括起来。

add_executable(my_app main.cpp utils.cpp)
target_link_libraries(my_app PRIVATE fmt)
message("编译目标: my_app")

参数可以是变量、列表、路径、甚至嵌套的命令调用。我个人习惯把长参数分行写,清晰很多:

target_include_directories(my_app
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/third_party
)
小技巧:CMake 命令名不区分大小写,但社区惯例是全小写。我建议你也这么做,保持风格统一。

3.5 条件判断:if-elseif-else-endif

条件判断是构建逻辑的核心。CMake 的 if() 语法和 C++ 有点像,但细节上有很多不同。

if(WIN32)
    message("这是 Windows 平台")
elseif(APPLE)
    message("这是 macOS 平台")
else()
    message("这是 Linux 或其他平台")
endif()

判断变量是否定义:

if(DEFINED MY_VAR)
    message("MY_VAR 已定义")
endif()

判断字符串相等:

if(MY_VAR STREQUAL "release")
    set(CMAKE_BUILD_TYPE Release)
endif()
我曾经踩过的坑:CMake 的 if() 对空字符串、0、FALSE、OFF、NO 等值都视为“假”。如果你用 if(MY_VAR) 判断变量是否存在,当变量值为 FALSE 时也会走假分支。正确的做法是用 if(DEFINED MY_VAR) 判断变量是否定义,而不是判断值真假。

3.6 循环:foreach 和 while

CMake 的循环主要有两种:foreachwhile。我个人用得最多的是 foreach,遍历列表非常方便。

set(SOURCES main.cpp utils.cpp network.cpp)
foreach(src ${SOURCES})
    message("源文件: ${src}")
endforeach()

还可以用范围循环:

foreach(i RANGE 1 5)
    message("i = ${i}")
endforeach()

while 用得少一些,但某些场景下很实用:

set(count 0)
while(count LESS 3)
    message("count = ${count}")
    math(EXPR count "${count} + 1")
endwhile()
我的建议:循环里尽量少做复杂操作,尤其是文件操作和外部命令调用。CMake 的循环性能一般,复杂逻辑建议用 Python 脚本预处理,CMake 只做简单的列表遍历。

知识体系总览

下面这张图,把本章的六个语法点串起来了。你可以把它当作一个快速索引,以后写 CMakeLists.txt 时忘了某个语法,回来瞄一眼就行。

CMake 基础语法 注释 # 单行 / #[[ 块注释 ]] 变量 set() / ${} 引用 列表 分号分隔 / list() 操作 命令调用 命令名(参数1 参数2 ...) 条件判断 if() / elseif() / else() 循环 foreach() / while() 核心要点 所有值本质都是字符串 | 变量引用用 ${} | 列表用分号分隔 条件判断注意空值和未定义的区别 | 循环尽量简单

嗯,到这里,CMake 的六个基础语法就讲完了。说实话,这些语法单独看都不难,难的是组合起来解决实际问题。下一章我们会把这些语法用在一个真实的项目里,到时候你就知道它们怎么配合了。

一句话总结:注释写清楚,变量命名好,列表操作熟,命令调用规范,条件判断小心,循环别太复杂。这六点做到了,你的 CMakeLists.txt 就成功了一半。

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