尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

Nixpkgs 包覆盖机制深度解析:`override`、`overrideAttrs`、`overrideDerivation` 与 `lib.makeOverridable` 包管理器操作系统【免费下载链接】nixpkgsNix 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构造的包天然带有override、overrideAttrs等方法。语义与注意事项在第一个示例中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的含义如果不需要访问previousAttrs或finalAttrs两个参数都可以完全省略。只传属性集的形式{ helloWithDebug pkgs.hello.overrideAttrs { separateDebugInfo true; }; }在上述例子中separateDebugInfo属性被覆盖为true从而为helloWithDebug构建调试信息。此时没有使用函数形式直接传入属性集覆盖语义等价于只写一个参数且仅使用 previousAttrs。为什么overrideAttrs优于overrideDerivation文档中特别强调了一个关键点separateDebugInfo只会被stdenv.mkDerivation函数处理而不会被生成后的原始 Nix derivation处理。因此若改用overrideDerivation它将只覆盖最终 derivation 的属性separateDebugInfo根本不会被处理此例中也就无法生效。这就是在几乎所有情况下都应优先使用overrideAttrs而非overrideDerivation的原因让stdenv.mkDerivation继续处理输入参数如separateDebugInfo这类高阶选项使用起来更容易可以直接使用你在 Nix 代码中看到的属性名例如buildInputs而不必使用生成后的名字如nativeBuildInputs对应的生成形态输入更少、代码更简洁。源码层面的实现印证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: ...)这与文档中的说明完全一致。同时finalPackage与overrideAttrs会被注入递归属性集args中args rattrs (args // { inherit finalPackage overrideAttrs; })从而支持finalAttrs引用最终结果。此外源码中还内置了一个非常实用的告警warning当你在overrideAttrs中覆盖了version却没有同时覆盖src时构建时会打印提示建议同时覆盖version和src例如借助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 的name、src、patches会被覆盖而其他所有属性会从原 derivation 保留。参数oldAttrs用来引用原 derivation 的属性集。关键陷阱属性求值先于覆盖包的属性是在被 overrideDerivation 修改**之前**求值的。例如url mirror://gnu/hello/${name}.tar.gz; 中的 name 引用会在 overrideDerivation 修改属性集之前被填入。这意味着在此例中仅覆盖 name 属性**不会**改变 url 的值——你必须同时覆盖 name 和 url 两个属性。这个求值时序问题正是overrideDerivation相对难用的根源之一也是文档建议在绝大多数场景转向overrideAttrs的重要原因。源码实现overrideDerivation定义于 lib/customisation.nixL99-L112overrideDerivation 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.drvAttrs与f 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.result为3。同时c上还挂载了额外的函数例如c.override它可以用来覆盖默认参数。在本例中(c.override { a 4; }).result的值为6。源码实现override与overrideAttrs如何被注入makeOverridable实现于 lib/customisation.nixL152-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。这样做是为了让override与overrideAttrs可以组合使用——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构造的包都天然携带override、overrideAttrs、overrideDerivation。makeScope与makeScopeWithSplicing同文件则在包集合层面如各语言包集沿用同一套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; }可以看到返回结果同时包含override与overrideDerivation两个 lambda而result随覆盖参数正确变化。测试用例与实战验证Nixpkgs 仓库内提供了专门的测试文件 pkgs/test/overriding.nix用lib.runTests对覆盖机制做了系统性验证是理解各函数行为的绝佳参考。摘录几个关键断言多次叠加overrideAttrsrepeatedOverrides-pname断言对pkgs.hello连续两次overrideAttrs后pname变为a-better-hello-with-blackjack证明覆盖是可叠加、可组合的只传属性集的覆盖overriding-using-only-attrset断言(pkgs.hello.overrideAttrs { pname hello-overriden; }).pname生效验证了文档中省略函数参数的写法finalAttrs引用最终值测试overrideAttrsFooBar中BAR finalAttrs.FOO断言FOO a且BAR a说明finalAttrs中引用的是覆盖后的最终属性集覆盖version/src的完整实战buildGoModule-overrideAttrs用overrideAttrs (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并断言其drvPath、name、vendorHash等与直接用buildGoModule构建的 0.4.0 完全一致——这是正确升级版本必须同时覆盖version与src的最佳范例正好呼应 make-derivation.nix 中的告警逻辑override覆盖函数参数buildPythonPackage-override-gccStdenv等测试展示通过override (previousArgs: { buildPythonPackage previousArgs.buildPythonPackage.override { stdenv pkgs.gccStdenv; }; })逐层替换依赖中的 stdenv覆盖可交换性commutationoverrideAttrs-overridePythonAttrs-test-commutation断言overrideAttrs与overridePythonAttrs两种覆盖顺序的结果相同验证覆盖操作之间是良构可组合的。选型建议与总结面对不同的定制需求可以参考以下决策路径需求场景推荐工具理由覆盖包函数的入参如barSupport true、替换某个依赖pkg.override直接作用于包函数参数语义最贴合修改构建属性pname、version、src、separateDebugInfo、patches、buildInputs等pkg.overrideAttrs让stdenv.mkDerivation继续处理输入支持finalAttrs/previousAttrs属性名与源码一致针对最终 derivation 属性的临时 hack如~/.config/nixpkgs/config.nixpkg.overrideDerivation保留能力但注意求值时序陷阱与性能开销让自定义函数返回的对象具备可覆盖能力lib.makeOverridable底层机制callPackage、stdenv.mkDerivation都构建于其上在包集合层面统一修改多个包并放回pkgsoverride/overrideAttrs Overlay先覆盖单个包再通过 overlay 组合进固定点覆盖机制是 Nix 声明式软件分发哲学的核心体现通过不可变数据结构上的受控改写让用户无需 fork 整个包集合即可获得定制化软件。掌握了override、overrideAttrs、overrideDerivation与lib.makeOverridable四者的差异与协作方式再配合 lib/customisation.nix、pkgs/stdenv/generic/make-derivation.nix 与 pkgs/test/overriding.nix 中的实现与测试你就能在 NixOS 配置、Nix 表达式或自定义包集中自如地完成各种精细定制。赞分享包管理器操作系统【免费下载链接】nixpkgsNix Packages collection NixOS项目地址https://gitcode.com/GitHub_Trending/ni/nixpkgs点击查看免费下载相关推荐定制 nixpkgs 包NixOS 中 nixpkgs.config、override、overrideAttrs 与 packageOverrides 全实战定制 nixpkgs 包NixOS 中 nixpkgs.config、override、overrideAttrs 与 packageOverrides 全实包管理器操作系统nixpkgs 中的 Nim 包构建指南buildNimPackage、buildNimSbom 与 lockfile 覆盖机制nixpkgs 中的 Nim 包构建指南buildNimPackage、buildNimSbom 与 lockfile 覆盖机制 本文基于 nixpkgs 官包管理器操作系统NixOS与Flakes技术手册Nixpkgs包覆写机制详解NixOS与Flakes技术手册Nixpkgs包覆写机制详解 前言 在Nix生态系统中包覆写 Overriding 是一项强大的功能它允许开发者在不修改原创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表