《CMakeList 知识系统学习系列(四):条件与循环》
前言:你是否在CMake中反复踩变量作用域的坑?是否因循环分割错误导致构建失败?本文将详细讲解CMake条件判断与循环的核心技巧!
CMake 条件判断与循环详解
一、条件判断(if
/elseif
/else
)
CMake 的条件判断语句用于根据变量值、文件状态或逻辑表达式动态调整构建逻辑。其核心语法如下:
scss
if(条件表达式)
# 条件成立时执行的代码
elseif(其他条件)
# 其他条件成立时执行的代码
else()
# 默认执行的代码
endif()
1. 基本条件表达式
类型 | 语法示例 | 说明 |
---|---|---|
变量存在性 | if(DEFINED VAR) |
检查变量 VAR 是否定义 |
布尔值判断 | if(VAR) |
VAR 为 ON /1 /TRUE 时成立。直接使用未定义变量会导致CMake警告 |
字符串比较 | if("${VAR}" STREQUAL "value") |
字符串相等性判断(区分大小写) |
数值比较 | if(NUM LESS 10) |
支持 LESS /GREATER /EQUAL |
文件/目录存在 | if(EXISTS "path/to/file") |
检查文件或目录是否存在 |
示例:根据编译类型设置优化选项
scss
if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options(-O3)
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-O0 -g)
else()
message(WARNING "Unknown build type")
endif()
判断变量和数值示例:
kotlin
if(age)
message(STATUS "age is not empty")
else()
message(STATUS "age is empty")
endif()
# 条件判断
set(age 10)
if(age)
message(STATUS "age is not empty")
else()
message(STATUS "age is empty")
endif()
function(show_if)
if(${age} LESS 10)
message(STATUS "age < 10")
elseif(${age} EQUAL 10)
message(STATUS "age = 10")
elseif(${age} GREATER 10)
message(STATUS "age > 10")
endif()
endfunction()
show_if()
set(age 11)
show_if()
set(age 9)
show_if()
输出:
-- age is empty
-- age is not empty
-- age = 10
-- age > 10
-- age < 10
2. 逻辑运算符
运算符 | 语法 | 说明 |
---|---|---|
与 | if(条件1 AND 条件2) |
同时成立时返回真 |
或 | if(条件1 OR 条件2) |
任一成立时返回真 |
非 | if(NOT 条件) |
条件不成立时返回真 |
组合逻辑 | if((A AND B) OR (C)) |
支持括号嵌套优先级 |
示例:多条件判断
scss
if(WIN32 AND MSVC AND NOT CMAKE_CL_64)
message("32位 Windows MSVC 编译器")
endif()
3. 高级条件判断
-
正则表达式匹配 :使用
MATCHES
判断字符串模式scssif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux|Darwin") message("当前为类 Unix 系统") endif() function(match_name name) if("${name}" MATCHES "worl.*") message(STATUS "name match") endif() endfunction() match_name("world")
二、循环(foreach
/while
)
1. foreach
循环
用于遍历列表或范围,支持多种迭代模式:
-
遍历列表
- 直接展开变量
- 语法 :
foreach(var ${my_list})
- 行为 :直接展开变量
${my_list}
为字符串,并根据CMake的默认分隔符(分号或空格)分割成多个元素。 - 风险:若变量值中包含空格或特殊字符,可能导致元素被错误分割。
kotlinset(my_list "a;b c" d) # 元素为 ["a", "b c", "d"] foreach(var ${my_list}) message("${var}") # 输出:a; b; c; d(错误分割) endforeach()
-
IN LISTS
语法 :
foreach(var IN LISTS my_list)
行为 :
IN LISTS
:显式指定列表变量名,直接遍历列表中的每个元素,保留元素完整性,避免空格或分号导致的错误分割
scssset(my_list "a;b c" d) foreach(var IN LISTS my_list) message("${var}") # 正确输出:a;b c; d endforeach()
3
IN ITEMS
语法 :
foreach(var IN ITEMS a b c)
行为:
IN ITEMS
:直接遍历后续的显式项列表,适用于硬编码元素。kotlinforeach(name IN ITEMS "John Doe" "Alice") # 显式遍历硬编码项 endforeach()
优先使用IN LISTS
处理列表变量,避免元素分割问题。
使用IN ITEMS
显式遍历硬编码项,提高代码清晰度
这里还有个小插曲,我在for LISTS的时候如下,结果输出不是想要的"a;b c" "d"。而是 in LISTS my_list
kotlin
set(my_list "a;b c" d)
foreach(var in LISTS my_list)
message("in lists ${var}")
endforeach()
输出
in lists in
in lists LISTS
in lists my_list
刚开始还纳闷呢。这是怎么输出的?
后面看in LISTS my_list不正是var后面的,那为题肯定就是in的大小写问题。
之前在CMakeLists.txt中写message和MESSAGE不是效果等同,之前就错误的认为CMakeLists中大小写不敏感。
查了下资料才知道:CMake的"大小写不敏感"仅针对命令关键字 ,而变量、生成器表达式、文件名等仍严格区分大小写。 那为什么命令关键字大小不敏感? 此设计源于历史兼容性,早期CMake版本使用全大写命令,后续版本逐渐转向小写风格,但保留对大小写的兼容性。原来是历史包袱。O(∩_∩)O哈哈~
- 范围循环
foreach
循环的RANGE
关键字用于生成一个整数序列,并遍历该序列中的每个数值。
支持三种参数形式:
- 单参数 :生成从
0
到N-1
的整数序列(包含0
和N-1
)。 - 双参数 :生成从
start
到end
的整数序列(包含start
和end
)。 - 三参数 :生成从
start
到end
的整数序列,步长为step
。
sql
foreach(loop_var RANGE <N>) # 单参数:0, 1, 2, ..., N-1
foreach(loop_var RANGE <start> <end>) # 双参数:start, start+1, ..., end
foreach(loop_var RANGE <start> <end> <step>) # 三参数:start, start+step, ..., end(步长可为正/负)
参数详解
- 单参数形式
-
语法 :
RANGE <N>
-
范围 :生成
0
到N-1
的整数序列。 -
示例:
cssforeach(i RANGE 3) message("i = ${i}") # 输出:i=0; i=1; i=2 endforeach()
- 双参数形式
-
语法 :
RANGE <start> <end>
-
范围 :生成从
start
到end
的整数序列(包含两端)。 -
示例:
cssforeach(i RANGE 2 4) message("i = ${i}") # 输出:i=2; i=3; i=4 endforeach()
- 三参数形式
-
语法 :
RANGE <start> <end> <step>
-
范围 :生成从
start
到end
的整数序列,步长为step
。- 若
step > 0
,则要求start ≤ end
。 - 若
step < 0
,则要求start ≥ end
。
- 若
-
示例:
cssforeach(i RANGE 5 1 -2) message("i = ${i}") # 输出:i=5; i=3; i=1 endforeach()
注意事项
-
数值类型
RANGE
仅支持整数。如果参数包含非整数,CMake会报错。 -
步长的方向
- 当步长为正数(
step > 0
)时,必须满足start ≤ end
,否则循环不会执行。 - 当步长为负数(
step < 0
)时,必须满足start ≥ end
,否则循环不会执行。
- 当步长为正数(
-
包含结束值
RANGE
生成的序列包含结束值(end
),例如RANGE 2 4
包含4
。 -
CMake版本要求
- 单参数和双参数形式:CMake 3.0及以上。
- 三参数形式(带步长):CMake 3.3及以上。
-
多列表合并遍历
scssset(list1 a b) set(list2 "c d" e) foreach(item IN LISTS list1 list2) message("Item: ${item}") # 输出:a → b → c d → e endforeach()
-
键值对遍历
ZIP_LISTS
(CMake 3.17+)kotlinlist(APPEND keys a b c) list(APPEND values 1 2 3) foreach(key value IN ZIP_LISTS keys values) message("Key: ${key}, Value: ${value}") # 输出:a→1 → b→2 → c→3 endforeach()
ZIP_LISTS需要3.17版本才支持
2. while
循环
基于条件表达式重复执行代码块,需手动更新终止条件:
scss
set(counter 0)
#如果counter变量小于5
while(counter LESS 5)
#打印
message("当前计数: ${counter}")
# counter=counter+1
math(EXPR counter "${counter} + 1")
endwhile()
逻辑差不多可以看懂。就是当counter小于5,就执行while里面的语句,打印counter值,并且把counter变量累加1。
可以看到和我们正常程序里面写的有点不一样。LESS之前if里面学过。比较整数大小的。把变量累加1.用到math。之前我们都是用set是直接给变量赋值
有人说。我们能不说直接把counter变量+1的值再赋值给counter?
不能。下面语句,输出的不是2.而是0+1了。所以计算并赋值得用math命令。至于math详细介绍,后面在细说
kotlin
set(counter 0)
set(temp ${counter}+1)
message(STATUS "temp:${temp}")
//输出
-- temp:0+1
3. 循环控制语句
-
break()
:立即退出循环scssforeach(i RANGE 10) if(i EQUAL 5) break() endif() message("i=${i}") endforeach()
-
continue()
:跳过当前迭代scssforeach(i RANGE 10) if(i MOD 2 EQUAL 0) continue() endif() message("奇数: ${i}") endforeach()
三、最佳实践
1. 条件判断优化
- 避免在循环内重复计算复杂条件,可将结果缓存到变量。
- 优先使用
if(DEFINED VAR)
而非if(VAR)
防止未定义变量报错。
2. 循环性能优化
- 减少在循环内部调用耗时操作(如
file(GLOB)
)。 - 使用
list(APPEND)
替代多次set
操作合并结果。
3. 常见错误处理
-
空列表循环:若列表可能为空,需添加保护逻辑:
scssif(NOT source_list) message(WARNING "列表为空") else() foreach(src IN LISTS source_list) # ... endforeach() endif()
-
无限
while
循环:确保条件最终会变为假。
四、总结
通过合理使用条件判断和循环,CMake 脚本可实现高度动态化和模块化的构建流程,显著提升跨平台项目的可维护性。