9. 流程控制:if/elseif/else条件判断,foreach循环遍历列表,while循环
说实话,CMake 的流程控制,是让构建脚本从「死配置」变成「活逻辑」的关键。
我刚开始写 CMake 时,觉得它就是个变量赋值工具。后来项目复杂了,要判断编译器版本、要区分平台、要遍历一堆源文件……才发现,没有流程控制,寸步难行。
9.1 if/elseif/else:让构建「看人下菜碟」
条件判断,说白了就是让 CMake 根据情况做不同的事。
语法很简单:
if(条件)
# 做点啥
elseif(另一个条件)
# 做点别的
else()
# 都不满足就干这个
endif()
嗯,这里要注意:endif() 不能少。我见过有人漏掉,结果 CMake 报错半天找不到原因。
常见的条件判断场景:
- 判断变量是否存在:
if(DEFINED 变量名) - 判断变量值:
if(变量名 STREQUAL "值") - 判断平台:
if(WIN32)、if(APPLE)、if(UNIX) - 判断编译器:
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
我个人习惯:把平台判断写在最前面,用 if(WIN32) 和 elseif(APPLE) 分流。这样后面就不用到处写条件了,清爽很多。
举个例子,区分 Windows 和 Linux 的链接库:
if(WIN32)
set(MY_LIB mylib.dll)
elseif(UNIX)
set(MY_LIB libmylib.so)
else()
message(FATAL_ERROR "不支持的平台")
endif()
我在项目中遇到过一个问题:某次在 macOS 上构建,if(UNIX) 居然也触发了。后来查文档才发现,macOS 也被 CMake 视为 UNIX 系统。所以如果你要精确判断 Linux,得用 if(CMAKE_SYSTEM_NAME STREQUAL "Linux")。
避坑指南:我曾经在 if() 里直接写 if(变量名) 来判断变量是否为真。但 CMake 的「真」定义很迷——非空字符串、非0数字、ON、YES 都算真。建议用 if(变量名 STREQUAL "ON") 这种明确写法。
9.2 foreach:遍历列表,批量处理
foreach 是我用得最多的循环。说白了,就是给你一个列表,挨个处理里面的元素。
基本语法:
foreach(变量 IN 列表)
# 用 ${变量} 操作每个元素
endforeach()
举个实际例子,批量添加源文件:
set(SOURCES
main.cpp
utils.cpp
network.cpp
database.cpp
)
foreach(SRC ${SOURCES})
message("正在处理源文件:${SRC}")
# 这里可以加编译选项、检查文件是否存在等
endforeach()
foreach 的几种变体:
| 写法 | 说明 | 示例 |
|---|---|---|
foreach(var IN LISTS list) |
遍历列表变量 | foreach(f IN LISTS MY_FILES) |
foreach(var IN ITEMS item1 item2) |
遍历直接给出的项 | foreach(f IN ITEMS a.cpp b.cpp) |
foreach(var RANGE start stop step) |
遍历数字范围 | foreach(i RANGE 1 10 2) |
foreach(var ZIP_LISTS list1 list2) |
同时遍历多个列表 | foreach(k v ZIP_LISTS KEYS VALUES) |
你想想看,如果不用 foreach,你要手动写 10 遍 add_executable() 或者 target_link_libraries(),那得多痛苦。
小技巧:我经常用 foreach 配合 list(APPEND) 来动态构建源文件列表。比如先收集所有 .cpp 文件,再统一加到目标里。
9.3 while:条件循环,慎用但有用
while 循环在 CMake 里用得少,但某些场景下它比 foreach 更合适。
语法:
while(条件)
# 循环体
endwhile()
举个例子,生成递增的测试用例编号:
set(COUNTER 0)
while(COUNTER LESS 10)
message("测试用例 #${COUNTER}")
math(EXPR COUNTER "${COUNTER} + 1")
endwhile()
注意:while 循环容易写出死循环。我曾经在项目里写了一个 while,忘记更新计数器,结果 CMake 卡住了 5 分钟才报超时。从那以后,我每次写 while 都会先确认「退出条件」是否明确。
while 的典型用途:
- 处理不确定长度的列表(比如从文件读取的行数未知)
- 实现重试逻辑(比如检查某个文件是否生成)
- 数学运算或迭代计算
9.4 流程控制的核心逻辑图
下面这张图,是我自己总结的 CMake 流程控制决策树。你看一眼,基本就明白什么时候该用哪个了。
9.5 实战:组合使用流程控制
光讲理论没意思。我拿一个真实项目里的片段给你看:
# 收集所有源文件,排除测试文件
file(GLOB ALL_SOURCES "src/*.cpp")
set(REAL_SOURCES)
foreach(SRC ${ALL_SOURCES})
# 跳过测试文件
if(SRC MATCHES "test_")
message("跳过测试文件:${SRC}")
continue()
endif()
# 检查文件是否存在(其实 file(GLOB) 已经保证了,但习惯性检查)
if(EXISTS ${SRC})
list(APPEND REAL_SOURCES ${SRC})
else()
message(WARNING "文件不存在:${SRC}")
endif()
endforeach()
# 根据平台添加不同的源文件
if(WIN32)
list(APPEND REAL_SOURCES "src/win32_specific.cpp")
elseif(APPLE)
list(APPEND REAL_SOURCES "src/macos_specific.cpp")
else()
list(APPEND REAL_SOURCES "src/linux_specific.cpp")
endif()
add_executable(myapp ${REAL_SOURCES})
你看,这里 foreach 遍历所有文件,if 做条件过滤,最后根据平台追加文件。三个流程控制配合起来,构建脚本就活了。
我的习惯:写流程控制时,先把 if() 和 endif() 成对写好,再往中间填内容。这样不会漏掉结尾。foreach 和 while 也一样。
嗯,流程控制就这些。说白了就是三个关键字:if 做判断,foreach 遍历列表,while 做条件循环。掌握了它们,你的 CMake 脚本就能应对各种复杂场景了。
公众号:蓝海资料掘金营,微信 deep3321