拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CMake实践:常见的调试技巧

目录1.简介2.用 message() 输出关键信息2.1.message简介2.2.常用模式及作用2.3.核心用法示例2.4.常见问题及解决3.查看缓存变量cmake -L 与缓存文件3.1.列出所有缓存变量cmake -L3.2.直接查看 / 删除 CMakeCache.txt4.变量追踪与作用域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 与 --trace5.1.--debug-output输出调试级信息5.2.--trace跟踪所有执行的命令最详细5.3.--warn-uninitialized5.4.--graphviz (CMake 2.8.10)6.调试 find_package 依赖查找失败7.使用 CMake GUI / ccmake8.调试工具链文件 (CMAKE_TOOLCHAIN_FILE)9.用VS2022单步调试CMakeList.txt工程9.1.CMake版本说明9.2.调试步骤10.用VSCode单步调试CMakeList.txt工程10.1.CMake版本说明10.2.调试步骤11.检查编译器 / 平台兼容性日志文件12.验证条件判断逻辑13.MSVC文件编译耗时统计14.其他实用技巧15.总结相关链接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, c3.跟踪条件分支执行当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:BOOLOFF OpenSSL_FOUND:BOOLON 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: 14.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_app PROPERTIES 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与--trace5.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-sourcefile,--trace-redirectfile。5.3.--warn-uninitialized警告使用了未显式初始化未设置的变量非常有用。cmake --warn-uninitialized .5.4.--graphviz(CMake 2.8.10)生成一个.dot文件可视化显示目标之间的依赖关系图。cmake --graphvizgraph.dot .然后用 Graphviz 工具如dot -Tpng graph.dot -o graph.png生成图片查看。6.调试find_package依赖查找失败find_package找不到依赖是常见问题如路径错误、版本不匹配可通过以下方法定位1.启用查找调试模式CMAKE_FIND_DEBUG_MODE设置CMAKE_FIND_DEBUG_MODE为ONCMake 会输出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 /ccmake1.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.用VS2022单步调试CMakeList.txt工程9.1.CMake版本说明VS2022 单步调试 CMake 脚本CMake 最低版本3.27.0.CMake 3.27是官方 ** 首次正式推出 CMake 调试器Debug Adapter Protocol** 的版本只有这个版本及以上才支持 VS2022 断点、单步、变量查看这是 VS 调试 CMake 脚本的底层依赖无法绕过VS2022 自带的 CMake 版本VS202217.4 及以上版本自带 CMake 3.27开箱即用无需手动安装如果你是新版 VS2022直接用内置的 CMake 就能调试老版 VS2022 会自带低版本 CMake必须升级9.2.调试步骤1.用VS2022打开CMake工程以fineftp-server为例下载源码后直接打开它。fineftp-server: 轻量级C跨平台FTP服务器解决方案2.在CMakeList.txt中设置断点选中CMakeList.txt文件右键弹出菜单选择 使用CMake调试程序配置缓存程序停留在断点的位置在局部变量窗口显示了CMake缓存变量的值如下图所示3.调试查找第三方库find_package当程序进行到 find_package(asio REQUIRED)单步往下走即可查看asio相关的CMake变量从而判断查找asio是否成功成功查找如下这种可视化的方式调试我觉得是检查CMakeList.txt正确最好的方法了。10.用VSCode单步调试CMakeList.txt工程使用VSCode调试CMake,必须安装插件CMake Tools插件如下图10.1.CMake版本说明同9.110.2.调试步骤1.以fineftp-server为例同样用VSCode打开fineftp-server源码文件夹如下图所示2.在CMakeList.txt文件中设置断点选中CMakeList.txt文件右键弹出菜单选择 使用CMake调试器清理重新配置所有项目程序停留在断点的位置在局部变量窗口显示了CMake缓存变量的值如下图所示3.调试查找第三方库find_package当程序进行到 find_package(asio REQUIRED)单步往下走即可查看asio相关的CMake变量从而判断查找asio是否成功成功查找如下其实方法都和VS2022差不多。11.检查编译器 / 平台兼容性日志文件CMake 在配置时会执行编译测试如检查编译器特性、库是否可链接结果记录在以下日志中用于调试 “编译失败”“特性检测错误”CMakeFiles/CMakeOutput.log记录成功的编译测试如 “编译器支持 C17”。CMakeFiles/CMakeError.log记录失败的编译测试如 “链接库时未找到符号”“编译器不支持某特性”。用法当遇到 “某特性被误判不支持” 或 “库链接失败” 时打开这两个文件搜索具体的测试代码和错误信息如编译器报错定位问题如缺少头文件、库路径错误。12.验证条件判断逻辑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})13.MSVC文件编译耗时统计/Bt是 MSVC 编译器选项显示编译时间统计打印每个文件编译耗时仅 MSVC 有效GCC/Clang 会识别为未知参数。/Bt基础文件编译计时/Bt更详细包含模板实例化等内部阶段耗时方式 1直接修改全局 CMAKE_CXX_FLAGSif(MSVC) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} /Bt) endif()⚠️ 注意CMake 习惯用斜杠/不要写成-BtMSVC 参数是/Bt。方式 2target 级别推荐只给指定目标开启只对某个 target 生效不污染全局编译选项target_compile_options(your_target_name PRIVATE $$CXX_COMPILER_ID:MSVC:/Bt )方式 3CMake 命令行传入cmake -DCMAKE_CXX_FLAGS/Bt ../Bt只在编译阶段输出不是链接时间统计链接耗时要看/time链接器选项if(MSVC) set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} /time) endif()14.其他实用技巧1.使用VERBOSE构建输出编译命令配置时设置CMAKE_VERBOSE_MAKEFILE为ON构建时会输出详细的编译 / 链接命令检查是否使用了正确的头文件路径、库路径、宏定义set(CMAKE_VERBOSE_MAKEFILE ON) # 在 CMakeLists.txt 中设置或构建时临时启用make VERBOSE1 # 或 ninja -v2.用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、条件分支定位到具体哪段代码导致异常类似 “二分法” 调试。15.总结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 SystemCMake 官方文档CMake Tutorial — CMake 4.1.0-rc1 DocumentationCMake 源码https://github.com/Kitware/CMakeCMake 源码CMake · GitLab中文版基础介绍: CMake 入门实战 | HaHackwiki: Home · Wiki · CMake / Community · GitLabModern CMake 简体中文版: Introduction · Modern CMake
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门