There is already an object we commonly use that takes a package set as an input in a very flexible way, overlays. They are already intended to be used by a consumer without regard for the Nixpkgs of the upstream flake. Notice that overlays don’t hard-code a system either. Overlays also shouldn’t depend on any inputs, just on final/prev, making them quite independent. But they are complicated.
The first few flakes we saw were awkward, and then we had a proliferation of flake libraries intended to hide the boilerplate, and “system” and then add niceties. These have had a chance to explore the design space. I think it comes down to two difficult to grok concepts, overlays and callPackage.
If everyone implemented their flakes with proper overlays, well partitioned and organzied, and if everyone including beginers understood overlays, we might be in a better position. Instead we end up with packages that are hard to re-use elsewhere, hard-coding them into modules, and so forth. People are even forced into this situation because it is easier than the alternative. It is also the case the overlays are just inherently complicated, and useful, and hard to learn, and have too many footguns. And we made the correct behavior require expertise, but the easy behavor damaging in the long run.
In some sense, the problem of too many Nixpkgs means that there was also a need, and that people are leveraging this capability, but without the tooling or the idioms to guide them.
I dont think merely having people use niv or raw getFlake is the answer. I suspect we would end up in the same position, with yet another form of having multiple nixpkgs, but now less structured. At least flake inputs are all in one place, localizing the problem and allowing for tooling to handle them. I do want to eventually get back to a proposal from last year about updating how lockfiles treat transitive locks (Reuse input lock files · Issue #7730 · NixOS/nix · GitHub).
One approach I’ve been enjoying is twofold, based around the notion of a “recipe”, a function that produces a derivation (pkg-fun, the default.nix 's you find all over Nixpkgs, a callPackage-able, etc):
-
Remove the need to understand super/self/final and callPackage in 95% of cases. Most people just want to add a leaf package or do a simple override. Not some deep compiler replacement or cross-compilation. Let people define an attribute set/tree, where each value is a recipe. There is an fairly mechanical translation of this form into overlays that adds callPackage and does the right fixed-point magic.
-
Re-use. These more limited forms are easier to handle than overlays, less footguns and less power. Attrsets of recipes can be manipulated predictably and composed together without needing to understand fixed points. They can be grabbed from a flake without reading the upstream lockfile. One can enforce this by a “flake = false” (perhaps that becomes the default?) and passing dummy values as inputs, if it errors it means someone’s overlay accessed a package value directly instead of doing proper composition.
None of this requries any technical change. There is work that can make it nicer; and tooling + templates + guides to make it understandable. Farid was making a tool to more easily manage follows statements, simply having a better interfaces to visualize and manage inputs might go a long way.