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 的循环主要有两种:foreach 和 while。我个人用得最多的是 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 的六个基础语法就讲完了。说实话,这些语法单独看都不难,难的是组合起来解决实际问题。下一章我们会把这些语法用在一个真实的项目里,到时候你就知道它们怎么配合了。
一句话总结:注释写清楚,变量命名好,列表操作熟,命令调用规范,条件判断小心,循环别太复杂。这六点做到了,你的 CMakeLists.txt 就成功了一半。
公众号:蓝海资料掘金营,微信deep3321