☰
nixpkgs 中 nix/modular 模块化打包机制详解:workDir 与组件分层原理
2026/10/9 1:35:35 网站建设 项目流程
  • 包管理器
  • 操作系统

【免费下载链接】nixpkgs

Nix Packages collection & NixOS

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

本文围绕 nixpkgs 仓库中pkgs/tools/package-management/nix/modular/目录的说明文档展开,深入讲解 Nix 官方包的模块化(modular)打包方案:该目录如何复刻上游 Nix 仓库的目录结构、workDir属性如何决定子项目的源码路径、为什么对 fetch 型源码不能使用 filesets(IFD 约束),以及mkMesonExecutable/mkMesonLibrary分层机制与nix-everything组装包的完整原理。读完本文,你可以独立阅读 nixpkgs 中 Nix 的模块化表达式,理解每个组件派生(derivation)的生成过程,并能正确使用overrideSource、appendPatches、overrideAllMesonComponents等覆盖接口。

目录定位:复刻上游结构以方便对照

nixpkgs 对 Nix 官方包的模块化打包位于 pkgs/tools/package-management/nix/modular,其目录说明见 README.md。README 明确了两条设计原则:

  1. 目录结构与上游仓库保持一致。该目录刻意模仿上游 Nix 仓库的布局(src/下每个子目录对应一个构建组件,packaging/存放打包逻辑,tests/、doc/分别存放功能测试与手册构建表达式),目的是让维护者在对照上游时“更容易比较”(to make comparisons easier)。
  2. 文件独立维护,差异在所难免。这些打包表达式与上游仓库分开维护,因此两边出现差异是预期内的。

从仓库实际结构看,modular 目录 下src/中每个子目录都只有一个package.nix,分别对应 Nix 的各个构建组件:libutil、libutil-c、libstore、libstore-c、libfetchers、libexpr、libflake、libmain、libcmd、nix(CLI 本身)、nswrapper、perl、internal-api-docs、external-api-docs等,以及tests/functional与doc/manual。而packaging/目录包含两个关键文件:components.nix(定义所有组件的构建逻辑与 scope 接口)和 everything.nix(把所有组件拼装成最终的多输出nix包)。

包入口:独立的 splice scope

组件集合的入口是 packages.nix。它的做法是调用lib.makeScopeWithSplicing'创建一个独立 scope,把 components.nix 作为f注入,并将splicePackages、nixDependencies.newScope、otherSplices等参数传入:

{ lib, splicePackages, nixDependencies, pkgs, teams, otherSplices, version, src, }: let officialRelease = true; # 新建一个 scope,用 callPackage 注入组件间内部依赖, # 而不"污染"顶层的 pkgs 属性集 nixComponents = lib.makeScopeWithSplicing' { inherit splicePackages; inherit (nixDependencies) newScope; } { inherit otherSplices; f = import ./packaging/components.nix { inherit lib teams officialRelease pkgs src version; }; }; in nixComponents.overrideSource src

源码注释解释了两个动机:其一,独立 scope 可以用callPackage注入组件之间的内部依赖(例如nix-cli依赖nix-store、nix-expr),而不会污染顶层pkgs属性集;其二,得到一个可以整体迭代的独立包集合。入口最后一行nixComponents.overrideSource src说明:默认构建就使用外层传入的 fetch 型src,而不是 fileset 本地源码。这也是 README 中“无 filesets”讨论的落点。

workDir属性:子项目源码路径如何计算

README 的第二个比较点是workDir属性:Nixpkgs 版的 Nix 打包继承了workDir属性,它决定“要构建的子项目(subproject)位于何处”,并与modular根目录比较以生成正确的相对路径,这一点与上游一致。

在仓库中,每个组件的package.nix都用一行workDir = ./.;声明自己的位置,例如 src/libutil/package.nix 与 src/nix/package.nix。真正消费workDir的逻辑在 components.nix 的localSourceLayer中:

localSourceLayer = finalAttrs: prevAttrs: let workDirPath = prevAttrs.workDir; # 注意:取 prevAttrs 而非 finalAttrs workDirSubpath = lib.path.removePrefix root workDirPath; # 相对 modular 根目录的子路径 src = lib.fileset.toSource { fileset = prevAttrs.fileset; # 断言 fileset._type == "fileset" inherit root; }; in { sourceRoot = "${src.name}/" + workDirSubpath; # 关键:定位到子目录 inherit src; # 清理 mkDerivation 无法序列化的属性 fileset = null; workDir = null; };

要点拆解:

  • root是modular目录本身(root = ../.;),workDirPath是组件package.nix所在的绝对路径;lib.path.removePrefix root workDirPath得到子路径(如src/libutil),再拼到src.name之后形成sourceRoot。这等效于“解压整棵源码树,但只把 stdenv 的工作目录指到子项目里”,与上游 meson 子项目构建的约定一致。
  • 源码用注释解释了一个微妙点:取的是prevAttrs.workDir而不是finalAttrs.workDir。原因是mkDerivation要求除passthru和meta之外的一切属性都必须可被序列化,而workDir这类路径属性不能直接进 derivation 输入,所以最终层把它置为null清除。
  • 对 fetch 型源码,存在对应的 makeFetchedSourceLayer,逻辑相同但源码来自finalScope.patchedSrc(打过补丁的完整源码),并会按补丁数量给版本号追加+N后缀(如2.30+2),以区分打过补丁的构建。

为什么不对 fetch 源码使用 filesets:IFD 约束

README 的第一个比较点解释了“无 filesets”(No filesets):如果要在 fetch 型源码上使用 filesets,会引入 IFD(Importing Fetched Datas,导入获取的数据)问题——fetch 发生在 derivation 里(构建时),而 fileset 的过滤必须发生在之后,且只能由求值器(evaluator)完成。求值器无法感知一个构建期才产生的 store 路径的内容,因此无法为其定义 fileset。

仓库中的实现印证了这个约束,overrideSource的文档注释写明:“This allows the expressions to be vendored without copying the sources, but it does make the build non-granular; all components will use a complete source. Filesets in the packaging expressions will be ignored.”(见 components.nix)。也就是说,一旦切换到 fetch 源码(默认路径),所有组件都使用完整源码树,fileset 声明被忽略,只靠sourceRoot做子目录定位。

overrideSource的实现细节同样值得注意:

  • 它把 scope 里的sourceLayer换成makeFetchedSourceLayer,并清空patches;
  • 若调用了appendPatches(appendPatches 会先执行overrideSource "${./..}"再追加补丁),则用stdenvNoCC.mkDerivation+srcOnly造出一个patchedSrc,供所有组件共享,避免每个组件重复打补丁;
  • resolvePath与filesetToSource被改写:前者把路径解析为patchedSrc + 子路径,后者退化为{ root, fileset }: finalScope.resolvePath root,即 fileset 参数在 fetch 模式下完全不参与选源。

filesetToSource在 scope 里是一层“间接引用”(components.nix 注释:Indirection for Nixpkgs to override when package.nix files are vendored),供下游在 vendoring 打包表达式时覆盖选源行为。

分层装配:mkMesonExecutable / mkMesonLibrary 的构建层

每个组件派生并非直接写stdenv.mkDerivation,而是通过 components.nix 中的mkPackageBuilder把一组 overlay 形状的分层扩展函数组合到一个用户函数上:

mkPackageBuilder = exts: userFn: stdenv.mkDerivation (lib.extends (lib.composeManyExtensions exts) userFn);

三种构建器按用途组合不同的层(components.nix):

构建器层顺序用途
mkMesonDerivationnixDefaultsLayer→sourceLayer→setVersionLayer→mesonLayer→fixupStaticLayer→mesonComponentOverrides通用 meson 派生(如文档、测试)
mkMesonExecutable上述 +bsdNoLinkAsNeeded、mesonBuildLayer可执行文件(nix-cli、nswrapper)
mkMesonLibrary上述 +mesonLibraryLayer共享库(nix-util等)

各层职责(均以源码为准):

  • nixDefaultsLayer:strictDeps默认开启、enableParallelBuilding、pos从pname位置注入;meta默认值(homepage、donationPage、按 majorMinor 版本生成的 changelog 链接、lgpl21Plus许可、unix ++ windows平台集)见 components.nix。
  • setVersionLayer:Nix 上游用仓库根的.version文件(由符号链接./.version指向)传递版本,构建时只有workDir可写,因此该层在preConfigure中chmod u+w ./.version并echo ${finalAttrs.version} > ./.version(components.nix)。
  • mesonLayer:显式设mesonBuildType = "release"(因为 meson setup-hook 在未指定时默认plain,而 Nix 构建的二进制应默认优化);非 Windows/Cygwin 且非静态目标下按mesonBuildType环境变量决定是否追加-Db_lto=true;注释特别指出故意读取环境变量而非finalAttrs.mesonBuildType,以便调试者按上游手册指引通过环境变量控制构建类型。
  • mesonLibraryLayer:Nix 2.32+ 为 GCC 加-fno-semantic-interposition -Wl,-Bsymbolic-functions(源码注释解释:GCC 默认对位置无关代码禁用内联,放弃 LD_PRELOAD 能力换取优化;Clang 则默认就内联),并把dev加入outputs。
  • fixupStaticLayer/bsdNoLinkAsNeeded:分别处理静态 stdenv 的propagated-build-inputs污染 hack,以及 BSD 上 meson 的--as-needed链接行为问题。
  • mesonComponentOverrides:保留给用户的扩展位,与overrideAllMesonComponents对应。

以 src/libutil/package.nix 为例,nix-util组件用mkMesonLibrary声明依赖(brotli、libsodium、openssl,2.27+ 加 libblake3,2.35pre 加 zstd,x86_64 加 libcpuid),传播 boost、libarchive、nlohmann_json,并用lib.mesonEnable "cpuid"按平台开关。CLI 组件 src/nix/package.nix 则展示了条件依赖的典型写法:buildInputs固定包含nix-store、nix-expr、nix-main、nix-cmd;当版本 ≥ 2.35 且withPluginCApi开启时追加各-cC API 库(让插件能从可执行文件解析符号,无需直接链接 Nix 库);withMimalloc在非 Windows 平台默认开启,注释称其可显著提升分配密集工作负载的求值性能。

nix-everything:把组件拼成最终多输出包

nix-everything组件由 everything.nix 定义,是所有组件派生的“组装壳”:

  • 多输出:outputs = [ "out" "dev" "doc" "man" ]。源码注释解释为何不用各组件原生的多输出直接透传:包括 Nix 自身在内,不少工具尚不能处理由任意输出组合的包;devdoc则通过passthru提供而非成为输出。
  • 不做实际构建:dontUnpack = true(解压由组件完成)、dontBuild = true、dontFixup = true(该派生不编译不链接,且 fixupPhase 目前不认为符号链接输出不可写)。
  • 测试门控:doCheck = true时,checkInputs聚合各库的单元测试运行器(nix-util-tests.tests.run、nix-store-tests.tests.run、nix-expr-tests.tests.run、nix-fetchers-tests.tests.run、nix-flake-tests.tests.run)与nix-functional-tests;旧版本(< 2.35pre,且非静态、build 可执行 host)还会纳入nix-perl-bindings(everything.nix)。
  • installPhase 用lndir合并:把nix-cli合并进$out、把各库的dev输出合并进$dev,并把nix-manual、nix-manual.man以符号链接转发到$doc/$man;Linux 且 ≥ 2.34pre 时额外并入nix-nswrapper。
  • passthru 接口:passthru.libs暴露全部nix-*-c等库(注释推荐使用-cC API 库并建议用完整包跑一遍单元测试);passthru.tests.pkg-config用testers.hasPkgConfigModules校验元数据;meta.pkgConfigModules列出nix-cmd、nix-expr、nix-flake、nix-store、nix-util等十余个模块(2.29pre+ 追加nix-fetchers-c)。

覆盖与扩展接口:如何对整棵 Nix 源码打补丁或换源

scope 层面提供了四个对外接口(在 components.nix 定义,并在 nix-everything 包装 上重新暴露):

  • overrideAllMesonComponents f:对每个组件派生应用一个扩展函数(overlay 形状)。components.nix中有一个刻意的注释:nix-everything本身不携带passthru.overrideAllMesonComponents之类属性,因为那会传播进nix.overrideAttrs f却在调用.overrideAllMesonComponents时被丢弃——两种机制应是同一个不动点覆盖机制的两个视图,作者明确“有意不支持这个坏掉的双不动点方案”。
  • overrideSource src:整体替换源码(vendoring 打包表达式而不拷贝源码的场景),副作用是所有组件改用完整源码、失去 fileset 粒度。
  • appendPatches ps:对整棵 Nix 源码追加补丁,影响所有组件;注意此模式下“对打包表达式的修改会被忽略”。
  • overrideScope f:直接覆盖包集合内部属性,文档示例为overrideScope (finalScope: prevScope: { aws-sdk-cpp = null; })。

版本条件在代码里通过whenAtLeast表达:如nix-fetchers-c仅在 2.29pre+ 提供、nix-nswrapper在 2.34pre+ 提供、nix-perl-bindings在 2.35pre+ 置为null(components.nix),这些条件与 Nix 上游各特性落地版本一一对应。

验证与延伸路径

理解本方案后可以沿以下路径在仓库中核对细节:组件层定义见 packaging/components.nix;组装包见 packaging/everything.nix;各组件声明见 src 目录(如 src/libutil/package.nix、src/nix/package.nix、src/libstore/package.nix);测试聚合见 tests/functional/package.nix。上层目录的 pkgs/tools/package-management/nix/README.md 则描述了 Nix 新版本测试流程(nixpkgs-review pr、nix-build nixVersions.nix_$version.tests等命令),与本模块化表达式共同构成 Nix 在 nixpkgs 中的完整维护链路。

  • 包管理器
  • 操作系统

【免费下载链接】nixpkgs

Nix Packages collection & NixOS

项目地址:https://gitcode.com/GitHub_Trending/ni/nixpkgs
点击查看免费下载
上一篇:Bitcoin Core GUI 导出只读钱包文件:从菜单操作到 ExportWatchOnlyWallet 实现原理
下一篇:终极Mac散热解决方案:smcFanControl完全指南 - 释放Intel Mac的散热潜能

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询