引言
Nix 的报错往往又长又绕——「error: hash mismatch in fixed-output derivation」后面跟一大段。会排错的人不会通读所有错误,而是按错误类型对号入座:哈希错查源码、依赖错查声明、语法错查引号、替换错查缓存。本文把 Nix 报错分成几大类,给每类对应的排查路径,再讲 --show-trace、builtins.trace、逐步缩小子依赖的调试思维。
前置:/nix-language-basics/(语法基础)、/nix-package-management/(derivation 与缓存)、/nix-reproducible-hermetic-builds/(固定哈希)。
目录
- 1. 排错思维:先分类再动手
- 2. 哈希不匹配:最常见的报错
- 3. 依赖与替换错误
- 4. 语法错误与 eval 错误
- 5. 构建日志:–verbose 与 -L
- 6. –show-trace:错误在哪个表达式
- 7. builtins.trace 与调试输出
- 8. 逐步缩小:定位到底哪个子依赖坏了
- 9. store 与缓存问题排查
- 10. 速查表与一句话记忆
- 延伸阅读
1. 排错思维:先分类再动手
1.1 Nix 报错的四大类
| 错误大类 | 典型消息 | 排查方向 |
|---|---|---|
| 哈希错误 | hash mismatch in fixed-output derivation | 源码/补丁变了 |
| 依赖错误 | a dependency was missing / 替换失败 | 声明缺失/版本错 |
| 语法/eval 错误 | syntax error / undefined variable | 表达式写错 |
| 缓存/网络错误 | unable to download / 替换失败 | 网络/缓存配置 |
1.2 排错顺序
# 1) 看错误第一行(错误类型关键词)
# 2) 按类型进对应排查路径
# 3) 再看具体细节(路径/哈希/变量名)
# 4) 不要一开始就 show-trace 通读全文
记忆:Nix 报错分四大类——哈希/依赖/语法/缓存;排错先读第一行定位类型、再走对应路径,别一上来就通读长错误。
2. 哈希不匹配:最常见的报错
2.1 场景
error: hash mismatch in fixed-output derivation '/nix/store/...':
specified: sha256-AAAA...=
got: sha256-BBBB...=
原因几乎总是:改了 src/rev/补丁,但哈希还是旧的。
2.2 修复
# 方法一:复制 got 的哈希替换 specified 的哈希
# 方法二:临时用 builtins.fetchurl 的 alias 或:
# 把 hash 换成 "lib.fakeSha256" 让构建报出实际值
nix build .#mypkg # 报出实际哈希,替换即可
2.3 哈希错的排查清单
# ☐ 改了 rev/url 忘更新 hash
# ☐ 补丁内容变了 hash 没变
# ☐ 源码在本地有非确定性内容(构建时生成)
# 注意:fixed-output 只能用于"内容确定"的源——动态内容别用
记忆:哈希不匹配几乎都是「改了源忘了改哈希」——复制 got 替换 specified 或临时用假哈希让构建报实际值;固定哈希只用于内容确定的源。
3. 依赖与替换错误
3.1 常见形态
error: a 'x86_64-linux' with features {...} is required to build ...
→ 平台/特性不满足(缺少 cross 支持)
error: attribute 'xxx' missing
→ 引用不存在的属性(拼写/版本不存在)
error: cannot find derivation for 'xxx'
→ 依赖 derivations 没构建出来
3.2 排查路径
# 1) 属性缺失:检查拼写与 nixpkgs 是否含该版本(nix search / nix eval)
# 2) 平台特性:检查 cross 与 hostPlatform 声明
# 3) 依赖没构建:先 nix build 依赖项,看它自己报什么
# 4) 版本被替换失败:检查 override 是否指向不存在的版本
记忆:依赖错误分三态——属性缺失(拼写/版本查 nix search)、平台不满足(cross/hostPlatform)、依赖没构建(先单独 build 依赖)。
4. 语法错误与 eval 错误
4.1 高频语法坑
syntax error → 引号/括号不配对、逗号遗漏
undefined variable 'x' → 变量没定义/作用域外(let 里忘绑定)
infinite recursion → 属性集互相引用成环(override 用错层)
4.2 用 nix-instantiate 快速验证
# 单独求值一个表达式,不改动系统
nix-instantiate --eval -E 'let x = 1; in x + 1' # 输出 2
nix eval nixpkgs#hello.version # 求值某包的属性
# 语法错误在求值时立刻报出,比 build 快得多
4.3 排查技巧
# 括号不配对:用 nixfmt 格式化(自动对括号)
# 无限递归:看递归是"引用自己"还是"override 层错"
# undefined:检查 let 绑定与 attrset 键名
记忆:语法/eval 错用 nix-instantiate –eval / nix eval 快速验证;高频坑是引号括号不配对、变量未定义、属性集成环递归——nixfmt 对括号、逐层查绑定。
5. 构建日志:–verbose 与 -L
5.1 看构建过程
# 默认只显示构建进度,-L 打印完整日志(含子 derivation)
nix build .#mypkg -L
# 详细调试
nix build .#mypkg --verbose --print-build-logs
# 保存日志
nix build .#mypkg -L 2>&1 | tee /tmp/build.log
5.2 从日志里找真凶
# 搜索 ERROR / error / Failed / command not found
# 定位到失败的那一步(configure/make/install)
# 子 derivation 失败:先看它自己的 -L 输出
记忆:
-L打印完整构建日志(含子 derivation)、--verbose更详细;搜 ERROR/失败步定位到 configure/make 哪一步,子失败先看子日志。
6. –show-trace:错误在哪个表达式
6.1 什么时候用
eval 错误默认只报最外层,--show-trace 展开求值调用栈——知道是哪个表达式触发了:
nix build .#mypkg --show-trace
# 输出从根到错误点的表达式链
6.2 读 trace
# trace 自底向上读:最下面是"错误源头",往上是"调用它的表达式"
# 定位到具体 attr/override/模块,而不是在错误消息里猜
# 注意:trace 很长,先搜关键文件名/属性名
记忆:
--show-trace展开求值调用栈,从底部读错误源头、往上读调用链;先搜文件名/属性名定位,别逐行读。
7. builtins.trace 与调试输出
7.1 在表达式里埋点
# 求值时打印变量,不改变结果
let
x = builtins.trace "debug: x = ${toString x}" 42;
in x + 1
# stderr 输出:debug: x = 42
7.2 调试模块/overlay
# overlay 里埋点看某属性被求值成什么
(final: prev: {
mypkg = builtins.trace
"overlay: mypkg.src.hash = ${final.mypkg.src.hash}"
prev.mypkg;
})
7.3 trace 的局限
# trace 只在"被求值"时打印——惰性求值下没用到就不会打
# 确认"有没有走到":trace 一个固定值也看不到 → 说明分支没执行
记忆:builtins.trace 在表达式里打印调试值且不改变结果——overlay/模块里埋点确认求值过程;注意惰性求值下没被用到就不打印。
8. 逐步缩小:定位到底哪个子依赖坏了
8.1 二分缩小
# 错误在"某个子依赖"里,直接看子依赖:
# 1) 单独 build 那个子依赖:nix build nixpkgs#那个包
# 2) 子依赖也大 → 用 --rebuild 或改小测试源
# 3) 沿依赖链向下,逐层找到第一个坏点
8.2 用临时最小复现
# 写一个最小 test.nix,只包含可疑的依赖,快速复现
{ pkgs ? import <nixpkgs> {} }:
pkgs.mypkg.overrideAttrs (old: {
# 注释掉部分步骤,缩小到具体环节
})
记忆:定位依赖链错误用二分——先单独 build 可疑子依赖、沿链向下找到第一个坏点;写最小 test.nix 注释部分步骤快速复现。
9. store 与缓存问题排查
9.1 store 常见问题
nix-store 损坏/权限错 → nix-store --verify 修复
磁盘满 → nix-collect-garbage 清理 + 查大文件
锁冲突 "waiting for lock" → 有别的 nix 进程,或 stale lock 清理
9.2 缓存/网络问题
unable to download 'https://...' → 网络不通/代理配置
缓存替换失败 → 源缓存挂了,切 --option substitute-from 或直连
二进制缓存路径错 → CACHIX_* 环境变量/配置检查
9.3 生产排障清单
# ☐ 先确定"本地能构建吗"(排除缓存因素)
# ☐ store 完整性:nix-store --verify
# ☐ 网络/代理:curl 测一下目标地址
# ☐ 锁冲突:等或清理 stale lock
# ☐ 磁盘:df -h + nix-collect-garbage
记忆:store 问题走 nix-store –verify 与 GC;缓存/网络走「本地先 build 排除缓存 + curl 测网络 + 查代理」;生产排障先确认本地能构建。
10. 速查表与一句话记忆
| 报错 | 排查 | 一句话 |
|---|---|---|
| hash mismatch | 更新哈希 | 改了源忘了改 hash |
| attribute missing | 查拼写/版本 | 引用不存在 |
| syntax error | nixfmt 对括号 | 引号括号不配对 |
| undefined variable | 查 let/attrset | 作用域外 |
| infinite recursion | 查 override 层 | 属性集成环 |
| 依赖失败 | 单独 build 子依赖 | 沿链二分定位 |
| 下载失败 | 查网络/代理 | curl 先测 |
| store 损坏 | nix-store –verify | 完整性修复 |
一句话记忆:Nix 排错先按错误分类对号入座——哈希不匹配(改了源忘了改 hash,复制 got 替换)、依赖错误(属性缺失查拼写版本、平台不满足查 cross、依赖失败先单独 build 子依赖沿链二分)、语法/eval 错(nix-instantiate –eval 快速验证、nixfmt 对括号、查变量绑定与 override 层)、缓存/网络错(本地先 build 排除缓存 + curl 测网 + 查代理);调试工具链是 -L 看完整构建日志、--show-trace 展开求值栈从底读源头、builtins.trace 在表达式里埋点(注意惰性求值);store 问题走 nix-store --verify 与 GC——「先分类、再动手、沿依赖链缩到最小复现」是 Nix 排错的黄金顺序。
延伸阅读
- /nix-language-basics/ — 语法与求值基础
- /nix-package-management/ — derivation 与缓存机制
- /nix-reproducible-hermetic-builds/ — 固定哈希与确定性
- /nixos-vm-integration-testing/ — 系统级测试与验证
- /nix-ci-cachix/ — CI 与缓存配置
- [[devops]] — 构建与发布排障
- Nix 官方调试文档
- NixOS 维基:常见问题
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。