INAV CMake 构建系统详解:CMAKE_BUILD_TYPE、按 MCU 优化策略、产物文件与磁盘占用剖析
发布时间:2026/10/13 15:00:58 锦皓数字建站

无人机嵌入式智能硬件【免费下载链接】inavINAV: Navigation-enabled flight control software项目地址https://gitcode.com/gh_mirrors/in/inav点击查看免费下载本文基于 INAV 仓库的 构建系统参考文档系统梳理 INAV 固件构建的三个 CMake 构建类型Debug / Release / RelWithDebInfo、按 MCU 家族区分的优化级别与 LTO 开关、.elf/.map/.hex/.bin四类产物的生成链路以及全量构建约 200 余个硬件目标时磁盘占用高达数十 GB 的根因分析与工程应对方案。读完本文你可以准确控制 INAV 固件的构建类型与优化级别正确产出可用于崩溃地址回查的固件配套文件并在 CI 或本地批量构建时显著节省磁盘与构建时间。一、三种 CMAKE_BUILD_TYPE 及其语义INAV 的构建类型定义在交叉编译工具链文件 cmake/arm-none-eabi.cmake 中支持以下三种Build Type编译参数调试符号优化级别LTO典型用途Debug-Og -g有-g最低-Og关闭日常开发、GDB 调试Release-DNDEBUG无按目标-O2/-Os开启正式发布RelWithDebInfo-ggdb3 -DNDEBUG有-ggdb3按目标-O2/-Os开启默认——优化且可调试三种类型的取舍逻辑Debug-Og级别优化、不定义NDEBUG断言保持开启、仅基础调试符号、不启用 LTO。产物体积最大但编译与链接最快调试信息对 GDB 最友好。Release应用目标相关的-O2或-Os优化、定义NDEBUG关闭断言、不带任何调试符号、启用 LTO。产物最小但 LTO 带来的跨模块优化使构建耗时最长。RelWithDebInfo默认优化级别与 LTO 与 Release 完全一致额外追加-ggdb3生成完整的 DWARF 调试信息——即可以放进飞行器的优化固件同时还能挂 GDB 调试。值得注意的一个细节NDEBUG是 Release 与 RelWithDebInfo 共有的也就是说默认的 RelWithDebInfo 构建中assert()是不生效的这与很多人带 Debug 就带断言的直觉不同。二、CMAKE_BUILD_TYPE 是如何被设定的默认值RelWithDebInfocmake/arm-none-eabi.cmake 在工具链初始化阶段写入了默认值逻辑第 2731 行if(CMAKE_BUILD_TYPE STREQUAL ) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif() set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING Build Type FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${CMAKE_CONFIGURATION_TYPES})即如果你在 configure 时不指定构建类型CMake 会强制把缓存值设为RelWithDebInfo。Docker 构建入口 cmake/docker.sh 中只有cmake -GNinja ..这一行初始化命令未携带任何-DCMAKE_BUILD_TYPESITL 构建脚本同样如此。因此常规的本地构建、Docker 构建、CI 构建全部落在 RelWithDebInfo 上。显式覆盖构建类型cd build cmake -DCMAKE_BUILD_TYPEDebug .. # 或 Release、RelWithDebInfo make MATEKF405一个常见误解后文介绍的make release目标并不控制优化级别——它只是构建被标记为可发布的那批硬件目标所用的仍是当前 build 目录已经配置好的CMAKE_BUILD_TYPE。想换优化级别必须改CMAKE_BUILD_TYPE本身。编译参数如何落到 C/C/ASM 三套 FLAGS 上cmake/arm-none-eabi.cmake 第 3348 行把三种类型分别映射到标准 CMake 变量上set(arm_none_eabi_debug -Og -g) # We set -Os or -O2 depending on the MCU family set(arm_none_eabi_release -DNDEBUG) set(arm_none_eabi_relwithdebinfo -ggdb3 ${arm_none_eabi_release})随后写入CMAKE_C_FLAGS_DEBUG/CMAKE_CXX_FLAGS_*/CMAKE_ASM_FLAGS_*等 CACHE INTERNAL 变量Release 与 RelWithDebInfo 分别对应第 4248 行。注意arm_none_eabi_release只有-DNDEBUG——优化级别-O2/-Os不写在这里而是由每个 MCU 目标单独注入见下节。三、IS_RELEASE_BUILD 与按 MCU 家族的优化级别优化参数只在发布型构建中生效主 CMakeLists.txt 第 7274 行定义了全局开关if(CMAKE_BUILD_TYPE STREQUAL Release OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) set(IS_RELEASE_BUILD ON) endif()在 STM32 目标构造函数 cmake/stm32.cmake 的add_stm32_executable()中第 257260 行目标级优化参数仅在IS_RELEASE_BUILD时同时作用于编译和链接两阶段if (IS_RELEASE_BUILD) target_compile_options(${elf_target} PRIVATE ${args_OPTIMIZATION}) target_link_options(${elf_target} PRIVATE ${args_OPTIMIZATION}) endif()也就是说Debug 构建完全拿不到任何目标级优化参数只享受工具链层默认的-Og -g。AT32 家族走平行的add_at32_executable()逻辑完全一致见 cmake/at32.cmake 第 249250 行。各 MCU 家族的优化级别一览MCU 家族优化级别定义位置STM32F4多数目标-O2cmake/stm32f4.cmake 第 95 行OPTIMIZATION -O2STM32F411-Oscmake/stm32f4.cmake 第 130 行512KB flash空间受限STM32F7-O2或-Oscmake/stm32f7.cmake按 flash 容量数值判断STM32H7-O2cmake/stm32h7.cmake 第 193 行AT32F4-O2cmake/at32f4.cmake 第 69 行F7 的选择逻辑特别值得注意——它不是查 MCU 字母后缀而是对 flash 容量做数值比较cmake/stm32f7.cmake 的define_target_stm32f7宏第 124151 行macro(define_target_stm32f7 subfamily size ram) function(target_stm32f7${subfamily}x${size} name) ... get_stm32_flash_size(flash_size ${size}) if(flash_size GREATER 512) set(opt -O2) # 大于 512KB flash——按速度优化 else() set(opt -Os) # 512KB 及以下——按体积优化 endif() ... target_stm32f7xx( NAME ${name} ... OPTIMIZATION ${opt} ) endfunction() endmacro() define_target_stm32f7(22 e 256) # F722512KB → -Os define_target_stm32f7(45 g 320) # F7451MB → -O2 define_target_stm32f7(46 g 320) define_target_stm32f7(65 g 512) define_target_stm32f7(65 i 512) # F7652MB → -O2其中get_stm32_flash_size()cmake/stm32.cmake 第 142193 行负责把 STM32 命名中的容量字符E512、G1024、I2048 KiB 等换算成数值。F411 的做法则更直接STM32F411_COMPILE_DEFINITIONS中直接携带OPTIMIZATION -Os并透传给目标函数cmake/stm32f4.cmake 第 127143 行。四、链接期优化LTO的启用条件LTO 在 cmake/main.cmake 的setup_executable()中统一启用第 97101 行对每个可执行目标生效if(IS_RELEASE_BUILD AND NOT (CMAKE_HOST_APPLE AND SITL)) set_target_properties(${exe} PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON ) endif()结论Release 与 RelWithDebInfo 均开启 LTOCMake 的INTERPROCEDURAL_OPTIMIZATION ON会为编译与链接自动追加-fltoDebug 关闭 LTO——这也是 Debug构建最快的原因之一唯一例外是macOS 上的 SITL宿主模拟构建从源码结构看该条件是对 macOS 宿主工具链 LTO 兼容性问题的规避。SITL 构建通过顶层-DSITLON切换host工具链CMakeLists.txt 第 1323 行。五、构建产物.elf / .map / .hex / .bin以构建MATEKF405为例一次完整构建会产出四类文件各自的角色与生成方式如下。1. .elf——完整 ELF 可执行文件add_executable()的产物CMAKE_EXECUTABLE_SUFFIX在工具链文件里被设为.elf。其中包含可加载段以及 Debug/RelWithDebInfo 构建下的 DWARF 调试段.debug_info、.debug_line等和符号表。GDB 调试、arm-none-eabi-nm/objdump分析、以及生成下述.hex/.bin都依赖它。2. .map——链接器映射文件cmake/stm32.cmake 的generate_map_file()第 217224 行对每个 ELF 目标追加-Wl,-Map,${map}链接参数无论构建类型都会生成。它记录了内存布局、符号名与地址在没有完整.elf的情况下也能把崩溃地址映射回函数排障时非常有用。3. .hex / .bin——实际刷写的固件由add_hex_target()/add_bin_target()生成的自定义目标cmake/stm32.cmake 第 195215 行调用objcopy从.elf提取${CMAKE_OBJCOPY} -Oihex --set-start 0x08000000 target.elf out.hex ${CMAKE_OBJCOPY} -Obinary target.elf out.bin源码中保留了一段值得留意的注释add_hex_target()内--set-start 0x08000000目前是为了绕过 configurator 对 hex 起始地址的某些错误假设而强制写入的并标注了 TODO 待验证后移除。关键事实这四类文件中只有.hex/.bin的大小与构建类型无关。objcopy只提取可加载段.text/.rodata/.data/.bss而 DWARF 调试信息与符号表只存在于非加载段因此它们从一开始就不会进入.hex/.bin——不存在单独的剥离调试信息步骤是调试信息压根不在objcopy会拷贝的段里。发布建议发布固件时务必把.elf和.map与分发的.hex放在一起——用户报告的崩溃往往只有裸地址只有同一次构建产出的配套.elf/.map才能把它映射回源码行。另外开启 bootloader 的目标如各 F4、F7 目标传入BOOTLOADER参数会额外产出_blbootloader 固件、_for_bl配合 bootloader 运行版本以及由 src/utils/combine_tool 合并的_with_bl.hexcmake/stm32.cmake 第 386436 行这意味着同一硬件目标会编译 23 份 ELF后文磁盘占用的估算需要把这一点考虑进去。六、如何从一个产物判断它是什么构建类型判定方法适用对象可靠度用arm-none-eabi-readelf -S检查.elf中的.debug_*段仅.elf高——可区分 Debug/RelWithDebInfo 与 Release对比.elf文件大小仅.elf中——只能粗略估计搜索断言相关字符串任意产物中——只能区分 Debug 与 Release/RelWithDebInfo两者共用了NDEBUGgrep CMAKE_BUILD_TYPE build/CMakeCache.txtbuild 目录高——只要还在原 build 目录就是精确值七、为什么全量构建所有目标会吃掉几十 GB 磁盘这是该参考文档最核心的结论用默认的 RelWithDebInfo 配置全量构建全部硬件目标仓库src/main/target/下目前有约 259 个目标目录build 目录会膨胀到数十 GB量级尽管每个目标最终的.hex/.bin只有几百 KB。根因一-ggdb3让中间 .o 文件暴增文档中给出了一个用本仓库同款工具链独立复现的最小实验针对单个含若干数组的小翻译单元$ arm-none-eabi-gcc -c -ggdb3 -Os test.c -o test_debug.o # 带结构体数组的源文件 19776 bytes $ arm-none-eabi-gcc -c -Os test.c -o test_release.o # 同一文件去掉 -ggdb3 752 bytes仅-ggdb3一项就带来约26 倍的 .o 体积差异。原因DWARF 调试信息行号表、类型信息、完整符号数据相对于实际生成的机器码非常大尤其在-Os/-O2把代码本身压缩得很小的情况下调试信息占比更加悬殊。把这个比例放大到每目标上千个源文件、再乘以约百个量级的目标数RelWithDebInfo 的中间目标文件自然成为磁盘大头。这不影响最终固件——如第五节所述.hex/.bin只含可加载段.o/.elf里多出来的调试数据根本不会到达刷写产物。根因二目标之间完全不共享目标文件从 cmake/stm32.cmake 的target_stm32()可以看到每个目标各自调用一次add_stm32_executable()→add_executable()独立编译整棵源码树且带有各自的-DTARGET宏与优化参数target_compile_definitions()会注入__TARGET__${name} ${name}见 cmake/main.cmake 第 89 行。即使大多数目标共享绝大部分代码公共驱动、HAL、飞控逻辑这些共享代码仍会被每个目标各编译一遍。CMake 没有内建机制让两个编译参数不同的可执行目标共享 .o 文件——这是当前每目标一个add_executable()结构的固有特性而非缺陷。工程应对方案文档给出了四条可落地的策略批量构建改用 Release。不需要调试产物的全量构建例如 CI、验证所有目标还能编译通过应使用cmake -DCMAKE_BUILD_TYPERelease ..注意这改变的是整个 build 目录的构建类型无法在同一配置下让部分目标走 Release、部分走 RelWithDebInfo。构建成功后删除中间 .o 文件只保留最终固件时find build -name *.o -delete代价是后续如需重新链接必须全量重编。此外 CMake 侧提供了更精细的clean_target目标cmake/main.cmake 第 136163 行可单独移除某个固件目标的 ELF、map 与对象文件。只构建需要的目标。文档建议通过-DTARGETSMATEKF405SE,SITL之类的变量收敛目标列表不过需要说明检索当前仓库的顶层 CMakeLists.txt 与cmake/目录未发现对TARGETS变量的解析逻辑从源码结构看该选项可能是规划中的能力或依赖特定构建环境使用前建议先以make targetscollect_targets()注册的列表目标会打印全部有效目标与可发布目标确认可构建清单。ccache 能显著加速重编但不减少 build 目录体积——它的缓存独立存放在别处磁盘大头仍是本次构建的 .o 文件。文档还指出把共享代码重构为 CMakeOBJECT库编译一次、链入各目标可以在 RelWithDebInfo 下大幅降低磁盘占用但鉴于各目标现有的差异化优化参数与预处理宏这是一项非平凡的重构目前尚未实施。八、make release目标的真实语义与命名陷阱最后澄清一个高频误解。collect_targets()cmake/main.cmake 第 165194 行会遍历所有目标把未标记SKIP_RELEASES的目标收集进release_targets并注册一个名为release的自定义目标set(release_target_name release) add_custom_target(${release_target_name} ${CMAKE_COMMAND} -E true DEPENDS ${release_targets} )它做的事情仅仅是构建这批可发布硬件目标完全不改变CMAKE_BUILD_TYPE。SKIP_RELEASES标记由setup_firmware_target()解析cmake/main.cmake 第 115118 行。由此引出一个命名陷阱如果你想新增一个全部按 Release 模式重编的便捷目标不能再叫release——同名目标会直接导致 CMake configure 阶段失败。需要另起名字例如release-slim。顺带一提CI 环境还依赖同一套RELEASE_TARGETS全局属性做分片构建cmake/ci.cmake 在CI_JOB_INDEX/CI_JOB_COUNT环境下把目标列表均分到各并行 job每份构建一个名为ci的聚合目标——这解释了为何release 目标清单在 CI 与本地构建中是同一个概念。小结不指定CMAKE_BUILD_TYPE时INAV 一律构建RelWithDebInfo-ggdb3 -DNDEBUG 目标级-O2/-Os LTO目标级优化参数只在 Release/RelWithDebInfo 下注入F411 与 ≤512KB flash 的 F7 用-Os其余用-O2.hex/.bin大小与构建类型无关发布时应保留配套.elf/.map用于崩溃回查全量构建的高磁盘占用源于-ggdb3放大的中间 .o 与目标间零共享可用 Release 构建、删除 .o、收敛目标列表、ccache 四种手段缓解make release只决定构建哪些目标不决定以什么优化级别构建。赞分享无人机嵌入式智能硬件【免费下载链接】inavINAV: Navigation-enabled flight control software项目地址https://gitcode.com/gh_mirrors/in/inav点击查看免费下载相关推荐CMake磁盘空间优化终极指南构建产物清理与空间管理技巧CMake磁盘空间优化终极指南构建产物清理与空间管理技巧 CMake作为现代C项目的主流构建工具在开发过程中会产生大量的构建产物这些文件往往占用大量磁示例工程教程构建工具moldIO剖析磁盘IO模式和优化策略moldIO剖析磁盘IO模式和优化策略 引言链接器性能瓶颈的IO视角 在现代软件开发中构建时间Build Time直接影响开发者的生产效率。传统链接器开发工具构建工具系统编程3分钟学会本地部署mjai-reviewer从安装到生成首份复盘报告3分钟学会本地部署mjai reviewer从安装到生成首份复盘报告 mjai reviewer是一款强大的麻将牌局复盘工具能够通过mjai兼容的麻将AI引后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。