CMake 变量完全指南:从 set() 到 CACHE 与嵌套引用(LKS32 嵌入式实战) [TOC](变量与 set())摘要本文深入讲解 CMake 中一切皆字符串的核心哲学系统梳理set()的三种作用域、${}变量引用与嵌套引用、CACHE 变量的持久化机制以及option()与$ENV{}两种特殊变量。结合 LKS32 嵌入式项目实战演示如何用表驱动配置实现多芯片切换、用 option 裁剪 HAL 外设以节省 Flash帮助读者建立配置驱动构建的完整认知。一切皆字符串变量与 set()——CMake 一切皆字符串CMake 没有 int、float、bool 类型——一切存储为字符串。理解这个核心概念是掌握 CMake 语法的基础。set() 是你最常用的命令——它会出现在每一个 CMakeLists.txt 中。本课目标理解 CMake“一切皆字符串”的设计哲学掌握 set() 的三种作用域普通、CACHE、PARENT_SCOPE学会变量引用 ${VAR} 和嵌套引用KaTeX parse error: Expected }, got EOF at end of input: {{VAR}}用 LKS32 项目中的真实变量使用来巩固理解CMake 变量一切都是字符串set (MY_NUMBER 42 ) # 这不是整数——是字符串 42 set (MY_BOOL ON ) # 这不是布尔——是字符串 ON set (MY_LIST a b c ) # 这不是列表——是分号分隔的字符串 a;b;c message ( MY_NUMBER ${MY_NUMBER} ) # 输出: MY_NUMBER 42 message ( MY_BOOL ${MY_BOOL} ) # 输出: MY_BOOL ON message ( MY_LIST ${MY_LIST} ) # 输出: MY_LIST a;b;c分号分隔这就是 CMake 最核心的设计**变量没有类型一切存储为字符串。列表本质上是分号分隔的字符串。**if() 判断时 CMake 会尝试把字符串解释为布尔值——但存储时始终是字符串。set() 的三种作用域——核心理解本页大纲一切皆字符串① 一切皆字符串哲学 → ② set() 基础语法 → ③ 变量引用 ${} → ④ 交互变量类型测试 → ⑤ 为什么嵌入式项目特别依赖这个特性 CMake 最重要的哲学一切皆字符串CMake 没有整数、没有浮点、没有布尔——所有值都是字符串。set(MY_NUM 42)里的 42 是字符串42不是整数。set(MY_FLAG ON)里的 ON 也是字符串。理解这一点你就理解了 CMake 一半的奇怪行为。# Python有类型 x 42 # int 类型 y 42 # str 类型 x 1 43 # 可以运算 # CMake没有类型 set(x 42) # 全是字符串 42 set(y 42) # 和上面没区别 # 没有数学运算要算数得用 math(EXPR ...)第 11 课 为什么这么设计CMake 的运行时是配置阶段configure——它只需要决定用什么参数编译不需要做复杂计算。字符串足够表达一切构建意图路径、宏名、选项所以干脆统一。 set() 基础语法——变量的三种写法语法示例说明普通变量set(MY_VAR value)当前作用域可见第 12 课讲作用域带引号set(MY_VAR “value with space”)含空格/特殊字符时必须引号多值列表set(MY_LIST a b c)其实是 “a;b;c”分号分隔第 13 课# 单个值芯片名 set(CHIP_NAME LKS32MC033) # 单个值优化等级被 toolchain 使用 set(MCU_FLAGS -mcpucortex-m0 -mthumb -mfloat-abisoft) # 列表源文件 set(HAL_SOURCES hal_gpio.c hal_uart.c hal_spi.c) # 列表Include 路径 set(INCLUDE_DIRS drivers/CMSIS/Include drivers/HAL/Inc bsp/Inc) 变量引用 ${}——取值的唯一方式定义变量用set()使用变量用${变量名}。CMake 会在处理命令前先把${VAR}替换成变量的值set(CHIP_NAME LKS32MC033) # ${CHIP_NAME} 会被替换成 LKS32MC033 message(STATUS 正在编译 ${CHIP_NAME}) # 输出正在编译 LKS32MC033 # 也可以嵌入到字符串中间 set(ELF_NAME ${CHIP_NAME}.elf) # ELF_NAME LKS32MC033.elf 替换时机${} 的替换发生在命令执行前——所以 message(STATUS ${VAR}) 等价于 message(STATUS LKS32MC033)。记住这个先替换后执行的模型很多 CMake 诡异行为都能解释。 为什么嵌入式项目特别依赖一切皆字符串 一句话总览一切皆字符串 CMake 用统一方式表达所有构建信息。set() 存值、${} 取值——这是 CMake 语法的地基后面所有语法都建立在这个模型上。 深入字符串的引号规则——CMake 最易踩的坑同样一个值带不带引号、带什么引号结果完全不同写法实际值说明set(A hello world)hello;world2 元素空格 → 分号 → 列表set(A “hello world”)hello world1 元素引号保留空格set(A “hello;world”)hello;world1 元素引号内分号是普通字符set(A ‘hello world’)hello world1 元素单引号也有效非标准但可用set(A “ab”)ab 转义双引号 最容易错的set(A “hello;world”)——很多人以为引号内分号还会分隔其实引号内的分号是普通字符A 是一个元素。记住分号的分隔作用只在引号外生效。 动手实验message() 观察变量# 写个测试 CMakeLists.txt $ cattest.cmakeEOFset(NAME LKS32MC033)set(VERSION1.2.0)set(FEATURES GPIO UART SPI)message(STATUS芯片: ${NAME})message(STATUS版本: ${VERSION})message(STATUS特性: ${FEATURES})message(STATUS未定义: ${NOPE})EOF# 用 cmake 直接跑不配置工程只执行脚本 $ cmake-P test.cmake--芯片:LKS32MC033--版本:1.2.0--特性:GPIO;UART;SPI ← 列表用分号显示--未定义:← 未定义空不报错 cmake -P 的妙用cmake -P script.cmake 只执行脚本不配置工程——测试 CMake 语法最快的工具。学语法时多跑 -P 实验比空想快 10 倍。️ 变量命名规范——CMake 社区约定规范说明大写 下划线CHIP_NAME / HAL_SOURCES——CMake 惯例区别于命令小写前缀分组ENABLE_开关/ CMAKE_内置/ LKS32_*项目专属避免小写小写容易与命令/函数名混淆描述性FLASH_SIZE 优于 F——代码即文档 实践建议学完本课立刻动手用 cmake -P 跑一个 set() message() 的小脚本亲眼看看字符串/列表/空值的输出——5 分钟胜过读 50 分钟。CACHE变量CACHE 变量——跨作用域的全局变量CACHE 是 CMake 最强大的变量机制——它把变量存入 CMakeCache.txt所有 CMakeLists.txt包括子目录都能看到且下次 configure 会记住用户的选择。CACHE 变量的四种类型类型语法用途STRINGset(VAR val CACHE STRING desc)任意字符串——最常用。toolchain.cmake 中的编译器路径、CPU 标志。BOOLset(VAR ON CACHE BOOL desc)ON/OFF 开关。控制是否启用某个特性如 ENABLE_TESTS。PATHset(VAR /path CACHE PATH desc)文件系统路径。cmake-gui 会显示文件夹选择器。FILEPATHset(VAR /path/file CACHE FILEPATH desc)文件路径。cmake-gui 会显示文件选择器。LKS32 项目中的 CACHE 变量——CMakePresets.json 传入在 LKS32 项目中CMAKE_BUILD_TYPE和CMAKE_TOOLCHAIN_FILE都是通过 CMakePresets.json 的cacheVariables传入的 CACHE 变量{ version : 3 , configurePresets : [ { name : debug , cacheVariables : { CMAKE_BUILD_TYPE : Debug , // - CACHE STRING CMAKE_TOOLCHAIN_FILE : ${sourceDir}/cmake/toolchain.cmake // - CACHE FILEPATH } } ] }CACHE 变量的重要行为如果缓存已有值则不更新set (MY_OPTION default CACHE STRING An option ) # 第一次 cmake configure: CMakeCache.txt 中 MY_OPTION default # 用户在命令行: cmake -DMY_OPTIONcustom # 第二次 cmake configure: CMakeCache.txt 中 MY_OPTION custom保持用户的值 # CMakeLists.txt 中 set(... CACHE ...) 不会覆盖用户设置的值 这就是为什么改了 toolchain.cmake 里的 CACHE 变量后必须删 CMakeCache.txt 或 rm -rf build——CACHE 记住的是旧值set() 不会覆盖。本页大纲CACHE 变量① 普通变量 vs CACHE 变量的本质区别 → ② CACHE 的三要素类型/描述/默认值→ ③ 交互CACHE 生命周期 → ④ 在 LKS32 项目里的应用 → ⑤ 什么时候该用 CACHE 普通变量 vs CACHE 变量——“会话记忆” vs “持久记忆”普通变量set(VAR val)只存在于当前 configure 运行期间下次 configure 就忘了。CACHE 变量set(VAR val CACHE STRING “desc”)写入CMakeCache.txt跨 configure 持久保存——这正是第 07 课看到的缓存文件。 CACHE 变量的三要素——类型/描述/默认值set(VAR_NAME default_value CACHE TYPE description ) # TYPE 可选值 # STRING — 字符串最常用 # BOOL — 布尔ON/OFF # FILEPATH — 文件路径 # PATH — 目录路径 # 实例LKS32 芯片选择 set(LKS32_CHIP LKS32MC033 CACHE STRING 目标芯片型号) # 实例是否启用 HAL 外设裁剪 set(ENABLE_HAL_PRUNING ON CACHE BOOL 是否裁剪未使用的外设驱动) 命令行覆盖CACHE 变量的持久不是绝对的——命令行 -D VAR新值 可以覆盖缓存值。这就是为什么 CMakePresets 里的 cacheVariables 能控制配置第 33 课。️ CACHE 在 LKS32 项目里的三大应用应用代码作用芯片选择set(LKS32_CHIP LKS32MC033 CACHE STRING “…”)用户可 -D 切换芯片第 35 课构建类型CMAKE_BUILD_TYPECMake 内置 CACHEDebug/Release 切换第 34 课外设开关set(ENABLE_UART ON CACHE BOOL “…”)裁剪 HAL 驱动第 53 课 判断口诀问自己“这个值用户可能用 -D 改吗”——可能 → CACHE纯内部计算 → 普通变量。CACHE 是对外接口普通变量是内部实现。 一句话总览CACHE 变量 CMake 的持久记忆。它把配置写进 CMakeCache.txt让用户能用 -D 控制构建——这是配置驱动构建的核心机制。 CMakeCache.txt 里 CACHE 变量长什么样# 打开 build/debug/CMakeCache.txt 你会看到 LKS32_CHIP:STRING LKS32MC033 ^变量名 ^类型 ^值 ENABLE_UART:BOOL ON CMAKE_BUILD_TYPE:STRING Debug CMAKE_TOOLCHAIN_FILE:FILEPATH C:/.../toolchain.cmake 看懂缓存格式格式是 变量名:类型值——类型信息也存了所以 GUI 能显示正确的控件BOOL 显示复选框。这也是为什么 CACHE 要声明类型的原因。️ CACHE 变量的五种使用场景#场景示例为什么 CACHE1用户可配置项芯片型号、优化等级用户 -D 覆盖2工具链信息编译器路径、链接脚本跨 configure 记住3外设开关ENABLE_SPI用户决定编什么4构建类型CMAKE_BUILD_TYPEDebug/Release 切换5版本/时间戳PROJECT_VERSION构建时固定值 记忆锚点CACHE 持久 可 -D 覆盖 存 CMakeCache.txt。看到用户能改的配置第一反应就是 CACHE/option。 缓存陷阱提醒如果 -D 改的 CACHE 变量没生效——先删 build/ 再重新 configure。缓存是最常见的改了没反应元凶第 15 课详解。引用与嵌套变量引用——${VAR} 与嵌套引用CMake 用${VAR}引用变量值。嵌套引用${${VAR}}是 CMake 的独门绝技——可以实现动态变量名。基本引用与嵌套引用set (CHIP LKS32MC033 ) set (CORE cortex-m0 ) message ( Chip: ${CHIP} ) # 输出: Chip: LKS32MC033 message ( Core: ${CORE} ) # 输出: Core: cortex-m0 # 未定义的变量——输出空字符串不报错 message ( Undefined: ${UNDEF_VAR} ) # 输出: Undefined:set (CHIP_NAME LKS32MC033 ) set (LKS32MC033_FLASH 32KB ) set (LKS32MC033_RAM 4KB ) # ${CHIP_NAME} LKS32MC033 # ${${CHIP_NAME}_FLASH} ${LKS32MC033_FLASH} 32KB message ( Flash: ${${CHIP_NAME}_FLASH} ) # 输出: Flash: 32KB message ( RAM: ${${CHIP_NAME}_RAM} ) # 输出: RAM: 4KB 嵌套引用的实用场景多芯片项目中可以通过 CHIP_NAME 变量动态选择对应的 FLASH_SIZE/RAM_SIZE/STARTUP_FILE——不需要为每个芯片写一套 if/else。LKS32 项目中的变量引用实例# PROJECT_NAME 由 project() 自动设置: lks32mc033-baremetal add_executable ( ${PROJECT_NAME} .elf ...) # - lks32mc033-baremetal.elf # CMAKE_SOURCE_DIR 是 CMake 内置变量: 项目根目录 include_directories ( ${CMAKE_SOURCE_DIR} /config) # - /path/to/LKS32_Cmake/config # 自定义变量 LINKER_SCRIPT set (LINKER_SCRIPT ${CMAKE_SOURCE_DIR} /drivers/Device/Source/lks32mc03x.ld) target_link_options (... -T${LINKER_SCRIPT}) # - -T/.../lks32mc03x.ldCMake 内置变量速查变量值说明CMAKE_SOURCE_DIR顶层 CMakeLists.txt 所在目录无论在多深的子目录中这个值始终指向根目录CMAKE_BINARY_DIR构建目录build/debug编译产物存放位置CMAKE_CURRENT_SOURCE_DIR当前 CMakeLists.txt 所在目录在子目录中会变——指向当前子目录PROJECT_NAMEproject() 设置的名称lks32mc033-baremetal本页大纲引用与嵌套① ${} 引用规则 → ② 嵌套引用KaTeX parse error: Expected }, got EOF at end of input: {{VAR}} → ③ 引号与转义 → ④ 交互嵌套引用演练 → ⑤ 未定义变量的行为 嵌套引用——KaTeX parse error: Expected }, got EOF at end of input: {{VAR}} 的威力与陷阱CMake 支持嵌套引用先解内层 ${VAR} 得到变量名再解外层。这在多芯片项目中非常有用# 定义每个芯片的 Flash 大小表驱动 set(LKS32MC033_FLASH 32768) # 32 KB set(STM32F103_FLASH 131072) # 128 KB # 当前芯片由用户配置 set(CHIP_NAME LKS32MC033) # 嵌套引用先取 CHIP_NAME 的值作为变量名 # ${${CHIP_NAME}_FLASH} → ${LKS32MC033_FLASH} → 32768 message(STATUS ${CHIP_NAME} Flash ${${CHIP_NAME}_FLASH}) # 输出LKS32MC033 Flash 32768 # 换成 STM32 只需改 CHIP_NAME set(CHIP_NAME STM32F103) message(STATUS ${CHIP_NAME} Flash ${${CHIP_NAME}_FLASH}) # 输出STM32F103 Flash 131072 这个模式的价值用嵌套引用做表驱动配置加新芯片 加一行 set()不用改任何逻辑代码。第 35 课多芯片 Presets 会大量使用。 引号与转义——含空格/特殊字符的值写法值说明set(A hello world)hello;world列表没引号 → 空格变分号 → 两个元素set(A “hello world”)hello world单个值引号 → 保留空格 → 一个元素set(A “path with \ spaces”)path with \ spaces\ 转义反斜杠set(A “say “hi””)say “hi” 转义双引号 最常见的坑set(MY_FLAGS -Wall -Wextra) 没有引号——MY_FLAGS 变成了两个元素的列表-Wall;-Wextra如果你要传给编译器其实列表形式反而刚好编译器参数就是多个。但如果你把它当一个字符串用就会踩坑。❓ 未定义变量的行为——CMake 与 C 语言不同# 未定义变量 空字符串不报错 message(STATUS 值${NO_SUCH_VAR}) # 输出值 空 # 对比 C 语言未定义变量会编译报错 // int main(){ printf(%d, NO_SUCH_VAR); } // 报错 # 所以可以用 if(DEFINED ...) 检查第 10 课 if(DEFINED NO_SUCH_VAR) message(STATUS 有定义) else() message(STATUS 未定义 → 空字符串) endif() 这个特性的双面性好处拼字符串时不怕漏变量坏处拼错变量名静默变空难排查。防御姿势关键路径用 if(NOT DEFINED …) 显式检查第 10 课。 一句话总览引用与嵌套 ${} 取值的完整规则。嵌套引用是表驱动配置的利器引号规则决定一个值还是列表未定义变量是空字符串——掌握这三个取值不再迷惑。 引用中的特殊形式——${} 家族全集写法含义示例${VAR}普通变量引用${CHIP_NAME} → LKS32MC033$ENV{VAR}环境变量引用$ENV{PATH} → 系统 PATH$CACHE{VAR}强制读 CACHE 值$CACHE{CMAKE_BUILD_TYPE}KaTeX parse error: Expected }, got EOF at end of input: {{VAR}}嵌套引用表驱动KaTeX parse error: Expected }, got EOF at end of input: {{CHIP}_FLASH}$TARGET_FILE:t生成器表达式第 18 课$TARGET_FILE:app 生成器表达式预告$… 是生成器表达式在生成阶段才求值——和第 18 课 POST_BUILD 强相关。现在只需知道它存在后面细讲。 表驱动配置完整实例——多芯片项目# 芯片参数表每个芯片一行 set(LKS32MC033_CORE cortex-m0) # 内核 set(LKS32MC033_FLASH 32768) # Flash 大小 set(LKS32MC033_RAM 4096) # RAM 大小 set(LKS32MC033_LD lks32mc03x.ld) # 链接脚本 set(STM32F103_CORE cortex-m3) set(STM32F103_FLASH 131072) set(STM32F103_RAM 20480) set(STM32F103_LD stm32f103x8.ld) # 当前芯片用户选择 set(CHIP_NAME LKS32MC033 CACHE STRING 目标芯片) # 嵌套引用取值 set(MCU_CORE ${${CHIP_NAME}_CORE}) set(FLASH_SIZE ${${CHIP_NAME}_FLASH}) set(LINKER_SCRIPT ${${CHIP_NAME}_LD}) message(STATUS 芯片${CHIP_NAME} 内核${MCU_CORE} Flash${FLASH_SIZE}) # 输出芯片LKS32MC033 内核cortex-m0 Flash32768 # 换芯片cmake -DCHIP_NAMESTM32F103 → 全部自动切换 这就是表驱动加新芯片 加 4 行 set() 到参数表逻辑零改动。第 35 课多芯片 Presets 第 54 课多架构实战都会用这个模式。 变量速查卡——随时翻阅想要写法注意存一个值set(VAR value)含空格加引号存多个值set(LIST a b c)实际是分号列表用户可配置option(VAR “描述” ON)可 -D 覆盖读环境变量$ENV{NAME}不是 ${}表驱动KaTeX parse error: Expected }, got EOF at end of input: {{CHIP}_FLASH}内层先解 引用顺序提醒${} 替换发生在命令执行前——所以 ${VAR} 里的 VAR 必须是当前作用域可见的变量。函数内局部变量、子目录变量都遵循作用域规则第 12 课。option()与$ENV{}option() 与环境变量——两种特殊变量除了 set()CMake 还提供了option() 和环境变量 $ENV{}——分别用于布尔开关和读取系统环境变量。option()——布尔开关的语法糖# 等价写法——option() 更简洁 option (ENABLE_UART Enable UART driver ON ) # 等价于: # set(ENABLE_UART ON CACHE BOOL Enable UART driver) option (ENABLE_SPI Enable SPI driver OFF ) option (ENABLE_I2C Enable I2C driver OFF )用户在命令行可以这样控制嵌入式场景用 option() 控制外设裁剪# 在顶层 CMakeLists.txt 定义开关option(USE_GPIOUse GPIO driverON)# 必须——LED 点灯option(USE_UARTUse UART driverOFF)option(USE_SPIUse SPI driverOFF)option(USE_I2CUse I2C driverOFF)option(USE_ADCUse ADC driverOFF)# 在 drivers/HAL/CMakeLists.txt 中按条件编译target_sources(drivers_HAL PRIVATE Src/lks32mc03x_gpio.c # 总是编译必须 Src/lks32mc03x_sys.c # 总是编译必须)if(USE_UART)target_sources(drivers_HAL PRIVATE Src/lks32mc03x_uart.c)endif()if(USE_SPI)target_sources(drivers_HAL PRIVATE Src/lks32mc03x_spi.c)endif() 效果不加 UART——uart.c 不编译。Flash 占用减少 ~200B。在 4KB RAM 的 LKS32 上每一个不编译的驱动都能省出宝贵的空间。$ENV{}——读取系统环境变量message ( Home dir: $ENV{HOME} ) # 读取 HOME 环境变量 message ( PATH: $ENV{PATH} ) # 读取 PATH # 设置环境变量影响子进程如 Ninja 调用的 GCC set (ENV{PATH} C:/msys64/ucrt64/bin;$ENV{PATH} ) LKS32 项目中不用 $ENV{}——因为 PATH 在 .vscode/settings.json 中通过 cmake.environment.PATH 配置了。CMake 读取这个设置并在启动 Ninja 时传递正确的 PATH。本页大纲option() 与 $ENV{}① option() 是什么 → ② 嵌入式场景用 option 控制外设编译 → ③ $ENV{} 读取环境变量 → ④ 交互option 开关演示 → ⑤ option vs set(CACHE BOOL) 对比️ option()——布尔开关的优雅封装option(名称 描述 默认值)是set(名称 默认值 CACHE BOOL 描述)的语法糖——专门用来定义 ON/OFF 开关GUI 工具cmake-gui会显示成复选框。# option(名称 描述 默认值) option(ENABLE_UART 启用 UART 驱动 ON) option(ENABLE_SPI 启用 SPI 驱动 OFF) option(ENABLE_ADC 启用 ADC 驱动 OFF) option(DEBUG_TRACE 开启调试日志 OFF) # 用户可以通过 -D 覆盖 # cmake -DENABLE_ADCON ... → ADC 开启 # cmake -DDEBUG_TRACEON ... → 调试日志开启 为什么用 option 而不是裸 set① 语义清晰一看就是开关② GUI 显示复选框 ③与 if() 配合做条件编译第 10 课——option 定义开关、if 使用开关黄金组合。️ 嵌入式实战用 option 控制 HAL 外设编译这是 LKS32 项目最实用的模式——按需编译外设驱动省 Flash第 53 课完整实战#外设开关用户可通过-D 控制option(ENABLE_GPIOGPIO 驱动ON)# 默认开必备option(ENABLE_UARTUART 驱动ON)# 默认开调试用option(ENABLE_SPISPI 驱动OFF)# 默认关省 Flashoption(ENABLE_I2CI2C 驱动OFF)# 默认关option(ENABLE_ADCADC 驱动OFF)# 默认关 #宏定义传给编译器if(ENABLE_SPI)add_compile_definitions(ENABLE_SPI)list(APPEND HAL_SOURCES hal_spi.c)# 第13课 list 操作endif() 效果默认配置只编译 GPIOUART——Flash 占用从 5KB 降到 1.28KB省 75%。要用 SPI 时只需 cmake -DENABLE_SPION无需改代码。这就是配置驱动构建的落地。 $ENV{}——读取系统环境变量除了 CMake 变量你还可以用$ENV{变量名}读取系统环境变量PATH、HOME 等# 读取环境变量 message(STATUS HOME $ENV{HOME}) message(STATUS PATH $ENV{PATH}) # 判断环境变量是否存在 if(DEFINED ENV{JLINK_PATH}) message(STATUS 找到 J-Link: $ENV{JLINK_PATH}) endif() # 设置环境变量仅本次 configure 有效 set(ENV{MY_VAR} hello) # 注意$ENV{} 是环境变量${} 是CMake 变量——别混 什么时候用ENV①读取编译器路径ENV{}① 读取编译器路径ENV①读取编译器路径ENV{PATH} 排查② 判断 CI 环境$ENV{GITHUB_ACTIONS}③ 读取用户配置。但优先用 CACHE 变量而不是环境变量——CACHE 更显式、可追踪第 03 课可复现原则。⚖️ option() vs set(CACHE BOOL)——什么时候用哪个对比option()set(CACHE BOOL)本质set(CACHE BOOL) 的语法糖完整写法可读性✅ 更高一看是开关一般GUI 显示复选框复选框推荐场景✅ 新定义开关需要更多控制时 本页小结option() 定义构建开关的最优雅方式$ENV{} 读取系统环境的通道。两者配合 if() 和 list()就构成了配置驱动构建的完整工具箱。 一句话总览option() 定义开关、$ENV{} 读环境、if() 用开关——三者配合让同一个 CMake 工程能编译出不同配置的固件。这是模块化 可裁剪的基础。️ option() 的完整家族——与 set(CACHE BOOL) 对照# 写法一option()推荐option(ENABLE_GPIOGPIO 驱动ON)# 写法二set(CACHE BOOL)等价set(ENABLE_GPIO ON CACHE BOOLGPIO 驱动)# 差异option 若变量已定义则不覆盖更安全 # 所以命令行-DENABLE_GPIOOFF 后CMakeLists 里option()不会把它改回 ON 这个差异很重要option() 有个安全特性如果变量已经存在如命令行 -D 设置过option() 不会覆盖它。而 set(CACHE) 的行为不同。实战中定义开关用 option()命令行覆盖它——这正是配置驱动的精髓。 $ENV{} 实战——读取 CI 环境判断# GitHub Actions 会设置 GITHUB_ACTIONStrue第 55 课 if(DEFINED ENV{GITHUB_ACTIONS}) message(STATUS 在 CI 环境构建) # CI 里开严格警告 add_compile_options(-Werror) else() message(STATUS 本地构建) endif() # 读取 Jenkins 构建号 if(DEFINED ENV{BUILD_NUMBER}) set(FW_VERSION 1.2.${ENV{BUILD_NUMBER}}) message(STATUS 固件版本: ${FW_VERSION}) endif() 为什么用 $ENV{} 判断 CICI 服务器通过环境变量传递我是谁——不用改代码同一个 CMakeLists 在本地和 CI 表现不同。这是一份配置多处构建的经典场景。 本课一句话option() 定义开关$ENV{} 读环境if() 用开关——三者配合一个 CMake 工程编译出无数种配置。 实战预告本课的 option $ENV{} if() 组合正是第 53 课裁剪 HAL 驱动省 Flash的核心武器——现在学的每个知识点后面都有用武之地。