调试CMake:message()调试、--trace选项、CMakePresets.json、CMakeGraphViz
调试CMake,说实话,是很多嵌入式开发者容易忽略的一环。大家总觉得CMake只是个构建工具,写错了大不了重来。但我在实际项目中吃过不少亏——有一次一个复杂的交叉编译配置,折腾了两天才发现是变量作用域的问题。从那以后,我养成了几个调试习惯,今天分享给你。
message():最朴素的调试利器
message() 是CMake里最直接的调试手段。说白了,就是打印信息到终端。你别小看它,我80%的CMake问题都是靠它定位的。
# 普通信息
message("当前编译器是:${CMAKE_C_COMPILER}")
# 带状态的信息
message(STATUS "找到库文件:${LIB_PATH}")
# 警告信息
message(WARNING "这个配置可能有问题,请检查")
# 致命错误,会终止构建
message(FATAL_ERROR "缺少必要的依赖库!")
我个人习惯在关键变量赋值后立即打印一下,看看值对不对。比如这样:
set(MCU_FAMILY "STM32F4")
message(STATUS "MCU_FAMILY = ${MCU_FAMILY}")
# 检查变量是否存在
if(NOT DEFINED MCU_FAMILY)
message(FATAL_ERROR "MCU_FAMILY 未定义!")
endif()
--trace 选项:追踪每一行执行
有时候 message() 不够用,尤其是变量莫名其妙被覆盖的时候。这时候 --trace 就派上用场了。
用法很简单:
cmake --trace -B build
它会打印出CMake执行的每一行代码,以及调用栈。我曾经用这个选项抓到一个bug——一个子目录的CMakeLists.txt里不小心重定义了全局变量,导致整个构建链都乱了。
如果你觉得输出太多,可以用 --trace-source=文件名 来过滤:
cmake --trace-source=CMakeLists.txt -B build
嗯,这里要注意:--trace 会大幅降低构建速度,所以只在调试时用。我一般先加 message(),解决不了再上 --trace。
CMakePresets.json:标准化配置
CMakePresets.json 是CMake 3.19引入的功能,用来统一团队的构建配置。你想想看,以前每个人都要手动传一堆 -D 参数,还容易传错。有了 presets,一切都标准化了。
一个典型的嵌入式项目 presets 长这样:
{
"version": 3,
"configurePresets": [
{
"name": "stm32-debug",
"displayName": "STM32 Debug 配置",
"description": "用于 STM32F4 开发板的调试构建",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/stm32-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/arm-gcc-toolchain.cmake",
"MCU_FAMILY": "STM32F4"
}
},
{
"name": "stm32-release",
"displayName": "STM32 Release 配置",
"description": "用于 STM32F4 开发板的发布构建",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/stm32-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/arm-gcc-toolchain.cmake",
"MCU_FAMILY": "STM32F4",
"ENABLE_OPTIMIZATION": "ON"
}
}
],
"buildPresets": [
{
"name": "stm32-debug",
"configurePreset": "stm32-debug"
},
{
"name": "stm32-release",
"configurePreset": "stm32-release"
}
]
}
使用起来也很简单:
# 配置
cmake --preset stm32-debug
# 构建
cmake --build --preset stm32-debug
CMakeGraphViz:可视化依赖关系
当项目变得庞大,模块之间的依赖关系会越来越复杂。CMakeGraphViz 可以生成依赖图,帮你理清头绪。
使用方法:
cmake --graphviz=graph.dot -B build
dot -Tpng graph.dot -o graph.png
这会生成一个 DOT 格式的文件,然后用 Graphviz 工具转换成图片。我一般用它来检查库之间的依赖是否合理,有没有循环依赖。
下面是我用 CMakeGraphViz 生成的一个典型嵌入式项目依赖图:
从这张图可以清楚看到:firmware.elf 依赖 hal_driver、freertos 和 lwip,而这些库又依赖底层的 cmsis_core、startup 和 linker_script。如果出现循环依赖,比如 hal_driver 依赖 freertos,freertos 又依赖 hal_driver,CMake 会报错,这时候 CMakeGraphViz 就能帮你快速定位。
综合调试策略
在实际项目中,我通常按这个顺序调试:
- 先用 message() 打印关键变量和路径,解决80%的问题
- 再用 --trace 追踪变量被修改的位置,解决15%的问题
- 最后用 CMakeGraphViz 检查依赖关系,解决剩下的5%
- 用 CMakePresets.json 固化正确的配置,防止问题再次出现
这套组合拳下来,大部分CMake问题都能在10分钟内定位。我曾经用这个方法帮同事解决过一个困扰他两天的链接错误——最后发现是 toolchain 文件里少定义了一个宏。
调试CMake其实没那么玄乎。掌握这几个工具,你也能像我一样,遇到问题不慌不忙,一步步定位解决。记住:message() 是你的第一道防线,--trace 是你的显微镜,CMakeGraphViz 是你的地图,而 CMakePresets.json 是你的保险箱。
公众号:蓝海资料掘金营,微信deep3321