Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

FAQ

What goes wrong without caisson that this fixes?

Caisson addresses major failure patterns seen in poly-flake setups:

First, input explosion: a flake-parts module that uses inputs.foo works only if every downstream flake also pulls in foo. This ends up meaning that each level of a dependency tree has to re-mention all of its transitive dependencies, either directly or in the form of a bunch of “follow” pins, or else you get an explosion of flake versions.

Second, collisions: Overlays that write top-level attributes tend to fight over the same flat namespace.

What is closure-inputs, and who sets it?

closure-inputs is the sources your flake passes to mkLib: its pinned inputs, under their input names and with no self. inherit (caisson.lib.caisson.pins.flake inputs) sources root; in the mkLib call passes them. The caisson framework threads it in: mkLibOverlay and mkModule apply it to each registered file as the file’s first argument list. A file always receives the sources of the flake that registered it: an overlay or module consumed from another flake sees the sources of the flake it came from, not those of the flake consuming it. Closed inputs is the full convention.

What happens when two overlays define the same thing?

Composition is done via an ordered pass across the specified overlays, traversing dependencies in a depth-first, postfix manner. For attrSets and lists, following the conventions results in a merge. For atomic attributes, contentions mean that the later overlay’s definition wins. I say “convention” because overlays are actually capable of addressing their predecessor directly, so they can technically implement whatever merging logic they deem appropriate.

Can I adopt this incrementally in an existing flake-parts flake?

Yes. lib.caisson.flake-parts.mkTopConfiguration wraps the mkFlake of flake-parts, and plain flake-parts modules work unchanged. It is recommended to start by composing a library with lib.caisson.mkLib. You can hand your existing top-level module to mkTopConfiguration and let the conventions spread file by file from there.

What does lib.caisson.flake-parts.mkModule do to my module?

A few things:

  • applies the closure argument list, so the module can use closure-inputs
  • records the file’s path as _file, for better error messages
  • gives path-registered modules a deduplication key.

Do I pin nixpkgs, home-manager, and the rest myself?

Yes, you manage the ecosystem pins in your own flake, which is the point: a caisson integration is glue over the ecosystem’s evaluator, composing with whatever ecosystem source version you give it, so caisson imposes no transitive pins and two consumers of the same caisson revision can run different nixpkgs revisions. Version skew within your ecosystems lives in your locks, where you can see and manage it. A flake that uses one version of an ecosystem everywhere can set a flake-level default at mkLib, a function of sources (defaultEcosystemSrc = sources: { nixos = sources.nixpkgs; };). It is also possible to pass ecosystemSrc, also a function of sources, explicitly on each caisson call. The explicit argument wins, and a source named exactly like the key is the final fallback (handy for leaf nodes).

What does this cost at evaluation time?

Measurements show: relatively little. The eval-weight harness runs in CI and holds caisson’s overhead (a minimal caisson consumer minus a raw flake-parts flake) to ceilings on deterministic counters: thunks, values, allocations, and full nixpkgs, nixpkgs-lib, and module-system evaluation counts. The overhead is orders of magnitude below a single nixpkgs evaluation. Evaluation weight documents the harness and the current numbers.

How mature is this?

Pre-release. At the time of writing, it is used heavily by the author and by no one else.