news 2026/9/19 21:06:51

Nixpkgs 包覆盖机制深度解析:`override`、`overrideAttrs`、`overrideDerivation` 与 `lib.makeOverridable`

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nixpkgs 包覆盖机制深度解析:`override`、`overrideAttrs`、`overrideDerivation` 与 `lib.makeOverridable`
  • 包管理器
  • 操作系统

【免费下载链接】nixpkgs

Nix Packages collection & NixOS

项目地址:https://gitcode.com/GitHub_Trending/ni/nixpkgs
点击查看免费下载

本篇技术指南系统讲解 Nixpkgs 中覆盖(Overriding)机制的四大核心工具:<pkg>.override<pkg>.overrideAttrs<pkg>.overrideDerivation以及底层的lib.makeOverridable。它们用于在不修改 nixpkgs 仓库源码的前提下,对单个软件包的构建参数与派生属性进行定制。读完本文,你将掌握每种覆盖函数的使用场景、函数签名语义、与 Overlay 的分工关系,以及它们背后的 Nix 求值原理,能够独立写出可复用的包定制表达式。

什么是覆盖(Overriding)

在 Nixpkgs 中,有时我们需要覆盖nixpkgs的部分内容,例如某个 derivation 的属性、甚至 derivation 的构建结果本身。覆盖机制正是为此设计的:这些函数用于对包做出修改,并只返回被修改后的单个包

与之相对,Overlay(覆盖层) 则是另一套机制——它作用于整个 Nixpkgs 包集合(package set),可以在pkgs这个集合层面统一修改多个包,并通过final: prev: { ... }函数形式叠加在包的固定点(fixpoint)计算之上。因此:

  • 覆盖(Override):针对单个包做局部修改,粒度细、局部性强;
  • Overlay:把被覆盖的包重新组合进整个 Nixpkgs 包集合,粒度是"集合"。

在 overlays.chapter.md 中有这样一个典型示例,展示了二者如何配合使用——先用override修改单个包,再通过 overlay 把它放回包集合:

final: prev: { boost = prev.boost.override { python = final.python3; }; rr = prev.callPackage ./pkgs/rr { stdenv = final.stdenv_32bit; }; }

注意其中的依赖关系:final.python3是覆盖后包集合中的 Python 3,而prev.boost则是覆盖前的原始 Boost。这种override+ overlay 的组合是 Nixpkgs 生态中最常见的定制方式。

<pkg>.override:覆盖函数的输入参数

基本用法

override函数通常对 nixpkgs 表达式(pkgs)中的所有 derivation 都可用。它用于覆盖传给包函数的参数。所谓包函数,是指形如{ pname, version, stdenv, ... }:的 Nix 函数——包源码文件(如pkgs/by-name/...pkgs/top-level/all-packages.nix中引用的文件)导出的通常是这样的函数,调用时传入一个属性集。

典型用法:

pkgs.foo.override { arg1 = val1; arg2 = val2; # ... }

即:用新的参数集{ arg1 = val1; arg2 = val2; }重新调用pkgs.foo背后的那个包函数,其余未提及的参数沿用默认值。

访问上一组参数

也可以传入一个函数来访问上一组参数,从而基于旧值派生新值:

pkgs.foo.override (previous: { arg1 = previous.arg1; # ... })

这里previous是上一次调用包函数时使用的参数集。这种写法在需要"保留原有依赖、只改其中某一项"的场景非常有用。

在包集合中全局应用

override往往与 Overlay 配合,在整个包集合层面生效。例如通过import pkgs.path导入 Nixpkgs 并传入overlays参数:

import pkgs.path { overlays = [ (self: super: { foo = super.foo.override { barSupport = true; }; }) ]; }

上述表达式会用barSupport = true重新构建foo,并把结果放回覆盖后的包集合self中。

callPackage组合

override也经常出现在callPackage的调用中,用于在构造某个包时,向其依赖传入已被覆盖的版本:

{ mypkg = pkgs.callPackage ./mypkg.nix { mydep = pkgs.mydep.override { # ... }; }; }

callPackage本身定义于 lib/customisation.nix 的callPackageWith,它从自动参数集autoArgs中按函数签名自动填充缺失参数,并把结果用makeOverridable包装(详见下文)。因此凡是经callPackage构造的包,天然带有overrideoverrideAttrs等方法。

语义与注意事项

在第一个示例中,pkgs.foo是以一组默认参数调用包函数得到的结果(通常是一个 derivation)。使用pkgs.foo.override会用给定的新参数重新调用同一个函数。

许多包(如示例中的foo)会在参数中提供带默认值的包选项(package options)来方便覆盖。但请注意:

  • 由于通常无法测试包在所有选项组合下都能成功构建,你可能会发现某些包在把选项覆盖为非默认值时构建失败;
  • 包维护者没有义务修复任意选项组合的问题
  • 如果发现某个选项组合不工作,欢迎提交修复,最好附带回归测试(regression test);
  • 如果想确保相关功能持续可用,可以考虑成为该包的维护者。

<pkg>.overrideAttrs:覆盖传给stdenv.mkDerivation的属性集

定位与适用对象

overrideAttrs允许你覆盖传给stdenv.mkDerivation调用的属性集,基于原 derivation 生成一个新 derivation。它对所有由stdenv.mkDerivation产生的 derivation 都可用——而这涵盖了 Nixpkgs 表达式pkgs中的绝大多数包。

基本用法与参数语义

{ helloBar = pkgs.hello.overrideAttrs ( finalAttrs: previousAttrs: { pname = previousAttrs.pname + "-bar"; } ); }

在上面的例子中,"-bar"被追加到pname属性,而其他所有属性都会从原始的hello包中保留下来。

关于两个函数参数的约定:

  • previousAttrs:习惯上指最初传给stdenv.mkDerivation的属性集;
  • finalAttrs:指最终传给mkDerivation的属性集,另外还包含一个finalPackage属性,它等于mkDerivation的结果或后续overrideAttrs调用的结果;
  • 如果只写一个参数的函数,那么这个参数具有previousAttrs的含义;
  • 如果不需要访问previousAttrsfinalAttrs,两个参数都可以完全省略。

只传属性集的形式

{ helloWithDebug = pkgs.hello.overrideAttrs { separateDebugInfo = true; }; }

在上述例子中,separateDebugInfo属性被覆盖为true,从而为helloWithDebug构建调试信息。此时没有使用函数形式,直接传入属性集,覆盖语义等价于"只写一个参数且仅使用 previousAttrs"。

为什么overrideAttrs优于overrideDerivation

文档中特别强调了一个关键点:separateDebugInfo只会被stdenv.mkDerivation函数处理,而不会被生成后的"原始 Nix derivation"处理。因此,若改用overrideDerivation,它将只覆盖最终 derivation 的属性,separateDebugInfo根本不会被处理,此例中也就无法生效。

这就是在(几乎)所有情况下都应优先使用overrideAttrs而非overrideDerivation的原因:

  1. stdenv.mkDerivation继续处理输入参数(如separateDebugInfo这类高阶选项);
  2. 使用起来更容易:可以直接使用你在 Nix 代码中看到的属性名(例如buildInputs),而不必使用生成后的名字(如nativeBuildInputs对应的生成形态);
  3. 输入更少、代码更简洁。

源码层面的实现印证

overrideAttrs的真正实现在 pkgs/stdenv/generic/make-derivation.nix 中(参见makeDerivationExtensible,约 L228-L311)。其核心逻辑是:

overrideAttrs = f0: makeDerivationExtensible ( final: let prev = rattrs final; thisOverlay = if isFunction f0 then let fPrev = f0 prev; in if isFunction fPrev then f0 final prev # f 是 (final: prev: { ... }) 形式 else fPrev # f 是 (prev: { ... }) 形式 else f0; # f 不是函数,大概率是 { ... } in ... (prev // (removeAttrs thisOverlay [ "__intentionallyOverridingVersion" ])) );

从源码可以看到overrideAttrs的三种输入形态是如何被统一处理的:属性集、单参数函数(prev: ...)、双参数函数(final: prev: ...),这与文档中的说明完全一致。同时,finalPackageoverrideAttrs会被注入递归属性集args中(args = rattrs (args // { inherit finalPackage overrideAttrs; })),从而支持finalAttrs引用最终结果。

此外,源码中还内置了一个非常实用的告警(warning):当你在overrideAttrs中覆盖了version却没有同时覆盖src时,构建时会打印提示,建议同时覆盖versionsrc(例如借助rec { version = "1.0.0"; src = pkgs.fetchurl { url = "mirror://gnu/hello/hello-${version}.tar.gz"; ... }; })。如果确属有意为之,可以通过设置__intentionallyOverridingVersion = true来关闭该告警——该属性会在合并前被removeAttrs移除,不会污染最终属性集。

<pkg>.overrideDerivation:覆盖最终 derivation 的属性

定位与警告

在几乎所有情况下你都应优先使用 `overrideAttrs`。`overrideDerivation` 并未被废弃,仍会继续工作,但它使用起来不够方便,能力也不如 `overrideAttrs`。
不要在本仓库 Nixpkgs 内部使用该函数:它会在修改前对 derivation 进行求值,破坏了包的抽象(package abstraction)。此外,这种"每次应用函数都要求值"的做法会带来性能开销,当大量覆盖叠加时可能成为问题。它只适用于临时定制(ad-hoc customisation),例如 `~/.config/nixpkgs/config.nix` 这样的用户级配置场景。

基本用法

overrideDerivation基于现有 derivation,用指定函数产生的属性集覆盖原 derivation 的属性,从而创建新 derivation。该函数对所有用makeOverridable定义的 derivation都可用;而大多数标准的 derivation 生成函数(如stdenv.mkDerivation)都是用makeOverridable定义的,因此pkgs中大多数包都带有这个函数。

{ mySed = pkgs.gnused.overrideDerivation (oldAttrs: { name = "sed-4.2.2-pre"; src = fetchurl { url = "ftp://alpha.gnu.org/gnu/sed/sed-4.2.2-pre.tar.bz2"; hash = "sha256-MxBJRcM2rYzQYwJ5XKxhXTQByvSg5jZc5cSHEZoB2IY="; }; patches = [ ]; }); }

在上述例子中,derivation 的namesrcpatches会被覆盖,而其他所有属性会从原 derivation 保留。参数oldAttrs用来引用原 derivation 的属性集。

关键陷阱:属性求值先于覆盖

包的属性是在被 `overrideDerivation` 修改**之前**求值的。例如,`url = "mirror://gnu/hello/${name}.tar.gz";` 中的 `name` 引用会在 `overrideDerivation` 修改属性集之前被填入。这意味着在此例中,仅覆盖 `name` 属性**不会**改变 `url` 的值——你必须同时覆盖 `name` 和 `url` 两个属性。

这个求值时序问题正是overrideDerivation相对难用的根源之一,也是文档建议在绝大多数场景转向overrideAttrs的重要原因。

源码实现

overrideDerivation定义于 lib/customisation.nix(L99-L112):

overrideDerivation = drv: f: (extendDerivation (seq drv.drvPath true)) ( { meta = drv.meta or { }; passthru = drv.passthru or { }; } // (drv.passthru or { }) // { ${if drv ? __spliced then "__spliced" else null} = mapAttrs ( _: sDrv: overrideDerivation sDrv f ) drv.__spliced; } ) (derivation (drv.drvAttrs // (f drv)));

从源码可以看出:overrideDerivation直接调用 Nix 内建derivation,把drv.drvAttrsf drv的结果合并后重新构造底层 derivation——这正是"它只作用于最终 derivation 属性"这一特性的来源。同时它也印证了文档中的性能警告:每次覆盖都会重新对 derivation 求值,并依赖seq drv.drvPath强制保留旧 derivation 的求值路径。

overrideDerivation的另一个应用场景,从源码注释看是build-support/vm:它被用来在 QEMU 虚拟机内构建任意 derivation(通过覆盖替换构建阶段的属性)。

lib.makeOverridable:让任意函数的结果可被覆盖

定位

lib.makeOverridable用来让某个函数的返回结果易于定制。这个工具只对"接受一个属性集、返回一个属性集"的函数有意义。

基本示例

{ f = { a, b }: { result = a + b; }; c = lib.makeOverridable f { a = 1; b = 2; }; }

变量c是函数f以默认参数应用后的值,因此c.result3。同时c上还挂载了额外的函数,例如c.override,它可以用来覆盖默认参数。在本例中,(c.override { a = 4; }).result的值为6

源码实现:overrideoverrideAttrs如何被注入

makeOverridable实现于 lib/customisation.nix(L152-L217)。其核心思路是:

  • 先以原始参数origArgs调用f得到result
  • 定义overrideArgs = mirrorArgs (newArgs: makeOverridable f (origArgs // (if isFunction newArgs then newArgs origArgs else newArgs)))——即override支持两种输入:直接属性集,或接收旧参数的函数;
  • result是属性集时,为其附加:
result // { override = overrideArgs; overrideDerivation = fdrv: makeOverridable (mirrorArgs (args: overrideDerivation (f args) fdrv)) origArgs; ${if result ? overrideAttrs then "overrideAttrs" else null} = fdrv: makeOverridable (mirrorArgs (args: (f args).overrideAttrs fdrv)) origArgs; }

值得注意的细节:

  • overrideAttrs的注入是有条件的:只有当result本身已经带有overrideAttrs(即底层是用stdenv.mkDerivation构建的)时,makeOverridable才会为结果重新添加上overrideAttrs。这样做是为了让overrideoverrideAttrs可以组合使用——overrideAttrs之后的结果仍然保留override方法。正如源码注释所述:"The real implementation ofoverrideAttrsis provided bystdenv.mkDerivation",而makeOverridable中的overrideAttrs只是一个把override重新挂回结果的胶水层;
  • result本身是函数时,makeOverridable会通过setFunctionArgs把它变成 functor 并继续传播参数信息(__functionArgs),同时挂上override
  • f本身是一个可调用的属性集(callable attrset)且自带override时,makeOverridable会保留f上的其他属性,并对f.override再做一层装饰(L207-L214)。

makeOverridable是整个覆盖体系的"发动机":callPackageWith(同文件 L267-L330)在自动填充缺失参数后正是调用makeOverridable f allArgs来产出包,这解释了为什么通过callPackage构造的包都天然携带overrideoverrideAttrsoverrideDerivationmakeScopemakeScopeWithSplicing'(同文件)则在包集合层面(如各语言包集)沿用同一套callPackage/newScope/overrideScope机制。

在 Nix REPL 中验证

makeOverridable的文档注释提供了一个可直接在nix-repl验证的交互示例:

nix-repl> x = {a, b}: { result = a + b; } nix-repl> y = lib.makeOverridable x { a = 1; b = 2; } nix-repl> y { override = «lambda»; overrideDerivation = «lambda»; result = 3; } nix-repl> y.override { a = 10; } { override = «lambda»; overrideDerivation = «lambda»; result = 12; }

可以看到返回结果同时包含overrideoverrideDerivation两个 lambda,而result随覆盖参数正确变化。

测试用例与实战验证

Nixpkgs 仓库内提供了专门的测试文件 pkgs/test/overriding.nix,用lib.runTests对覆盖机制做了系统性验证,是理解各函数行为的绝佳参考。摘录几个关键断言:

  • 多次叠加overrideAttrsrepeatedOverrides-pname断言对pkgs.hello连续两次overrideAttrspname变为"a-better-hello-with-blackjack",证明覆盖是可叠加、可组合的;
  • 只传属性集的覆盖overriding-using-only-attrset断言(pkgs.hello.overrideAttrs { pname = "hello-overriden"; }).pname生效,验证了文档中"省略函数参数"的写法;
  • finalAttrs引用最终值:测试overrideAttrsFooBarBAR = finalAttrs.FOO,断言FOO == "a"BAR == "a",说明finalAttrs中引用的是覆盖后的最终属性集;
  • 覆盖version/src的完整实战buildGoModule-overrideAttrsoverrideAttrs (finalAttrs: previousAttrs: { version = "0.4.0"; src = pkgs.fetchFromGitHub { inherit (previousAttrs.src) owner repo; rev = "v${finalAttrs.version}"; ... }; })把 pet 从 0.3.4 升到 0.4.0,并断言其drvPathnamevendorHash等与直接用buildGoModule构建的 0.4.0 完全一致——这是"正确升级版本必须同时覆盖versionsrc"的最佳范例,正好呼应 make-derivation.nix 中的告警逻辑;
  • override覆盖函数参数buildPythonPackage-override-gccStdenv等测试展示通过override (previousArgs: { buildPythonPackage = previousArgs.buildPythonPackage.override { stdenv = pkgs.gccStdenv; }; })逐层替换依赖中的 stdenv;
  • 覆盖可交换性(commutation)overrideAttrs-overridePythonAttrs-test-commutation断言overrideAttrsoverridePythonAttrs两种覆盖顺序的结果相同,验证覆盖操作之间是良构可组合的。

选型建议与总结

面对不同的定制需求,可以参考以下决策路径:

需求场景推荐工具理由
覆盖包函数的入参(如barSupport = true、替换某个依赖)<pkg>.override直接作用于包函数参数,语义最贴合
修改构建属性(pnameversionsrcseparateDebugInfopatchesbuildInputs等)<pkg>.overrideAttrsstdenv.mkDerivation继续处理输入,支持finalAttrs/previousAttrs,属性名与源码一致
针对最终 derivation 属性的临时 hack(如~/.config/nixpkgs/config.nix<pkg>.overrideDerivation保留能力但注意求值时序陷阱与性能开销
让自定义函数返回的对象具备可覆盖能力lib.makeOverridable底层机制,callPackagestdenv.mkDerivation都构建于其上
在包集合层面统一修改多个包并放回pkgsoverride/overrideAttrs+ Overlay先覆盖单个包,再通过 overlay 组合进固定点

覆盖机制是 Nix 声明式软件分发哲学的核心体现:通过不可变数据结构上的受控"改写",让用户无需 fork 整个包集合即可获得定制化软件。掌握了overrideoverrideAttrsoverrideDerivationlib.makeOverridable四者的差异与协作方式,再配合 lib/customisation.nix、pkgs/stdenv/generic/make-derivation.nix 与 pkgs/test/overriding.nix 中的实现与测试,你就能在 NixOS 配置、Nix 表达式或自定义包集中自如地完成各种精细定制。

  • 包管理器
  • 操作系统

【免费下载链接】nixpkgs

Nix Packages collection & NixOS

项目地址:https://gitcode.com/GitHub_Trending/ni/nixpkgs
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 21:06:32

Textual 0.18.0 并发管理 Worker API:统一管理 asyncio 任务与线程

Textual 0.18.0 并发管理 Worker API&#xff1a;统一管理 asyncio 任务与线程 【免费下载链接】textual The lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser. 项…

作者头像 李华
网站建设 2026/9/19 21:05:34

大语言模型长文本推理优化技术与实践

1. 长上下文推理的挑战与机遇大语言模型在处理长文本时总会遇到一个尴尬局面——当输入内容超过某个临界长度&#xff0c;推理速度就会断崖式下跌。我在实际项目中最常遇到这种情况&#xff1a;法律合同分析需要处理200页PDF&#xff0c;医疗报告总结要解析数十万字的病历记录&…

作者头像 李华
网站建设 2026/9/19 21:02:46

Grafana Tempo 依赖探秘:oklog/ulid Go 实现原理与 ULID 实战指南

Grafana Tempo 依赖探秘&#xff1a;oklog/ulid Go 实现原理与 ULID 实战指南 【免费下载链接】tempo Grafana Tempo is a high volume, minimal dependency distributed tracing backend. 项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo ULID&#xff08…

作者头像 李华
网站建设 2026/9/19 21:01:20

普渡大学实习总结文档工程化指南:从docx生成到面试复用

简介&#xff1a;这份普渡大学个人实习总结文档&#xff0c;面向计划参加海外科研实习、国际交换项目或对跨文化学术体验感兴趣的高校学生与青年研究者。作者通过Iaeste国际学生科技交流计划赴美&#xff0c;在计算基因组学交叉实验室参与QTL数量遗传性状位点分析&#xff0c;围…

作者头像 李华
网站建设 2026/9/19 21:00:55

2026具身大脑产业观察:5家高潜力初创企业,解锁具身智能资本新赛道

2026具身大脑产业观察&#xff1a;5家高潜力初创企业&#xff0c;解锁具身智能资本新赛道随着具身智能产业从技术验证全面迈向商业化落地&#xff0c;行业竞争逻辑发生核心迭代。此前行业聚焦机器人本体形态、运动性能等硬件指标&#xff0c;而2026年产业竞争核心已转向具身大脑…

作者头像 李华