引言
用 Nix 最大的自由是「任何包都能定制」——上游版本老、想打自己的补丁、想把某个依赖换成别的版本,都不用等发行版更新。这一切靠 override 与 overrideAttrs 两个机制。本文讲清它们的区别、怎么给包打源码补丁、怎么替换依赖、怎么在 flake 里覆盖 nixpkgs,以及 override 的常见陷阱。
前置:/nix-language-basics/(函数与属性集)、/nix-package-management/(derivation 与 nixpkgs)、/nix-language-deep-dive/(override 与 fix 函数)。
目录
- 1. 为什么需要定制包
- 2. override 与 overrideAttrs 的区别
- 3. override:替换构建依赖
- 4. overrideAttrs:改构建自身的属性
- 5. 打源码补丁:patches 与 applyPatches
- 6. 替换依赖版本:从一个包的 override 开始
- 7. 在 flake 里覆盖 nixpkgs
- 8. override 的陷阱:层级与递归
- 9. 维护自己的补丁集合
- 10. 速查表与一句话记忆
- 延伸阅读
1. 为什么需要定制包
1.1 三个真实场景
# 1) 上游修复了 bug 但还没发版 → 打一个小补丁
# 2) 官方包版本太老/太新 → 替换到目标版本
# 3) 依赖的某个库要换实现 → 从依赖链上替换
# 传统发行版:只能等打包者更新;Nix:override 即刻生效
1.2 定制的原则
# 优先用 nixpkgs 官方选项(如 config 选项、overlay)
# 其次 override 单个包(精准、不扩散)
# 最后才是全量 overlay(影响面大,谨慎)
# 定制要"最小化"——只改必要的属性,别整包重写
记忆:定制包的三场景——打补丁、换版本、换依赖;定制原则从窄到宽:官方选项 → override 单包 → 全量 overlay,越小越好。
2. override 与 overrideAttrs 的区别
2.1 两个机制管不同层
override:替换「传给包函数」的参数(依赖、版本等)
→ 影响包的依赖图
overrideAttrs:替换「derivation 自身的属性」(src、patches、buildInputs 等)
→ 影响单个包怎么被构建
2.2 速记对照
| 机制 | 改什么 | 典型用法 |
|---|---|---|
override | 包函数的参数 | 换依赖、换版本 |
overrideAttrs | 构建属性 | 打补丁、改源码、加依赖 |
记忆:override 改「传给包函数的参数」(依赖/版本),overrideAttrs 改「derivation 自身属性」(src/patches);两者各管一层,不要混用。
3. override:替换构建依赖
3.1 基础用法
# 让 mypkg 用另一版本的依赖 libfoo
myPkg = pkgs.mypkg.override {
libfoo = pkgs.libfoo_2_1; # 换成 libfoo 2.1
};
3.2 典型:换 Python 版本
# 一个用 python3 构建的工具,换成 python311 构建
myTool = pkgs.mytool.override {
python3 = pkgs.python311;
};
3.3 配合 flake 使用
{
packages.default = pkgs.mytool.override { libfoo = self'.packages.libfoo; };
}
记忆:override 像「改函数的实参」——给包函数传不同参数(换依赖/换版本)就得到新派生,原始包不变、无副作用。
4. overrideAttrs:改构建自身的属性
4.1 基础用法
# 给 mypkg 加一个构建依赖
myPkg = pkgs.mypkg.overrideAttrs (old: {
buildInputs = old.buildInputs ++ [ pkgs.extra-lib ];
});
4.2 改版本与源码
myPkg = pkgs.mypkg.overrideAttrs (old: {
# 用 git 上更新的源码替换
src = pkgs.fetchFromGitHub {
owner = "upstream";
repo = "mypkg";
rev = "abcdef123";
hash = "sha256-...";
};
});
记忆:overrideAttrs 接收「旧属性集」返回「新属性集」(old: {…}),改 src/patches/buildInputs 等构建属性;old 让你在旧值基础上增量改。
5. 打源码补丁:patches 与 applyPatches
5.1 patches 属性
myPkg = pkgs.mypkg.overrideAttrs (old: {
patches = (old.patches or []) ++ [
(pkgs.fetchpatch {
url = "https://github.com/upstream/mypkg/commit/xyz.patch";
sha256 = "sha256-...";
})
];
});
5.2 本地补丁文件
# 把补丁文件放 flake 里,用相对路径引用
myPkg = pkgs.mypkg.overrideAttrs (old: {
patches = (old.patches or []) ++ [ ./my-fix.patch ];
});
5.3 补丁格式与校验
# 补丁必须是 unified diff(git format-patch / git diff 生成)
# 补丁路径相对源码根目录
# 合并失败会构建报错——先本地 patch --dry-run 验证
# 生产补丁要带 hash 校验(fetchpatch 的 sha256)
记忆:打补丁 = overrideAttrs 加 patches 属性——线上补丁用 fetchpatch(带 hash),本地补丁用相对路径文件;unified diff、路径相对源码根、先 dry-run 验证。
6. 替换依赖版本:从一个包的 override 开始
6.1 从依赖链上替换
当一个包 A 依赖 B,B 有个 bug 想换版本——不改 A 的源码,而是构造一个「用新 B 的 A」:
# B 换版本,然后 A 用新 B 重建
newB = pkgs.B.override { ... }; # 或 overrideAttrs 换 src/版本
newA = pkgs.A.override { B = newB; };
6.2 换整个 nixpkgs 的版本
# 在 flake 里引入另一个 nixpkgs 版本(如 unstable 分支)
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-24.11";
};
# 需要稳定版软件时:nixpkgs-stable.legacyPackages.${system}.xxx
}
记忆:换依赖版本=先 override 依赖再让依赖方用新版本重建(A.override { B = newB; });跨版本引入用 flake 多 nixpkgs inputs,按需取 stable/unstable。
7. 在 flake 里覆盖 nixpkgs
7.1 用 overlays 做全局定制
当很多包都要同一种定制时,用 overlay 一次性覆盖:
{
nixpkgs.overlays = [
(final: prev: {
mypkg = prev.mypkg.overrideAttrs (old: { patches = [ ./fix.patch ]; });
})
];
}
7.2 override 与 overlay 的选择
# 单包定制 → override / overrideAttrs(精确、局部)
# 多处统一定制 → overlay(一处定义、全局生效)
# overlay 里还能调用 prev 的原始包做依赖
# 注意:overlay 会影响所有用到该包的地方,改动面大
记忆:flake 里全局定制用 nixpkgs.overlays(final: prev: {…});单包定制用 override、统一定制用 overlay——overlay 影响面大要谨慎。
8. override 的陷阱:层级与递归
8.1 陷阱一:override 不级联
A.override { B = newB } 只改 A 这一次调用——如果 C 也依赖 B,C 不会自动用 newB。层级依赖要逐层 override:
newA = pkgs.A.override { B = newB; };
newC = pkgs.C.override { B = newB; }; # C 也要显式换
8.2 陷阱二:overrideAttrs 丢属性
忘了用 old.xxx 增量、直接覆盖整张属性集,会把 src 等关键属性弄丢:
# 反模式:整集覆盖丢掉了 src
overrideAttrs (_: { buildInputs = [ ... ]; }) # ✗ src 没了
# 正确:基于 old 增量改
overrideAttrs (old: { buildInputs = old.buildInputs ++ [ ... ]; }) # ✓
8.3 陷阱三:递归覆盖
nixpkgs 自身的包集合是递归的——用 overlay 改一个包时,其他包对它的引用也要走 final 才能全局生效;只改 prev 会漏掉「已经被固定引用」的地方。
记忆:override 三大陷阱——不级联(C 依赖 B 要单独换)、overrideAttrs 丢属性(务必基于 old 增量)、overlay 递归(全局生效要理解 final/prev)。
9. 维护自己的补丁集合
9.1 补丁目录组织
patches/
mypkg-fix-a.patch # 每个补丁一个文件
mypkg-fix-b.patch
README.md # 记录补丁来源与原因
9.2 生产级补丁管理
# 1) 记录:补丁解决什么问题、来自上游哪个 commit、何时会过时
# 2) 校验:fetchpatch 带 hash,防上游改动串改
# 3) 追踪:上游发新版本后,用 nixpkgs-update 检查补丁是否还适用
# 4) 精简:能提 PR 到上游的优先提,本地补丁越少越好
# 5) 测试:改包后必须 nix build 验证 + 跑相关测试
记忆:补丁集合要文档化(来源/原因/过时条件)+ hash 校验 + 上游追踪 + 本地精简——补丁是债,能提上游就提上游。
10. 速查表与一句话记忆
| 机制 | 改什么 | 一句话 |
|---|---|---|
override | 包函数参数 | 换依赖/换版本 |
overrideAttrs | 构建属性 | 打补丁/改 src |
patches | 源码补丁 | fetchpatch 或本地文件 |
fetchpatch | 线上补丁 | 带 hash 校验 |
| overlay | 全局定制 | 一处定义全局生效 |
| flake 多 inputs | 跨版本引入 | stable/unstable 并行 |
一句话记忆:Nix 包定制的两把钥匙——override 改「传给包函数的参数」(换依赖/换版本,不级联需逐层换),overrideAttrs 改「derivation 自身属性」(改 src/patches/buildInputs,务必基于 old 增量别丢属性);打补丁用 patches 属性(线上 fetchpatch 带 hash、本地补丁放 flake 相对路径);多个包统一定制用 overlay(final: prev:,注意递归与影响面),跨版本用 flake 多 nixpkgs inputs;生产补丁要文档化来源与过时条件、能提上游就提上游——「最小化、可追溯、可回滚」是定制包的三条纪律。
延伸阅读
- /nix-language-basics/ — 函数式语法与属性集
- /nix-package-management/ — derivation 与 nixpkgs 结构
- /nix-language-deep-dive/ — fix 定点组合与 override 底层
- /nix-cross-compilation-overlay/ — overlay 机制深入
- /nix-flakes/ — flake 工作流与 inputs
- [[devops]] — 依赖与供应链管理
- Nix 官方手册 override 章节
- nixpkgs 手册:自定义包
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。