尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【CMake】语法深度解析:控制流、列表与函数实战指南

【CMake】语法深度解析:控制流、列表与函数实战指南 草莓熊Lotso个人主页❄️个人专栏:《C知识分享》 《Linux 入门到实践零基础也能懂》✨生活是默默的坚持毅力是永久的享受 博主简介文章目录前言一. if 判断CMake 逻辑控制的基石1.1 基本语法结构1.2 布尔值判断的底层逻辑1.3 常用比较操作1.3.1 数值比较1.3.2 字符串比较1.3.3 版本号比较1.3.4 路径比较1.4 逻辑运算与存在性检测1.4.1 逻辑运算符1.4.2 存在性检测二. 循环批量处理的利器2.1 foreach 循环的五种语法形式2.1.1 基本形式直接列举元素2.1.2 RANGE 形式遍历数字范围2.1.3 IN ITEMS 形式遍历字面量列表2.1.4 IN LISTS 形式遍历变量列表2.2 while 循环三. list 函数列表操作的瑞士军刀3.1 基本语法3.2 常用列表操作3.2.1 添加元素3.2.2 移除元素3.2.3 其他常用操作3.3 列表操作的常见陷阱四. 函数代码复用与模块化的核心4.1 函数的定义与调用4.2 参数传递内置变量详解4.3 变量作用域最容易踩坑的地方4.3.1 基本作用域规则4.3.2 PARENT_SCOPE 选项4.4 返回值的两种实现方式结尾前言在 C/C 跨平台开发中CMake 早已成为事实标准。但很多开发者仅停留在复制粘贴模板代码的阶段当需要处理复杂的条件判断、批量文件操作或代码复用场景时往往束手无策。控制流语句、列表操作和函数是 CMake 脚本的三大核心支柱掌握它们才能真正从模板搬运工转变为构建系统设计师。本文将结合 CMake 源码逻辑和实战代码从原理到实践逐行拆解 if 判断、foreach/while 循环、list 函数和自定义函数的所有用法帮你彻底攻克 CMake 脚本编写的核心难点。一. if 判断CMake 逻辑控制的基石if()是 CMake 中最基础也最容易出错的控制流语句。它的行为与大多数编程语言有显著差异特别是在变量解析和布尔值判断方面很多坑都源于对其底层逻辑的不了解。1.1 基本语法结构CMake 的 if 语句支持完整的条件分支结构if(condition) commands elseif(condition) # 可选可重复 commands else() # 可选 commands endif()核心特性支持嵌套使用条件表达式无需括号包裹多个条件可通过逻辑运算符组合小括号()可用于改变运算优先级1.2 布尔值判断的底层逻辑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_castbool(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关键陷阱普通字符串字面量如abc永远为 false除非它恰好是 true 常量变量名不需要${}包裹if(OS)等价于if(${OS})环境变量不能直接用if(ENV{VAR})测试必须先展开if($ENV{VAR})1.3 常用比较操作CMake 提供了丰富的比较操作符适用于不同类型的数据1.3.1 数值比较EQUAL等于LESS小于GREATER大于LESS_EQUAL小于等于GREATER_EQUAL大于等于if(20 LESS 30) message(20 30 true) endif()1.3.2 字符串比较STREQUAL等于STRLESS字典序小于STRGREATER字典序大于if(abc STRLESS abd) message(abc abd (字典序) true) endif()1.3.3 版本号比较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()1.3.4 路径比较PATH_EQUAL路径相等CMake 3.24if(/a/b/c PATH_EQUAL /a//b/c/) message(路径相等 true) # 多个分隔符会被合并 endif()1.4 逻辑运算与存在性检测1.4.1 逻辑运算符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()1.4.2 存在性检测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用于在条件为真时重复执行代码块。2.1 foreach 循环的五种语法形式foreach 循环语法非常灵活共有五种常用形式2.1.1 基本形式直接列举元素foreach(variable item1 item2 ...) # 循环体 endforeach()示例foreach(X 1 2;3;4 abc) message(X1: ${X}) endforeach()输出X1: 1 X1: 2;3;4 X1: abc注意分号分隔的字符串会被当作一个整体元素。2.1.2 RANGE 形式遍历数字范围# 遍历 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: 10 X3: 10 X3: 13 X3: 16 X3: 192.1.3 IN ITEMS 形式遍历字面量列表foreach(variable IN ITEMS item1 item2 ...)与基本形式类似但更明确地表示后面是字面量元素foreach(X IN ITEMS 1 2;3;4 abc) message(X4: ${X}) endforeach()输出与基本形式完全相同。2.1.4 IN LISTS 形式遍历变量列表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不会。2.2 while 循环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)进行数值计算while 循环的条件判断规则与 if 完全相同三. list 函数列表操作的瑞士军刀在 CMake 中列表本质上是用分号分隔的字符串。list 函数提供了丰富的操作方法用于处理这种特殊的数据结构。3.1 基本语法list(COMMAND list_variable [args...])list 函数的第一个参数是操作命令第二个参数是要操作的列表变量。3.2 常用列表操作3.2.1 添加元素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.cpp3.2.2 移除元素REMOVE_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})3.2.3 其他常用操作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})3.3 列表操作的常见陷阱空列表处理空列表在 CMake 中是空字符串很多操作对空列表无效元素包含分号如果元素本身包含分号需要用引号包裹列表展开在函数参数中传递列表时会自动展开为多个参数四. 函数代码复用与模块化的核心函数是组织 CMake 代码、提高复用性的重要工具。通过定义函数可以封装复杂的操作避免代码重复使构建脚本更加清晰易读。4.1 函数的定义与调用# 定义函数 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 84.2 参数传递内置变量详解CMake 函数提供了一组内置变量用于访问传递给函数的参数内置变量描述示例调用 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;E4.3 变量作用域最容易踩坑的地方CMake 函数具有独立的作用域函数内部定义的变量默认是局部变量不会影响外部作用域。4.3.1 基本作用域规则set(SCOP 全局作用域) function(print_var) # 可以读取父作用域的变量 message(函数内读取: ${SCOP}) # 函数内部修改变量默认只影响局部作用域 set(SCOP 函数作用域) message(函数内修改后: ${SCOP}) endfunction() print_var() # 外部变量的值不变 message(函数外部: ${SCOP})输出函数内读取: 全局作用域 函数内修改后: 函数作用域 函数外部: 全局作用域4.3.2 PARENT_SCOPE 选项如果需要将函数内部的变量值传递到父作用域可以使用PARENT_SCOPE选项function(print_var) message(函数内读取: ${SCOP}) # 使用 PARENT_SCOPE 修改父作用域的变量 set(SCOP 函数作用域 PARENT_SCOPE) # 注意当前函数作用域的变量值仍然不变 message(函数内修改后: ${SCOP}) endfunction() print_var() message(函数外部: ${SCOP})输出函数内读取: 全局作用域 函数内修改后: 全局作用域 # 关键当前作用域的值没有变 函数外部: 函数作用域 # 父作用域的值被修改了底层原理函数调用时父作用域的变量会被拷贝到函数的临时内存空间PARENT_SCOPE直接修改父作用域的原始内存空间函数内部仍然使用临时内存空间的值4.4 返回值的两种实现方式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 判断布尔值判断的三层逻辑命名常量 → 数字 → 变量普通字符串字面量非 true 常量永远为 false环境变量必须用if($ENV{VAR})测试不能直接用if(ENV{VAR})掌握数值、字符串、版本号和路径四种比较操作2. 循环foreach 的五种语法形式及适用场景IN LISTS会展开变量中的列表IN ITEMS不会while 循环必须在体内修改条件变量避免无限循环3. list 函数列表本质是分号分隔的字符串掌握添加、移除、排序、获取长度等常用操作注意空列表和包含分号的元素的特殊处理4. 函数参数传递的内置变量ARGC、ARGV、ARGN等变量作用域规则默认局部PARENT_SCOPE修改父作用域PARENT_SCOPE的陷阱当前函数作用域的值不变返回值的两种实现方式PARENT_SCOPE推荐和缓存变量结尾 我是草莓熊 Lotso若这篇技术干货帮你打通了学习中的卡点 【关注】跟我一起深耕技术领域从基础到进阶见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好需要时直接查、随时用 【评论】分享你的经验或疑问比如曾踩过的技术坑一起交流避坑 ️ 【投票】用你的选择助力社区内容方向告诉大家哪个技术点最该重点拆解 技术之路难免有困惑但同行的人会让前进更有方向愿我们都能在自己专注的领域里一步步靠近心中的技术目标结语掌握上面这些知识你就能轻松处理 CMake 脚本中的各种复杂逻辑写出更加清晰、健壮、可维护的构建配置。在下一篇文章中我们将继续深入 CMake 的目标系统和属性机制这是现代 CMake 的核心所在。✨把这些内容吃透超牛的放松下吧✨ʕ˘ᴥ˘ʔづきらど
返回列表