nixpkgs-multiverse: every version that ever existed

Lobsters Hottest 工具

摘要

nixpkgs-multiverse is a Nix flake that provides access to every version of every package that ever existed in Nixpkgs, allowing users to easily pin and mix historical package versions without multiple flake inputs.

<p><a href="https://lobste.rs/s/09l5py/nixpkgs_multiverse_every_version_ever">Comments</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/10 00:52

# nixpkgs-multiverse: every version that ever existed Source: [https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed](https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed) *Enter the Nixpkgs multiverse\. All the versions that ever existed, all in one place\.* I bumped the`nixpkgs`release for my NixOS configuration to refresh many of my packages and found that a package I depended on at a particular version is no longer available\. The package was “version bumped forward” in a way that broke some of my tooling\. It’s late and I don’t want to fix it, so I just add another`nixpkgs`input pinned to the commit that had the version I want\. This works, but it is miserable in a way that compounds\. ``` { inputs = { nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs-25_11.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-25_05.url = "github:NixOS/nixpkgs/nixos-25.05"; }; } ``` The need for the most recent package is so common that I had keep an overlay that would inject`unstable`as a package set for me to easily pull from\. ``` unstable-packages = final: _prev: { unstable = import inputs.nixpkgs-unstable { system = final.stdenv.hostPlatform.system; config.allowUnfree = true; overlays = [inputs.nix-vscode-extensions.overlays.default]; }; }; ``` If I have a need for a particular version of a package and it’s not present in my current`nixpkgs`, I am left searching for the commit and pinning it\.11Thankfully sites like[nixhub\.io](https://www.nixhub.io/)or[lazamar’s search](https://lazamar.co.uk/nix-versions/)make this a little easier\. Every pin is a whole extra`nixpkgs`in the file\. Flake inputs are fetched**eagerly**even if not used\. A flake with three`nixpkgs`inputs whose output references only the*first*are all materialised\. Nix lets us easily create a closure that reproduces a specific version of a package, but Nixpkgs makes it hard to hold one package still while everything else moves\. Each Nixpkgs input to a flake is a distinct universe\. If we can have multiple Nixpkgs as input to achieve fetching a particular package, why not**have every version that ever existed always available**? 🤯 [nixpkgs\-multiverse](https://github.com/fzakaria/nixpkgs-multiverse)is one flake input that gives you**all of them**at once\. ``` $ nix run 'github:fzakaria/nixpkgs-multiverse#versions.python3."3.6.2"' -- --version Python 3.6.2 $ nix run 'github:fzakaria/nixpkgs-multiverse#versions.python3."3.8.9"' -- --version Python 3.8.9 # We can also get the latest version of a package. $ nix run 'github:fzakaria/nixpkgs-multiverse#latest.python3' -- --version Python 3.14.6 ``` We can query the flake for all the versions of a package that**ever existed in Nixpkgs**\. ``` $ nix eval --json --apply 'f: f "python3"' \ github:fzakaria/nixpkgs-multiverse#multiverse.x86_64-linux.versionsOf [ "3.5.3", "3.6.2", # 53 other versions omitted for brevity # ... "3.13.13", "3.14.6" ] ``` If we want a specific complete revision of Nixpkgs we can use the`at`function\. ``` let mv = multiverse.multiverse.x86_64-linux; # newest revision the index knows, as a real Nixpkgs pkgs_tip = mv.tip; # by release pkgs_24_11 = mv.at "24.11"; # newest revision on or before that date pkgs_2022_03_15 = mv.at "2022-03-15"; # by commit pkgs_aae12a743f75 = mv.at "aae12a743f75"; in { packages = [ pkgs_tip.python3 pkgs_24_11.python3 pkgs_2022_03_15.python3 pkgs_aae12a743f75.python3 ]; } ``` That is access to all the versions of all the packages that ever existed in Nixpkgs\. You can mix them all together in one shell, one package or a build environment\. ![lotr meme about having one flake to rule them all](https://fzakaria.com/assets/images/lotr_multiverse.png) How is it possible to have multiple Python versions? That is the whole point of Nix itself\. Every package immaculately describes its dependencies using a hash via the[intensional model](https://fzakaria.com/2025/03/08/demystifying-nix-s-intensional-model)\.22The hash is a unique identifier for the exact set of inputs that were used to build it\. If you change any input, the hash changes and you get a new package\. [multiversecluster\_arevision 2023\-06\-12cluster\_brevision 24\.05cluster\_crevision 25\.05envbuildEnv"three\-pythons"papython3\-3\.10\.11env\-\>papbpython3\-3\.11\.9env\-\>pbpcpython3\-3\.12\.10env\-\>pcbabash\-5\.2\-p15pa\-\>bagaglibc\-2\.37\-8pa\-\>gaoaopenssl\-3\.0\.9pa\-\>oabbbash\-5\.2p26pb\-\>bbgbglibc\-2\.39\-52pb\-\>gbobopenssl\-3\.0\.13pb\-\>obbcbash\-5\.2p37pc\-\>bcgcglibc\-2\.40\-66pc\-\>gcocopenssl\-3\.4\.1pc\-\>oc](https://fzakaria.com/assets/graphviz/c2754b42502fbbb6.svg)Nixpkgs already supports multiple versions of a package in a single revision \(i\.e\.`python39`,`python312`,`gcc12`\) as separate attributes\. We took this to its logical conclusion of making them all available easily\. ## [§](https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed#whats-the-magic)What’s the magic? Our`flake\.nix`deliberately has no inputs:`inputs = \{ \}`\. Inputs are fetched eagerly, and we have 1,393 of them\. We need to fetch them lazily, only when something actually references a revision\. To do this, we fetch revisions with`builtins\.fetchTree`, pinned by`narHash`, only when needed\. Two files do all the work:`revisions\.json`and`versions\.json`\. `revisions\.json`is one ordered array of every revision from[Nixpkgs](https://github.com/NixOS/nixpkgs), 1,393 as of this writing, from 2017 to 2026\.33A NixOS release is not special; it is a commit that happens to carry a`release`label\. ``` [ { "rev": "0eeebd64de89…", "date": "2023-06-12", "channel": "nixos-unstable", "narHash": "sha256-2xT+Jmk3m…" }, { "rev": "afb2b21ba489…", "date": "2025-05-23", "channel": "release", "release": "25.05", "narHash": "sha256-rWtXrcIzU5wm…" } ] ``` We limit our commits to those that were actually built and cached by Hydra, so we only include commits that were either a release or a`nixos\-unstable`channel bump\. How do we know which revisions to pick for the`nixos\-unstable`ones? We rely on the[nix\-releases S3 bucket](https://nix-releases.s3.amazonaws.com/)to tell us which commits actually became published builds\. The S3 bucket uses the commit hash as the directory name, so we can list the bucket and get a complete list of all revisions that were actually built\. `index/versions\.json`is the map from \(attribute, version\) to a revision: ``` { "revisionCount": 1393, "attrs": { "python3": { "3.8.9": 412, "3.12.10": 1204 } } } ``` That integer is an offset into`revisions\.json`\. It is the most recent revision that shipped that version\. ## [§](https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed#sparse-data)Sparse data At this many revisions, it turns out that how you encode the data matters a lot\. My first encoding stored every revision a version appeared in\. Although it was simple, it was a disaster in terms of size for these JSON files\. As you might expect, most versions of most packages are unchanged across many revisions\. The size of our`versions\.json`file was growing linearly with the number of revisions\. By storing only the*newest*revision that shipped a version, we can keep the file small and still answer the question “which revision had this version”\. Here is how it actually grows as revisions get indexed: [1980\-01\-01T00:00:00\+00:00image/svg\+xmlMatplotlib v3\.10\.5, https://matplotlib\.org/](https://fzakaria.com/assets/plotnine/bb1965e463779551.svg)5\.18 MB covering**1,393 revisions**and 289,521 distinct \(attribute, version\) pairs\. ## [§](https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed#performance)Performance The key design rule for our flake: > Cost is per**revision touched**, not per package\. If we were to add revisions as inputs, evaluating our flake would explode\. Each flake in our measurement below has N`nixpkgs`inputs and an output that references**only the first one**; the timing is how long before that output evaluates\.44Everything is`git\+file`against a local clone, so there is no network latency\. [1980\-01\-01T00:00:00\+00:00image/svg\+xmlMatplotlib v3\.10\.5, https://matplotlib\.org/](https://fzakaria.com/assets/plotnine/3ccac799768d6a6d.svg)Five`nixpkgs`pins that are not used cost**26 seconds**before the output evaluates\. Each input costs about 5 seconds, and the input is fetched and materialised even if never used\. In contrast, the green line is`nixpkgs\-multiverse`with**1,393 revisions available**, which is a flat 0\.20s to parse the JSON\. 🤩 Revisions are memoised, so pulling 3 packages out of one revision costs the same as pulling one\. [1980\-01\-01T00:00:00\+00:00image/svg\+xmlMatplotlib v3\.10\.5, https://matplotlib\.org/](https://fzakaria.com/assets/plotnine/db726a030d67b90f.svg)## [§](https://fzakaria.com/2026/08/09/nixpkgs-multiverse-every-version-that-ever-existed#why-i-like-this)Why I like this That concept that the`/nix/store`can hold many graphs of the same package is core to understanding Nix\. The popularity and rise of flakes made it even more apparent that we can mix multiple revisions of Nixpkgs together\. The thing I keep coming back to is that Nixpkgs history*already is*the multiverse\. Every version that ever existed is already built, already cached, already reachable\. It was just addressed by commit hash instead of by version number, which is exactly backwards from how anyone thinks about it\. The whole project is 5 MB of JSON and about 200 lines of Nix\. It does not build anything, mirror anything, or host anything\. It is a phone book\. ``` inputs.multiverse.url = "github:fzakaria/nixpkgs-multiverse"; ``` [1980\-01\-01T00:00:00\+00:00image/svg\+xmlMatplotlib v3\.10\.5, https://matplotlib\.org/](https://fzakaria.com/assets/plotnine/4dcff788eb3cd44a.svg)

相似文章

nixpkgs-multiverse:快速模式

Lobsters Hottest

nixpkgs-multiverse 引入了一种快速模式,跳过 Nixpkgs 评估并直接获取包存储路径,从而可以更快地访问历史包版本。

GuixPkgs:每个 Guix 包,作为 Nix flake

Lobsters Hottest

GuixPkgs 是一个项目,它将每个 GNU Guix 包以 Nix flake 的形式提供,使用户能够在单个 flake 中混合使用 Guix 和 Nixpkgs 包。它使用 guix-transfer 将 Guix 派生转换为 Nix 派生,并提供二进制缓存以避免完全重建 Guix 引导过程。

nixpkgs的圣杯:版本范围

Lobsters Hottest

本文介绍了nixpkgs的版本范围支持,这是一个以前缺乏此类功能的包管理器,使用求解器和历史数据来实现曾经无法表达的依赖约束。

一个 Nix flake 统治一切

Hacker News Top

本文介绍 omniflake,这是一款 Nix 工具,它将数千个 flake 合并为一个输入,以简化依赖管理并提升基于 Nix 系统的用户体验。

Nix Flakes 及其在 Guix 中的对应物

Lobsters Hottest

详细比较 Nix Flakes 与 Guix 包管理系统中的对应物,涵盖依赖声明、锁定、纯净性、输出、开发环境和系统配置。