8人参与 • 2026-08-06 • C/C++
在 c/c++ 跨平台开发中,cmake 早已成为事实标准。但很多开发者仅停留在"复制粘贴模板代码"的阶段,当需要处理复杂的条件判断、批量文件操作或代码复用场景时,往往束手无策。控制流语句、列表操作和函数是 cmake 脚本的三大核心支柱,掌握它们才能真正从"模板搬运工"转变为"构建系统设计师"。本文将结合 cmake 源码逻辑和实战代码,从原理到实践逐行拆解 if 判断、foreach/while 循环、list 函数和自定义函数的所有用法,帮你彻底攻克 cmake 脚本编写的核心难点。
if() 是 cmake 中最基础也最容易出错的控制流语句。它的行为与大多数编程语言有显著差异,特别是在变量解析和布尔值判断方面,很多坑都源于对其底层逻辑的不了解。
cmake 的 if 语句支持完整的条件分支结构:
if(<condition>) <commands> elseif(<condition>) # 可选,可重复 <commands> else() # 可选 <commands> endif()
核心特性:
() 可用于改变运算优先级cmake 中布尔值的判断规则非常特殊,我们可以从 cmake 源码中的 getbooleanvalue 函数一探究竟:
// cmake 源码片段:getbooleanvalue 函数逻辑
bool getbooleanvalue(const std::string& arg) const {
// 第一步:检查命名常量
if (cmison(arg)) return true; // "1", "on", "yes", "true", "y"
if (cmisoff(arg)) return false; // "0", "off", "no", "false", "n", "ignore", "notfound"
// 第二步:检查是否为数字
char* end;
double d = std::strtod(arg.c_str(), &end);
if (*end == '\0') { // 整个字符串都是数字
return static_cast<bool>(d);
}
// 第三步:检查是否为已定义的变量
cmvalue def = this->getdefinitionifunquoted(arg);
return !def.isoff();
}基于这个逻辑,我们可以总结出完整的布尔值判断规则:
| 输入类型 | 判断为 true 的情况 | 判断为 false 的情况 |
|---|---|---|
| 命名常量 | “1”, “on”, “yes”, “true”, “y”(大小写不敏感) | “0”, “off”, “no”, “false”, “n”, “ignore”, “notfound”,以及以 “-notfound” 结尾的字符串 |
| 数字 | 非零数字(包括浮点数) | 0 |
| 变量 | 变量已定义且值不是 false 常量 | 变量未定义,或值为 false 常量 |
| 字符串字面量 | 仅当字符串内容是 true 常量 | 其他所有字符串 |
实战验证代码:
cmake_minimum_required(version 3.18)
project(ifbooleandemo)
# 1. 命名常量测试
message("=== 命名常量测试 ===")
if("on") message("1: on = true") else() message("1: on = false") endif()
if("off") message("2: off = true") else() message("2: off = false") endif()
if("yes") message("3: yes = true") else() message("3: yes = false") endif()
if("no") message("4: no = true") else() message("4: no = false") endif()
# 2. 数字测试
message("\n=== 数字测试 ===")
if("1.1") message("5: 1.1 = true") else() message("5: 1.1 = false") endif()
if("0") message("6: 0 = true") else() message("6: 0 = false") endif()
if("123a") message("7: 123a = true") else() message("7: 123a = false") endif()
# 3. 变量测试
message("\n=== 变量测试 ===")
set(os "linux")
if(os) message("8: os(linux) = true") else() message("8: os(linux) = false") endif()
set(os "off")
if(os) message("9: os(off) = true") else() message("9: os(off) = false") endif()
# 4. 字符串字面量测试
message("\n=== 字符串字面量测试 ===")
if("abc") message("10: \"abc\" = true") else() message("10: \"abc\" = false") endif()
if("on") message("11: \"on\" = true") else() message("11: \"on\" = false") endif()运行结果:
=== 命名常量测试 ===
1: on = true
2: off = false
3: yes = true
4: no = false
=== 数字测试 ===
5: 1.1 = true
6: 0 = false
7: 123a = false
=== 变量测试 ===
8: os(linux) = true
9: os(off) = false
=== 字符串字面量测试 ===
10: "abc" = false
11: "on" = true
关键陷阱:
${} 包裹,if(os) 等价于 if(${os})if(env{var}) 测试,必须先展开:if($env{var})cmake 提供了丰富的比较操作符,适用于不同类型的数据:
equal:等于less:小于greater:大于less_equal:小于等于greater_equal:大于等于if("20" less "30")
message("20 < 30 = true")
endif()
strequal:等于strless:字典序小于strgreater:字典序大于if("abc" strless "abd")
message("abc < abd (字典序) = true")
endif()
version_equal:版本等于version_less:版本小于version_greater:版本大于set(version "1.2.3")
if(version version_less "1.3.0")
message("版本 1.2.3 < 1.3.0 = true")
endif()
path_equal:路径相等(cmake 3.24+)if("/a/b/c" path_equal "/a//b/c/")
message("路径相等 = true") # 多个分隔符会被合并
endif()
not:逻辑非and:逻辑与or:逻辑或if(not "off")
message("not off = true")
endif()
if("on" and "off")
message("on and off = true")
else()
message("on and off = false")
endif()
if("on" or "off")
message("on or off = true")
endif()cmake 提供了多种检测命令,用于检查文件、目标、变量等是否存在:
| 检测命令 | 功能 |
|---|---|
exists <path> | 检查文件或目录是否存在 |
is_directory <path> | 检查是否为目录 |
is_absolute <path> | 检查是否为绝对路径 |
command <command> | 检查命令是否存在 |
target <target> | 检查目标是否存在 |
defined <var> | 检查变量是否定义 |
defined cache{<var>} | 检查缓存变量是否定义 |
defined env{<var>} | 检查环境变量是否定义 |
实战示例:
# 检查文件是否存在
if(exists ${cmake_current_list_file})
message("当前 cmakelists.txt 存在")
endif()
# 检查目标是否存在
add_library(mymath interface imported)
if(target mymath)
message("目标 mymath 存在")
endif()
# 检查变量是否定义
set(name "bit")
if(defined name)
message("变量 name 已定义")
endif()
# 检查环境变量是否定义
if(defined env{path})
message("环境变量 path 已定义")
endif()cmake 提供了两种循环结构:foreach 和 while。其中 foreach 用于遍历列表或范围,是最常用的循环方式;while 用于在条件为真时重复执行代码块。
foreach 循环语法非常灵活,共有五种常用形式:
foreach(<variable> <item1> <item2> ...) # 循环体 endforeach()
示例:
foreach(x "1" "2;3;4" "abc")
message("x1: ${x}")
endforeach()
输出:
x1: 1 x1: 2;3;4 x1: abc
注意:分号分隔的字符串会被当作一个整体元素。
# 遍历 0 到 stop(包含) foreach(<variable> range <stop>) # 遍历 start 到 stop(包含),步长为 step foreach(<variable> range <start> <stop> [<step>])
示例:
# 遍历 0 到 10
foreach(x range 10)
message("x2: ${x}")
endforeach()
# 遍历 10 到 20,步长为 3
foreach(x range 10 20 3)
message("x3: ${x}")
endforeach()输出:
x2: 0
x2: 1
...
x2: 10x3: 10
x3: 13
x3: 16
x3: 19
foreach(<variable> in items <item1> <item2> ...)
与基本形式类似,但更明确地表示后面是字面量元素:
foreach(x in items "1" "2;3;4" "abc")
message("x4: ${x}")
endforeach()
输出与基本形式完全相同。
foreach(<variable> in lists <list_variable1> <list_variable2> ...)
这是最常用的形式,用于遍历存储在变量中的列表:
set(l1 "1")
set(l2 "2;3;4")
set(l3 "abc")
foreach(x in lists l1 l2 l3)
message("x5: ${x}")
endforeach()输出:
x5: 1
x5: 2
x5: 3
x5: 4
x5: abc
关键区别:in lists 会自动展开变量中的列表(按分号拆分),而 in items 不会。
while 循环在条件为真时重复执行代码块:
while(<condition>) # 循环体 endwhile()
示例:计数器循环
set(count 1)
while(count less_equal 5)
message(status "计数: ${count}")
math(expr count "${count} + 1") # 递增计数
endwhile()
输出:
-- 计数: 1
-- 计数: 2
-- 计数: 3
-- 计数: 4
-- 计数: 5
注意事项:
math(expr) 进行数值计算在 cmake 中,列表本质上是用分号分隔的字符串。list 函数提供了丰富的操作方法,用于处理这种特殊的数据结构。
list(command <list_variable> [args...])
list 函数的第一个参数是操作命令,第二个参数是要操作的列表变量。
append:在列表末尾添加元素prepend:在列表开头添加元素insert:在指定位置插入元素set(srcs "main.cpp" "utils.cpp" "math.cpp")
# 末尾添加
list(append srcs "network.cpp")
message("append: ${srcs}") # main.cpp;utils.cpp;math.cpp;network.cpp
# 开头添加
list(prepend srcs "config.cpp")
message("prepend: ${srcs}") # config.cpp;main.cpp;utils.cpp;math.cpp;network.cpp
# 指定位置插入(索引从 0 开始)
list(insert srcs 2 "logger.cpp")
message("insert: ${srcs}") # config.cpp;main.cpp;logger.cpp;utils.cpp;math.cpp;network.cppremove_item:移除指定值的元素remove_at:移除指定索引的元素remove_duplicates:移除重复元素# 移除指定值
list(remove_item srcs "math.cpp")
message("remove_item: ${srcs}")
# 移除指定索引
list(remove_at srcs 1)
message("remove_at: ${srcs}")
# 移除重复
list(append srcs "utils.cpp")
list(remove_duplicates srcs)
message("remove_duplicates: ${srcs}")length:获取列表长度get:获取指定索引的元素sort:排序列表reverse:反转列表join:用指定字符串连接列表元素# 获取长度
list(length srcs len)
message("列表长度: ${len}")
# 获取元素
list(get srcs 0 first_item)
message("第一个元素: ${first_item}")
# 排序
list(sort srcs)
message("排序后: ${srcs}")
# 反转
list(reverse srcs)
message("反转后: ${srcs}")
# 连接
list(join srcs " | " joined)
message("连接后: ${joined}")函数是组织 cmake 代码、提高复用性的重要工具。通过定义函数,可以封装复杂的操作,避免代码重复,使构建脚本更加清晰易读。
# 定义函数 function(<function_name> <arg1> <arg2> ...) # 函数体 endfunction() # 调用函数 <function_name>(<value1> <value2> ...)
示例:简单的加法函数
function(add_numbers a b)
math(expr result "${a} + ${b}")
message("${a} + ${b} = ${result}")
endfunction()
# 调用
add_numbers(3 5) # 输出: 3 + 5 = 8cmake 函数提供了一组内置变量,用于访问传递给函数的参数:
| 内置变量 | 描述 | 示例(调用 func(a b c d e)) |
|---|---|---|
${argc} | 参数总数 | 5 |
${argv} | 所有参数组成的列表 | “a;b;c;d;e” |
${argv0} | 第一个参数 | “a” |
${argv1} | 第二个参数 | “b” |
${argn} | 剩余参数(可变参数) | 若函数定义为 func(a b),则为 “c;d;e” |
实战示例:打印所有参数
function(print_args first second)
message("argc = ${argc}")
message("argv = ${argv}")
message("argv0 = ${argv0}")
message("argv1 = ${argv1}")
message("argv2 = ${argv2}")
message("argn = ${argn}")
endfunction()
# 调用
print_args(a b c d e)输出:
argc = 5
argv = a;b;c;d;e
argv0 = a
argv1 = b
argv2 = c
argn = c;d;e
cmake 函数具有独立的作用域,函数内部定义的变量默认是局部变量,不会影响外部作用域。
set(scop "全局作用域")
function(print_var)
# 可以读取父作用域的变量
message("函数内读取: ${scop}")
# 函数内部修改变量,默认只影响局部作用域
set(scop "函数作用域")
message("函数内修改后: ${scop}")
endfunction()
print_var()
# 外部变量的值不变
message("函数外部: ${scop}")输出:
函数内读取: 全局作用域
函数内修改后: 函数作用域
函数外部: 全局作用域
如果需要将函数内部的变量值传递到父作用域,可以使用 parent_scope 选项:
function(print_var)
message("函数内读取: ${scop}")
# 使用 parent_scope 修改父作用域的变量
set(scop "函数作用域" parent_scope)
# 注意:当前函数作用域的变量值仍然不变!
message("函数内修改后: ${scop}")
endfunction()
print_var()
message("函数外部: ${scop}")输出:
函数内读取: 全局作用域
函数内修改后: 全局作用域 # 关键!当前作用域的值没有变
函数外部: 函数作用域 # 父作用域的值被修改了
底层原理:
parent_scope 直接修改父作用域的原始内存空间cmake 函数没有传统的返回值语法,但可以通过以下两种方式"返回"结果:
方式 1:通过 parent_scope 设置变量(推荐,如果你理解这个的话)
function(sum a b output_var)
math(expr result "${a} + ${b}")
set(${output_var} ${result} parent_scope)
endfunction()
# 调用
sum(3 5 result)
message("3 + 5 = ${result}") # 输出: 3 + 5 = 8方式 2:通过缓存变量(全局可见)
function(sum_global a b)
math(expr result "${a} + ${b}")
set(global_sum ${result} cache internal "全局求和结果" force)
endfunction()
# 调用
sum_global(3 5)
message("全局结果: ${global_sum}") # 输出: 全局结果: 8注意:缓存变量会跨多次 cmake 运行保持不变,使用时需谨慎。
总结:
1. if 判断
if($env{var}) 测试,不能直接用 if(env{var})2. 循环
in lists 会展开变量中的列表,in items 不会3. list 函数
4. 函数
argc、argv、argn 等parent_scope 修改父作用域parent_scope 的陷阱:当前函数作用域的值不变parent_scope(推荐)和缓存变量到此这篇关于 c/c++ 开发中cmake语法深度解析之控制流、列表与函数实战指南的文章就介绍到这了,更多相关c++ cmake语法内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论