目录
1.简介
2.用 message() 输出关键信息
2.1.message简介
2.2.常用模式及作用
2.3.核心用法示例
2.4.常见问题及解决
3.查看缓存变量:cmake -L 与缓存文件
3.1.列出所有缓存变量(cmake -L)
3.2.直接查看 / 删除 CMakeCache.txt
4.变量追踪与作用域
4.1.CMAKE_MESSAGE_CONTEXT (CMake 3.25+)
4.2.作用域问题
4.3.监控变量变化:variable_watch()
4.4.属性获取
4.4.1.获取 CMake 全局属性(get_cmake_property())
4.4.2.查询目标属性(get_target_property())
4.4.3.批量打印属性(cmake_print_properties())
4.4.4.输出到文件(file(WRITE))
5.详细跟踪 CMake 执行流程:--debug-output 与 --trace
5.1.--debug-output:输出调试级信息
5.2.--trace:跟踪所有执行的命令(最详细)
5.3.--warn-uninitialized
5.4.--graphviz= (CMake 2.8.10+)
6.调试 find_package 依赖查找失败
7.使用 CMake GUI / ccmake
8.调试工具链文件 (CMAKE_TOOLCHAIN_FILE)
9.检查编译器 / 平台兼容性:日志文件
10.验证条件判断逻辑
11.其他实用技巧
12.总结
相关链接
1.简介
在 CMake 脚本开发中,由于其语法特性(如变量作用域、缓存机制、条件逻辑)和跨平台配置的复杂性,经常需要调试来定位问题(如变量值异常、依赖找不到、条件分支错误等)。下面就一些常见的调试方法介绍介绍。
2.用 message()
输出关键信息
2.1.message简介
message()
是用于输出信息到控制台的核心命令,主要用于调试配置过程、反馈状态或提示错误,是跟踪 CMake 脚本执行的重要工具。
基本语法:
message([<模式>] "消息内容")
- 模式(可选):指定消息级别,控制输出样式和影响(如是否中断执行)。
- 消息内容:可包含文本、变量(用
${变量名}
引用)、表达式或列表。
2.2.常用模式及作用
CMake 通过模式区分消息的重要性,常用模式如下:
模式 | 作用与特点 |
---|---|
STATUS | 最常用,用于配置过程的状态提示(如变量值、路径信息)。输出时会自动添加缩进,与 CMake 原生输出风格一致(推荐优先使用)。 |
WARNING | 警告信息,以醒目样式显示(通常为黄色),但不中断 CMake 执行(如提示过时用法、潜在问题)。 |
SEND_ERROR | 错误信息,会继续执行后续脚本,但最终标记构建失败(用于非致命错误,如可选依赖缺失)。 |
FATAL_ERROR | 致命错误,立即中断 CMake 执行(用于关键依赖缺失、无效配置等必须解决的问题)。 |
无模式 | 默认模式,输出普通文本(不推荐,风格与 CMake 原生输出不一致,易混淆)。 |
DEPRECATION | 过时提示,仅在开发者模式(-Wdev )下显示(用于标记即将废弃的功能)。 |
2.3.核心用法示例
1.输出状态信息(STATUS
)
用于反馈配置过程中的关键信息(如变量值、路径、条件分支):
# 输出普通变量
set(MY_VAR "test")
message(STATUS "MY_VAR = ${MY_VAR}") # 输出:-- MY_VAR = test# 输出缓存变量(如 find_package 结果)
find_package(OpenSSL)
message(STATUS "OpenSSL_FOUND = ${OpenSSL_FOUND}") # 检查依赖是否找到
message(STATUS "OpenSSL_INCLUDE_DIRS = ${OpenSSL_INCLUDE_DIRS}") # 路径是否正确# 输出内置变量(如路径、编译器信息)
message(STATUS "CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}") # 源码根目录
message(STATUS "CMAKE_CXX_COMPILER = ${CMAKE_CXX_COMPILER}") # 编译器路径
2.调试变量与列表
变量引用需用 ${}
,否则会被当作字符串:
set(MY_VAR "hello")
message(STATUS "变量值: ${MY_VAR}") # 正确:输出 "变量值: hello"
# message(STATUS "变量值: MY_VAR") # 错误:输出 "变量值: MY_VAR"
列表默认用分号分隔,可通过 string(JOIN)
格式化输出:
set(MYLIST "a" "b" "c") # CMake 列表(内部存储为 "a;b;c")
string(JOIN ", " LIST_STR "${MYLIST}") # 转换为 "a, b, c"
message(STATUS "列表内容: ${LIST_STR}") # 输出:列表内容: a, b, c
3.跟踪条件分支执行
当 if()
分支逻辑不符合预期时,在分支内输出标记,确认是否进入目标分支:
option(ENABLE_FEATURE "启用功能" OFF)if(ENABLE_FEATURE)message(STATUS "进入 ENABLE_FEATURE 分支") # 若未输出,说明条件不成立# ... 功能代码 ...
else()message(STATUS "进入 ELSE 分支(功能未启用)") # 确认是否走了默认分支
endif()# 复杂条件判断(如版本比较)
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "11.0")message(STATUS "编译器版本 > 11.0")
else()message(STATUS "编译器版本 <= 11.0(当前: ${CMAKE_CXX_COMPILER_VERSION})")
endif()
4.错误与警告提示
警告(WARNING
):提示潜在问题但不中断执行:
if(CMAKE_VERSION VERSION_LESS "3.10")message(WARNING "CMake 版本过低,部分功能可能受限(推荐 >=3.10)")
endif()
致命错误(FATAL_ERROR
):关键问题必须解决时中断执行:
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/src/main.cpp")message(FATAL_ERROR "未找到核心源文件 src/main.cpp,请检查源码完整性!")
endif()
2.4.常见问题及解决
- 优先使用
STATUS
模式:保持输出风格与 CMake 一致,避免混乱。 - 变量未展开:忘记用
${}
引用变量,导致输出变量名而非值(如message(STATUS "VAR: MY_VAR")
应改为message(STATUS "VAR: ${MY_VAR}")
)。 - 列表格式混乱:CMake 列表默认用分号分隔,可通过
string(JOIN ", " 新变量 原列表)
转换为可读性更高的格式。 - 消息不显示:模式级别过高(如
DEPRECATION
默认不显示)或 CMake 以静默模式运行(如加了-Wno-dev
),改用STATUS
或WARNING
模式即可。
3.查看缓存变量:cmake -L
与缓存文件
CMake 会将关键变量(如 option
、find_package
结果、路径配置)存储在 CMakeCache.txt 中(构建目录下),这些变量可能被缓存而不更新,导致配置异常。
3.1.列出所有缓存变量(cmake -L
)
在构建目录执行以下命令,可列出所有缓存变量及其值(快速确认变量是否被正确设置):
# 列出所有非高级缓存变量(常用)
cmake -L .# 列出所有缓存变量(包括高级变量,如编译器细节)
cmake -LA .# 搜索特定变量(结合 grep)
cmake -LA . | grep "OpenSSL" # 查找与 OpenSSL 相关的缓存变量
示例输出(部分):
ENABLE_FEATURE:BOOL=OFF
OpenSSL_FOUND:BOOL=ON
OpenSSL_INCLUDE_DIRS:PATH=/usr/include/openssl
...
3.2.直接查看 / 删除 CMakeCache.txt
若怀疑旧缓存影响配置(如修改 option
后值未更新),可:
- 直接打开构建目录下的
CMakeCache.txt
,搜索目标变量(如ENABLE_FEATURE
),查看其实际值; - 删除
CMakeCache.txt
或整个构建目录(rm -rf build && mkdir build && cd build
),重新配置(避免缓存干扰)。
4.变量追踪与作用域
4.1.CMAKE_MESSAGE_CONTEXT
(CMake 3.25+)
-
设置一个上下文字符串,该字符串会自动添加到当前目录及所有子目录中所有后续
message()
调用的输出中。 -
非常适合在大型项目或递归结构中追踪消息来源。
list(APPEND CMAKE_MESSAGE_CONTEXT "MyModule")
message(STATUS "Configuring MyModule...") # 输出: [MyModule] -- Configuring MyModule...
list(POP_BACK CMAKE_MESSAGE_CONTEXT) # 退出当前上下文
4.2.作用域问题
-
set(... PARENT_SCOPE)
: 修改父作用域变量。 -
set(... CACHE ... FORCE)
: 强制修改缓存变量。 -
使用
message()
在不同位置(函数内/外、不同CMakeLists.txt
中)打印变量值,观察其变化,是诊断作用域问题的关键。
4.3.监控变量变化:variable_watch()
用于跟踪指定变量的读取、修改、删除操作,当变量发生这些行为时,CMake 会自动输出调试信息(包括操作类型、位置、新旧值等)。
用法:
# 监控单个变量
variable_watch(MY_VAR)# 监控多个变量
variable_watch(CMAKE_CXX_STANDARD)
variable_watch(FOO_BAR)
效果:当 MY_VAR
被读取(如 if(MY_VAR)
)、修改(如 set(MY_VAR 1)
)或删除(如 unset(MY_VAR)
)时,会输出类似:
Variable MY_VAR was modified at [...]/CMakeLists.txt:10 (set). Old value: "0", new value: "1"
4.4.属性获取
4.4.1.获取 CMake 全局属性(get_cmake_property())
用于查询 CMake 的全局属性(如已定义的变量列表、目标列表、目录列表等),结合 message()
可打印全局状态。
常用场景:
- 打印所有已定义的变量
- 打印所有已创建的目标(可执行文件、库)
- 打印所有包含的目录
示例:
# 打印所有已定义的变量
get_cmake_property(all_vars VARIABLES)
list(SORT all_vars) # 排序便于查看
message("All variables:\n${all_vars}")# 打印所有目标(可执行文件、库等)
get_cmake_property(all_targets BUILDSYSTEM_TARGETS)
message("All targets:\n${all_targets}")# 打印所有包含的目录
get_cmake_property(all_dirs SUBDIRECTORIES)
message("All subdirectories:\n${all_dirs}")
4.4.2.查询目标属性(get_target_property())
用于获取特定目标(如可执行文件、库)的属性(如包含目录、链接库、编译选项等),排查目标配置问题。
常用属性:INCLUDE_DIRECTORIES
(包含目录)、LINK_LIBRARIES
(链接库)、COMPILE_DEFINITIONS
(编译宏)、SOURCES
(源文件列表)等。
示例:
# 假设已创建目标 "my_app"
add_executable(my_app main.cpp)# 查看目标的包含目录
get_target_property(inc_dirs my_app INCLUDE_DIRECTORIES)
message("my_app include dirs: ${inc_dirs}")# 查看目标的链接库
get_target_property(link_libs my_app LINK_LIBRARIES)
message("my_app linked libs: ${link_libs}")# 查看目标的编译选项
get_target_property(compile_opts my_app COMPILE_OPTIONS)
message("my_app compile options: ${compile_opts}")
4.4.3.批量打印属性(cmake_print_properties())
用于批量打印指定类型(目标、源文件、目录等)的属性,比 get_target_property()
更高效。
支持的类型:TARGETS
、SOURCES
、DIRECTORIES
、TESTS
等。
示例:
# 打印目标 "my_app" 的所有属性
cmake_print_properties(TARGETS my_appPROPERTIES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS
)# 打印当前目录的属性(如 CMAKE_CURRENT_SOURCE_DIR)
cmake_print_properties(DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}PROPERTIES CMAKE_CURRENT_SOURCE_DIR CMAKE_CURRENT_BINARY_DIR
)
4.4.4.输出到文件(file(WRITE))
当调试信息过多(如大量变量或属性),控制台输出混乱时,可将信息写入文件查看。
示例:
# 将所有变量写入文件
get_cmake_property(all_vars VARIABLES)
list(SORT all_vars)
file(WRITE "${CMAKE_BINARY_DIR}/cmake_vars.txt" "All variables:\n${all_vars}")# 将目标属性写入文件
get_target_property(inc_dirs my_app INCLUDE_DIRECTORIES)
file(APPEND "${CMAKE_BINARY_DIR}/my_app_info.txt" "Include dirs: ${inc_dirs}\n")
5.详细跟踪 CMake 执行流程:--debug-output
与 --trace
5.1.--debug-output
:输出调试级信息
显示 CMake 内部的调试信息(如变量查找、缓存读取),但不显示所有命令:
cmake --debug-output .. # 在构建目录执行,.. 是源码目录
5.2.--trace
:跟踪所有执行的命令(最详细)
输出 每一行执行的 CMake 命令(包括 if
、set
、include
等),适合追踪脚本执行路径:
cmake --trace .. # 输出所有命令(可能非常多,建议重定向到文件)
cmake --trace .. > cmake_trace.log # 保存到文件,方便搜索
cmake --trace-expand .. > cmake_trace_expanded.log
-
启用后会打印 CMake 执行的每一行脚本,是终极调试手段(输出量巨大)。
-
cmake --trace .
: 基本跟踪。 -
cmake --trace-expand .
: 跟踪并展开所有变量。这是查看变量实际值如何被代入命令的最强方式。 -
可以指定跟踪范围
--trace-source=<file>
,--trace-redirect=<file>
。
5.3.--warn-uninitialized
-
警告使用了未显式初始化(未设置)的变量(非常有用!)。
-
cmake --warn-uninitialized .
5.4.
--graphviz=
(CMake 2.8.10+)
-
生成一个
.dot
文件,可视化显示目标之间的依赖关系图。 -
cmake --graphviz=graph.dot .
然后用 Graphviz 工具(如dot -Tpng graph.dot -o graph.png
)生成图片查看。
6.调试 find_package
依赖查找失败
find_package
找不到依赖是常见问题(如路径错误、版本不匹配),可通过以下方法定位:
1.启用查找调试模式(CMAKE_FIND_DEBUG_MODE
)
设置 CMAKE_FIND_DEBUG_MODE
为 ON
,CMake 会输出 find_package
查找依赖的 详细过程(搜索路径、检查的文件、匹配的版本等):
# 在 find_package 前设置(临时生效)
set(CMAKE_FIND_DEBUG_MODE ON)
find_package(SomeLib REQUIRED)
set(CMAKE_FIND_DEBUG_MODE OFF) # 用完关闭,避免输出过多
示例输出(关键部分):
CMAKE_FIND_DEBUG_MODE: FIND_PACKAGE(SomeLib)
CMAKE_FIND_DEBUG_MODE: Checking prefixes: /usr/local, /usr, ...
CMAKE_FIND_DEBUG_MODE: Looking for SomeLibConfig.cmake in .../lib/cmake/SomeLib
CMAKE_FIND_DEBUG_MODE: Found SomeLibConfig.cmake at /usr/lib/cmake/SomeLib
...
2.检查 SomeLib_DIR
缓存变量
-
CMake GUI 或
cmake -L
查看缓存变量,确保SomeLib_DIR
指向了包含SomeLibConfig.cmake
的正确目录。
3.手动检查模块路径
-
打印
CMAKE_MODULE_PATH
和CMAKE_PREFIX_PATH
查看自定义查找路径。 -
检查标准路径(
/usr/lib/cmake/SomeLib
,/usr/local/lib/cmake/SomeLib
等)。
7.使用 CMake GUI / ccmake
1.cmake-gui
(图形界面)
-
可视化缓存变量: 清晰看到所有缓存变量的当前值(包括类型和描述)。
-
修改和重新配置: 方便地修改变量值(如
CMAKE_BUILD_TYPE
,BUILD_SHARED_LIBS
, 库路径等)并点击 "Configure" 观察效果。 -
查看生成输出: 界面下方有输出日志窗口。
-
分组和搜索: 方便管理大量变量。
2.ccmake
(终端 curses 界面)
-
在终端中提供类似
cmake-gui
的交互式缓存变量编辑功能。 -
对于远程开发或无 GUI 环境非常有用。基本操作:
-
c
或g
进行配置/生成。 -
t
切换高级变量显示。 -
?
查看帮助。 -
方向键移动,
Enter
编辑变量。
-
8.调试工具链文件 (CMAKE_TOOLCHAIN_FILE
)
-
大量使用
message()
: 在工具链文件中关键位置(设置编译器标志、路径、平台变量前后)打印信息。 -
检查环境变量: 工具链文件经常依赖环境变量(
PATH
,CC
,CXX
,SDKROOT
等),确保它们设置正确并在工具链文件中打印出来。 -
验证编译器: 在工具链文件末尾或之后添加:
message(STATUS "CMAKE_C_COMPILER = ${CMAKE_C_COMPILER}")
message(STATUS "CMAKE_CXX_COMPILER = ${CMAKE_CXX_COMPILER}")
enable_language(C CXX) # 强制尝试检测编译器
9.检查编译器 / 平台兼容性:日志文件
CMake 在配置时会执行编译测试(如检查编译器特性、库是否可链接),结果记录在以下日志中,用于调试 “编译失败”“特性检测错误”:
CMakeFiles/CMakeOutput.log
:记录成功的编译测试(如 “编译器支持 C++17”)。CMakeFiles/CMakeError.log
:记录失败的编译测试(如 “链接库时未找到符号”“编译器不支持某特性”)。
用法:
当遇到 “某特性被误判不支持” 或 “库链接失败” 时,打开这两个文件,搜索具体的测试代码和错误信息(如编译器报错),定位问题(如缺少头文件、库路径错误)。
10.验证条件判断逻辑
CMake 的 if()
条件判断支持多种语法(如版本比较、变量存在性、路径检查),若分支逻辑异常,可直接输出条件表达式的结果:
# 检查变量是否存在(NOT DEFINED)
message(STATUS "MY_VAR 是否未定义: $<NOT:$<DEFINED:MY_VAR>>") # 生成器表达式(CMake 3.15+)# 检查版本比较结果
set(CMAKE_VERSION_STR "3.20.0")
message(STATUS "版本是否 >=3.10: ${CMAKE_VERSION_STR VERSION_GREATER_EQUAL 3.10}") # 输出 TRUE/FALSE# 检查路径是否存在
message(STATUS "src 目录是否存在: ${EXISTS ${CMAKE_SOURCE_DIR}/src}")
11.其他实用技巧
1.使用 VERBOSE
构建输出编译命令
配置时设置 CMAKE_VERBOSE_MAKEFILE
为 ON
,构建时会输出详细的编译 / 链接命令(检查是否使用了正确的头文件路径、库路径、宏定义):
set(CMAKE_VERBOSE_MAKEFILE ON) # 在 CMakeLists.txt 中设置
或构建时临时启用:
make VERBOSE=1 # 或 ninja -v
2.用 try_compile
/try_run
调试编译特性
当需要验证 “某段代码是否能编译 / 运行” 时,使用 try_compile
(编译测试)或 try_run
(运行测试),并输出结果:
# 测试代码是否能编译(如检查是否支持 std::optional)
try_compile(SUPPORT_OPTIONAL${CMAKE_BINARY_DIR}/test # 测试目录SOURCES ${CMAKE_SOURCE_DIR}/test/optional_test.cpp # 测试代码
)
message(STATUS "是否支持 std::optional: ${SUPPORT_OPTIONAL}")
3.缩小范围:逐步注释代码
若脚本复杂,可逐步注释部分代码(如 include
、find_package
、条件分支),定位到具体哪段代码导致异常(类似 “二分法” 调试)。
12.总结
CMake 调试的核心是 “验证变量值”“跟踪执行流程”“检查依赖查找过程”,常用工具链包括:
message()
输出变量和分支状态;cmake -L
查看缓存变量;--trace
/--debug-output
跟踪命令执行;CMAKE_FIND_DEBUG_MODE
调试依赖查找;- 日志文件(
CMakeOutput.log
/CMakeError.log
)分析编译测试。
根据问题的复杂程度,由浅入深地运用这些技巧,大部分 CMake 配置问题都能有效定位和解决。调试完成后记得清理或注释掉调试性的 message()
语句,保持 CMakeLists.txt 的整洁。
相关链接
- CMake 官网 CMake - Upgrade Your Software Build System
- CMake 官方文档:CMake Tutorial — CMake 4.1.0-rc1 Documentation
- CMake 源码:https://github.com/Kitware/CMake
- CMake 源码:CMake · GitLab
- 中文版基础介绍: CMake 入门实战 | HaHack
- wiki: Home · Wiki · CMake / Community · GitLab
- Modern CMake 简体中文版: Introduction · Modern CMake