调试与日志:message()的多种用法,CMake --trace,--debug-output,排查构建问题

调试CMake构建,说白了就是搞清楚「到底发生了什么」。

我刚开始用CMake那会儿,经常遇到莫名其妙的错误——变量没传过去、路径不对、条件分支走错了。那时候我只会加一堆message()打印,后来才发现,CMake自己就带了一套很完整的调试工具。

这一章,我把这些工具掰开揉碎了讲给你听。

message():你的第一把调试利器

message()是CMake里最常用的调试命令。它的用法其实比大多数人想象的要丰富。

基本用法

message("Hello, CMake!")

这行代码会在配置阶段打印一行信息。注意,是配置阶段,不是编译阶段。很多新手搞混这一点。

消息类型

message()支持多种消息类型,每种类型的行为不同:

类型 行为 典型场景
STATUS 打印信息,前缀为 -- 报告进度、状态
WARNING 打印警告,继续执行 提醒用户注意
AUTHOR_WARNING 类似WARNING,但可被抑制 开发者提示
SEND_ERROR 打印错误,继续执行但跳过生成 严重问题
FATAL_ERROR 打印错误,立即终止 致命错误
DEPRECATION 弃用警告 标记过时用法
message(STATUS "找到库文件: ${LIB_PATH}")
message(WARNING "这个选项已经弃用,请使用新接口")
message(FATAL_ERROR "必须指定 CMAKE_INSTALL_PREFIX")
我的习惯:开发阶段多用STATUS和WARNING,发布前把调试信息清理干净。FATAL_ERROR只在「没有这个参数程序就完全跑不起来」的时候用。

变量展开与格式化

message()会自动展开变量引用:

set(MY_VAR "hello")
message("MY_VAR = ${MY_VAR}")

如果你想看变量名本身,而不是它的值,用引号包起来:

message("${MY_VAR}")   # 输出 hello
message("\${MY_VAR}")  # 输出 ${MY_VAR}

嗯,这个转义技巧我当年找了好久才找到。

CMake --trace:追踪每一行执行

message()需要你手动插入代码。但有时候你根本不知道问题出在哪一行,这时候就需要--trace了。

基本用法

cmake --trace .

这个命令会打印CMakeLists.txt中每一行代码的执行情况。输出格式类似:

/path/to/CMakeLists.txt:10: set(MY_VAR "hello")
/path/to/CMakeLists.txt:12: message("MY_VAR = hello")

每一行都告诉你:哪个文件的哪一行,执行了什么命令。

--trace-expand:展开变量

--trace只打印原始代码行。如果你想看变量展开后的结果,用--trace-expand:

cmake --trace-expand .

输出会变成:

/path/to/CMakeLists.txt:12: message("MY_VAR = hello")

注意看,${MY_VAR}已经被替换成了hello。这在排查变量传递问题时特别有用。

我曾经踩过的坑:有一次项目构建失败,我查了半天,最后用--trace-expand才发现,有个变量名拼写错了——LIBRARY写成了LIBRARYY。这种错误message()根本看不出来,但--trace-expand一跑,变量展开后是空的,问题一目了然。

--trace-source:只看特定文件

大型项目有成百上千个CMake文件,全部trace输出量太大。你可以指定只看某个文件:

cmake --trace-source=CMakeLists.txt .

也可以指定行数范围:

cmake --trace-source=CMakeLists.txt:10:20 .

--debug-output:更详细的内部信息

--trace关注的是CMake代码的执行。--debug-output则关注CMake内部的运行状态。

cmake --debug-output .

它会输出:

  • 查找库和头文件的详细过程
  • 缓存变量的读写操作
  • 策略(policy)的设置和检查
  • 生成器的选择过程

说白了,--debug-output告诉你CMake「在想什么」。

--debug-trycompile

这个选项专门用来调试try_compile和try_run。当你的检查编译失败时,加上它:

cmake --debug-trycompile .

它会保留临时生成的测试项目,不会自动删除。你可以去构建目录里找到这些测试代码,手动编译看看哪里出了问题。

排查构建问题的实战流程

遇到构建问题,我一般按这个顺序排查:

  1. 先看错误信息——CMake的错误信息其实很明确,很多人不看就慌了
  2. 加message()——在关键位置打印变量值,确认数据流
  3. 用--trace-expand——如果message()不够,trace能帮你找到「哪一行出了问题」
  4. 用--debug-output——当问题涉及查找库、策略、生成器时,这个最管用
  5. 清理构建目录——有时候缓存脏了,重新配置一次就好了
核心原则:先定位问题范围,再选择工具。不要一上来就开--trace,输出量太大反而找不到重点。

知识体系图

下面这张图总结了本章的核心内容:

CMake调试与日志工具体系 message() • STATUS / WARNING • SEND_ERROR / FATAL_ERROR • DEPRECATION • 变量展开与转义 手动插入,精准定位 --trace 系列 • --trace 逐行追踪 • --trace-expand 展开变量 • --trace-source 限定文件 • --trace-format 格式化输出 自动追踪,无死角 --debug 系列 • --debug-output 内部状态 • --debug-trycompile • 保留临时测试项目 • 查看查找库过程 深入内部,诊断疑难 排查流程 看错误信息 加 message() --trace-expand --debug-output 最后一步:清理构建目录,重新配置

几个实用技巧

条件打印

有时候你只想在特定条件下打印信息:

if(CMAKE_VERBOSE_MAKEFILE)
  message(STATUS "详细模式: 正在处理 ${CURRENT_TARGET}")
endif()

这样,只有用户开启了详细模式,才会输出。不会污染正常的构建输出。

打印列表变量

列表变量用message()直接打印会连在一起,不好看。我习惯这样:

foreach(item IN LISTS MY_LIST)
  message(STATUS "  - ${item}")
endforeach()

结合变量文件

调试复杂问题时,我会把关键变量导出到文件:

file(WRITE "${CMAKE_BINARY_DIR}/debug_vars.txt"
  "CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}\n"
  "CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}\n"
  "MY_VAR = ${MY_VAR}\n"
)

这样可以在构建完成后慢慢看,不用在终端里翻来翻去。

我的建议:把调试信息输出到文件,配合less或grep查看,效率比在终端里翻页高得多。

调试CMake构建,说白了就是「让CMake告诉你它看到了什么」。message()是手动问,--trace是自动录,--debug-output是深度诊断。三种工具配合使用,大部分问题都能快速定位。

嗯,这一章的内容就到这里。记住,遇到问题先别慌,CMake的调试工具比你想象的要强大。


公众号:蓝海资料掘金营,微信deep3321