CMakeCache 陈旧缓存行为异常速查
更新时间:2026-10-24。FAQ 速查页,面向用 CMake 管理构建的同学。CMakeCache 陈旧缓存指
build/CMakeCache.txt里记录的旧值(编译器路径、选项开关、依赖查找结果)与现实脱节,导致「明明改了配置,构建行为却纹丝不动」或「换了环境后 configure 报莫名其妙的错」。它和 CMakeLists 语法错误速查 正好分处 configure 的两端:一个是脚本写错跑不过,一个是脚本没错但被旧缓存带偏。本篇按「改了什么不生效」逐条定位。
本文要回答的问题
- 为什么
cmake -DXXX=ON ..改了选项,重新构建后行为完全没变? CMakeCache.txt里到底记了什么?哪些值一旦写进去就「钉死」了?- 换了编译器之后 configure 还认旧路径,怎么破?
- 「删 build 目录重头来」为什么总能解决问题,代价又是什么?
- ccache 命中旧产物和 CMakeCache 陈旧是一回事吗?
一、报错原文与典型现象
典型现场一(工程路径以 /proj/demo 占位):改了选项不生效,configure 输出里旧值纹丝不动:
$ cmake -B build -DENABLE_TESTS=ON
-- Configuring done
-- Generating done
$ grep ENABLE_TESTS build/CMakeCache.txt
ENABLE_TESTS:BOOL=OFF典型现场二:换编译器后 configure 直接拒绝:
CMake Error at CMakeLists.txt:3 (project):
The CMAKE_CXX_COMPILER:
/opt/old-toolchain/bin/g++
is not a full path to an existing compiler tool.还有最隐蔽的一种:不报错,但二进制行为怪异——编译选项改了、代码也改了,跑出来的程序还是旧逻辑,查到最后是 ccache 把旧编译产物按哈希命中直接回给了链接器。读懂现场抓住两件东西:CMakeCache.txt 里那个变量当前的实际值(grep 变量名 build/CMakeCache.txt 一行看清)与这个值是从哪次 configure 继承来的(cache 一旦写入,默认不再随命令行变化)。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
-D 传了新值但配置没变化 | cache 里已有同名变量,旧值优先 | grep VAR build/CMakeCache.txt 看实际值 | 用 -DVAR=值 强行覆盖只对未定义时生效;改已存在值用 cmake -DVAR=新值(同名覆盖)或 cmake -U VAR 先清再设;最稳是删 cache 重来 |
| 换了编译器 configure 报旧路径不存在 | CMAKE_CXX_COMPILER 已钉在 cache 里 | grep CMAKE_CXX_COMPILER build/CMakeCache.txt | 编译器变量只能在空 build 目录首次 configure 时指定;换编译器必须删 build 目录重建 |
| 升级 CMake 后老 build 目录 configure 报错 | cache 里记录的旧版本内部字段不兼容 | 报错含 CMakeCache.txt 版本相关字样 | 删 build 目录重建;不要跨大版本复用 build 目录 |
| 手工改了 CMakeCache.txt 后行为更乱 | 内部字段与派生文件不同步 | 回忆是否直接编辑过该文件 | CMakeCache.txt 只读不改;要改值走 cmake -D 或 ccmake/cmake-gui |
| 源码目录整体搬家后 configure 失败 | cache 里写死了旧的绝对路径 | 报错里出现旧的源码绝对路径 | build 目录不可移动也不可跨源码树复用,删了重建 |
| 代码改了但二进制行为没变 | ccache 按预处理器输出哈希命中旧产物 | ccache -s 看命中率;make VERBOSE=1 看是否走了缓存 | 排查期 ccache -C 清空或 CCACHE_DISABLE=1 绕过;确认非代码问题后再恢复 |
find_package 找到了旧版本的库 | _DIR 变量缓存了旧安装位置 | grep _DIR build/CMakeCache.txt 看指向 | cmake -U '*_DIR' 清掉查找结果再 configure;或删 build 目录 |
| option() 改了默认值但存量构建不变 | option() 只在 cache 无此项时写入默认值 | 对照 CMakeLists 里的 option 默认值与 cache 实际值 | option 默认值只影响首次 configure;存量 build 用 -D 显式覆盖 |
三、排查决策图
下面这张图按「陈旧值藏在哪一层」分岔:红是报错入口与命中的根因,蓝是排查动作,绿是验证通过,橙是修复动作,紫是防复发沉淀。
读这张图抓一条主线:任何「改了不生效」都先用 grep 看 cache 里的实际值——值是错的就分两类:编译器与源码路径这类「钉死变量」只能删 build 目录;普通选项类用 -D 覆盖或 -U 清掉。值全对但行为还旧,嫌疑就转移到 ccache 这类编译缓存上,排查期先禁用再复测。
四、逐条排查展开
cache 变量的「一次性」语义是万恶之源。 CMake 的 cache 变量只在第一次写入时取值,之后 -D 也好、option() 改默认值也好,都动不了它。这是设计使然——CMake 认为 cache 是「用户的选择记录」,不该被脚本悄悄覆盖。但对新手而言这就是「我改了没用」的直接来源。改已存在 cache 值的正确姿势是显式覆盖:
cmake -B build -DENABLE_TESTS=ON # 同名 -D 会覆盖已存在的 cache 值
cmake -B build -U ENABLE_TESTS # 或者先从 cache 里删掉这一项编译器与源码路径是「钉死变量」,删目录是唯一解。 CMAKE_CXX_COMPILER、源码树绝对路径这些值在首次 configure 时被写进 cache 并参与生成大量派生文件(CMakeFiles/ 下的检测记录),后期任何 -D 都改不动——CMake 检测到你想换编译器时会直接报错拒绝。所以换编译器、搬源码目录、升 CMake 大版本这三件事,标准动作就是:
rm -rf build && cmake -B build -DCMAKE_CXX_COMPILER=/usr/bin/clang++代价是全量重编,但换来的是一个干净的、状态自洽的构建目录。CI 上每个构建配置独立目录、本地按编译器分目录(build-gcc/、build-clang/),可以把这个代价压到最低。
手工编辑 CMakeCache.txt 是禁区。 文件里除了用户可见的选项,还有大量 INTERNAL 字段(ABI 检测结果、特性探测记录),手工改一处、派生文件不同步,configure 行为会变得更难预测。要交互式改值用 ccmake build 或 cmake-gui,它们改完会触发必要的重检测。
依赖查找结果也被缓存。 find_package(Boost) 找到的 Boost_DIR 一旦写入 cache,升级了 Boost、换了安装位置都不会自动重找。cmake -U 'Boost_*' 清掉相关条目再 configure 即可;批量清 -U 支持 glob 模式。
ccache 是另一层缓存,病因不同但症状雷同。 ccache 按「预处理器输出 + 编译参数」的哈希命中旧编译产物,正常情况下完全一致的重编译直接返回缓存是提速利器;但当你改了「不参与哈希的东西」(比如链接的库变了而源码没变、或上一次缓存是带错参数编出来的),就会拿到行为旧的 .o。鉴别方法:make VERBOSE=1 看编译命令前有没有 ccache 前缀、ccache -s 看命中率突增;排查期 export CCACHE_DISABLE=1 或 ccache -C 清空,确认问题消失再回头分析缓存条目。注意它只影响编译产物,与 CMakeCache 无关,别混为一谈。
option() 默认值只影响首次 configure。 在 CMakeLists 里把 option(ENABLE_TESTS "..." OFF) 改成 ON,所有已经 configure 过的 build 目录都不会变——它们 cache 里已有这一项。这对 CI 尤其坑:CI 如果复用 build 目录,默认值变更就静默失效。规则:CI 每次全新 configure;默认值变更要在升级说明里显式提示存量开发者 -D 一次。
五、规范做法与预防
- 构建目录按配置隔离:
build-release/、build-debug/、build-clang/各占一个目录,互不污染,也免了来回-D切换造成的 cache 打架。 - CI 一律干净构建:CI 不复用 build 目录,每次从零 configure,陈旧缓存病在 CI 上从源头绝迹。
- 环境变更后的肌肉记忆:换编译器、搬目录、升 CMake 大版本,第一反应就是删 build 目录,别试图抢救。
- 排错期关掉编译缓存:
CCACHE_DISABLE=1进排查清单,先把 ccache 从嫌疑人名单里划掉再往下查。 - CMakeCache.txt 只读纪律:写进团队规范——改配置走
cmake -D或 ccmake,绝不手工编辑;review 里看到有人提交 build 目录内容直接打回。
这一篇在知识体系里的位置
本篇是 Makefile/CMake 排错速查簇的缓存状态篇,管「configure 结果的缓存与现实脱节」。configure 本身的语法病看 CMakeLists 语法错误速查;构建调度层的竞态病看本批的 并行构建偶发失败速查;编译器与版本相关见 CMake 找不到编译器速查、CMake 版本不兼容速查;依赖查找失败见 CMake 找不到依赖包速查、CMake FetchContent 拉取失败速查;编译与链接期其余岔路见 fatal error 头文件找不到速查、undefined reference 链接错误速查、multiple definition 重复定义速查、make 编译内存超限速查、make 编译器内部崩溃速查、构建 Permission denied 速查、make: No rule to make target 速查、make 构建卡死无响应速查、make 构建输出乱码速查、Makefile missing separator 速查、error: cannot convert 类型不匹配速查。「先 grep cache 看实际值」这个动作对一切「改了配置不生效」类问题通用。
一句话总结
CMakeCache 陈旧先用 grep 变量名 build/CMakeCache.txt 看缓存实际值:编译器路径、源码路径这类钉死变量只能删 build 目录全量重建;普通选项与依赖查找结果用 cmake -D 显式覆盖或 -U 清单项再 configure;cache 值全对但二进制行为还旧就轮到 ccache,排查期 CCACHE_DISABLE=1 绕过验证;长期靠构建目录按配置隔离加 CI 干净构建,把陈旧缓存病从源头掐掉。