# nixpkgs-多元宇宙:快速模式
来源:https://fzakaria.com/2026/08/14/nixpkgs-multiverse-fast-mode
> *“最快的评估,是永远不发生的评估。”* ——孙子,《评估的艺术》
nixpkgs-多元宇宙 (https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed) 让你可以通过一个单一的 flake 输入,获取 Nixpkgs 中所有曾经发布过的每个软件包的所有版本。
> **注意** 我仍然惊叹于这竟然是可行的。这感觉像是突然间解锁了 Nixpkgs 的一个新维度,而我仍在努力理解这意味着什么。我认为这种能力从根本上改变了我们思考 Nixpkgs 的方式,它不仅仅是一个新功能,而是一种思考整个生态系统的新方式。
这一切的核心曾存在一个代价:请求 `python3` 的特定版本(例如 `3.8.9`)意味着需要获取 2021 年整个约 378 MB 的 Nixpkgs 源码树,并对其进行评估以确定 `outPath`。
如果我们能跳过那个评估呢?如果我们能直接请求路径,让 Nix 在缓存中存在该路径时直接获取它呢?
如果你曾使用过 `nix-store`,这是一个常见的模式:
```
# 检查存储是否已有该路径
$ nix path-info --store https://cache.nixos.org/ \
/nix/store/6cfajs6lsy9b4wxp3jvyyl1g5x2pjmpr-python3-3.8.9
/nix/store/6cfajs6lsy9b4wxp3jvyyl1g5x2pjmpr-python3-3.8.9
# 如果有,获取它
$ nix-store --realise /nix/store/6cfajs6lsy9b4wxp3jvyyl1g5x2pjmpr-python3-3.8.9
/nix/store/6cfajs6lsy9b4wxp3jvyyl1g5x2pjmpr-python3-3.8.9
```
但这需要预先知道存储路径。
nixpkgs-多元宇宙 (https://github.com/fzakaria/nixpkgs-multiverse) 现在提供了一个 `fast` 属性,它正是做这件事:为每个软件包的每个已索引版本提供存储路径。这让你能跳过下载和评估 Nixpkgs 的过程,直接从缓存获取存储路径。
```
$ nix build 'github:fzakaria/nixpkgs-multiverse#fast.versions.python3."3.8.9".out' \
--print-out-paths
/nix/store/6cfajs6lsy9b4wxp3jvyyl1g5x2pjmpr-python3-3.8.9
$ nix shell 'github:fzakaria/nixpkgs-multiverse#fast.versions.python3."3.8.9".out'
$ python3 --version
Python 3.8.9
```
没有 Nixpkgs 被获取,没有发生任何评估。此功能的运行无需实验性特性,也无需 `--impure` 参数。
除了发布版本外,完整的 Nix API 都适用于此快速路径。
```
# 特定版本,零评估
mv.fast.version "python3" "3.8.9"
# 指定版本时最新的已索引版本
mv.fast.latest.python3
# 版本锁定生效时的当前版本
mv.fast.tip.hello
# 整个修订版本,作为伪构建
mv.fast.at "2022-03-15"
# 精确的修订键也能工作
mv.fast."967d40bec14b".python3
```
如果你想了解更多,请阅读关于此功能的文档 (https://nixmultiverse.com/docs/nix-api#the-fast-path)。
## § (https://fzakaria.com/2026/08/14/nixpkgs-multiverse-fast-mode#the-trick-mkfakederivation) 诀窍:`mkFakeDerivation`
每个 `nixos-unstable` 频道更新都会发布一个列表,列出 Hydra 为其构建的每个路径:`store-paths.xz`,或者在 2017 年之前的版本中使用 `MANIFEST`。这些文件仍然可用,它们是多元宇宙索引的来源。
该列表是从派生名称到存储路径的映射。多元宇宙索引是一个从 `(属性,版本)` 到其发布版本的映射。通过连接两者,每个历史版本都获得了一个具体的地址。
仅知道路径是不够的,尤其是在 Nix 语言中。我们需要让 Nix 相信一个看起来像存储路径的字符串*确实是一个*存储路径。`builtins.storePath` 存在,但它是一个 impure 函数,需要 `--impure` 才能工作。
我们如何绕过这一点?
我们为字符串附加“上下文”。上下文是字符串在 Nix 中携带的无形负载。当你在字符串中插值一个派生时,结果会记住它的来源,这就是让 `nix build` 实现依赖关系,而不是将悬空路径写入脚本的原因。
`builtins.appendContext` 允许你手动附加它。
```
storePath = p: builtins.appendContext p { ${p} = { path = true; }; };
```
`path = true` 标识“这个字符串命名一个必须存在的存储路径”,这正是 `builtins.storePath` 对于已存储在你的存储中的路径所做的,但这种方法适用于尚未在你的存储中且也不在此次评估输入闭包中的路径。循环漏洞!👿
然后我们将它包装在一个类似于派生的属性集中,Nix CLI 就会满意:
```
{
type = "derivation";
name = "python3-3.8.9";
pname = "python3";
version = "3.8.9";
system = "x86_64-linux";
outputs = [ "out" ];
out = storePath "/nix/store/6cfajs6lsy9b4wxp3jvyyl1g5x2pjmpr-python3-3.8.9";
}
```
这是 tomberek (https://github.com/tomberek) 在 fastpkgs (https://github.com/tomberek/fastpkgs) 中的 `mkFakeDerivation` 技巧,这是一个惊人的技巧,可以规避对 `--impure` 的需求。
这一切都保持纯粹的评估,并且保证生成的图与 Nixpkgs 如果被评估后所产生的结果逐位相同。唯一的区别是我们跳过了 Nixpkgs 本身的评估,而是直接使用存储路径。
评估路径*推导*地址,快速路径*记住*地址。
一个“伪”(`mkFakeDerivation`)派生没有 `drvPath`,因为它背后没有 `.drv`。没有东西可以构建它,它只能被替换。然而,当你传递一个派生属性集时,`nix` CLI 通常需要 `drvPath`,所以我们必须确保追加输出(即 `.out`):
```
$ nix build 'github:fzakaria/nixpkgs-multiverse#fast.latest.hello.out'
$ nix build 'github:fzakaria/nixpkgs-multiverse#fast.latest.ffmpeg.lib'
```
`override` 和 `nix develop` 需要一个真正的派生。每个伪派生都带有一个惰性的 `.eval`,即真实的、与修订版本完全对应的派生:
```
(mv.fast.version "python3" "3.8.9").eval.override { ... }
```
本着尽量保持索引小巧的精神,`meta` 是空的,因此没有关于软件包的额外信息。你仍然可以通过使用 `.eval` 来获取真实派生的 `meta`。
此方案依赖于 cache.nixos.org (https://cache.nixos.org/) 仍然提供十三年前的路径,谢天谢地它确实还在提供,并且使用相同的签名密钥。
为了证明缓存提供了 Nixpkgs 曾构建过的几乎每一个路径,我对每个软件包的每个已索引版本进行了普查,并询问缓存它是否仍然存在。
截至 2026 年 8 月 14 日,**所有 271,187 个版本都仍然存在**。全部,精确到每个 NAR 负载文件。这是自 2013 年以来的 14.8 TB 已解压软件,只需一个*快速*命令即可获取。11 NixOS 基础设施从未对二进制缓存进行过垃圾回收。它是一个只增不减的 S3 存储桶,费用由 NixOS 基金会 (https://nixos.org/community/) 及其赞助商支付。
nixmultiverse.com (https://nixmultiverse.com/) 上有一些关于此次普查、依赖图和其他功能的数据。请查看!
nixmultiverse.com 宇宙滑块截屏 (https://fzakaria.com/assets/images/multiverse-universe-slider.mp4) 所有这些也可以通过 mvs (https://nixmultiverse.com/docs/cli) 命令行工具用于离线使用。
```
# 它有多大,已解压、已下载、以及完整闭包的大小
$ mvs size
[email protected]
python3 3.8.9 · /nix/store/6cfajs6lsy9b4wxp3jvyyl1g5x2pjmpr-python3-3.8.9
nar (解压后) 50.1 MiB
下载 10.6 MiB
闭包 93.8 MiB · 16 个路径
缓存 存在
# 谁链接到了它
$ mvs rdeps pcre2
pcre2 10.47 · 被 255 个已索引软件包引用
# 我的存储中的这个路径到底是什么
$ mvs identify /nix/store/8qi947kixhz1nw83dkwxm6d0wndprqkj-hello-2.12.2
软件包 hello 2.12.2
# 默认使用快速路径运行
$ mvs run
[email protected]
Hello, world!
```
我想现在有一个注意事项:多元宇宙中存在一个诀窍。它主要仍然是一个索引、一些 JSON,以及一个备忘表后面的 `fetchTree`。这个巧妙的技巧是 tomberek (https://github.com/tomberek) 的,它是三行*重要*的代码。