Improving flakes

Why?

I’m starting to sense that the community is potentially at risk of one day fracturing into two distinct factions the longer that flakes linger as an unstable novelty. On the one hand, flakes offer an integrated approach to a problem that many people have tried to solve using quite a few external tools introduced throughout the years. For those who have jumped on the bandwagon, flakes, while far from perfect, seem like the proper approach, at least in theory.

After all, why should we delegate revision management and modularization to an external tool when this is really a core language problem? For many, myself included, using an external tool just feels like the wrong way to do things, and we’ve taken up an active role in evangelizing flake usage, if for no other reason than to flesh out their problems and bring a stronger final product to a stable release.

On the other hand, it’s clear there are some warts that make using flakes quite unattractive for long timer nixers, and their complaints are far from irrelevant. Many respected members of the community seem to have taken up quite a negative view on flakes, and their stance seems justified in many respects.

Rather than letting the division fester and grow into something more sinister, why not attempt to bring these two viewpoints together and find a compelling solution that addresses the problem of flakes head on?

How?

A nice end goal for this thread would be some form of draft RFC to fix what currently urks the community about flakes so we can finally have a proper solution to the problems that they try to address. The faster we can get a solution into a stable release, the less chance we have of something like a hard fork dividing the community.

Of course solving what’s wrong with flakes isn’t a trivial problem, and that’s probably got something to do with why many of us have put it off until now. I’ll try to kick things off with a few of the issues I’ve experienced with flakes since they were first introduced.

I understand time is precious, and especially for those not to invested in flakes, it may not seem worth the effort, but voicing your opinion here will give us more insight into how to properly address this going forward.

What?

I’ll start with a list of a few things I really feel need to be improved before a stable release is cut:

  • While flakes work fairly nicely at the top-level of a project, they really fall short when attempting to use them from inside the project as a sort of module layer. If you put in enough (quite a bit of effort really), you can actually do some pretty cool things with subflakes, like modularizing your dependency structure, i.e. inputs. This allows a cleaner solution to the miriad of fetch* functions in nixpkgs, as one can simply point the src of a derivation to an input.

    However, the interface is anything but intuitive at the moment. One must continually specifying flake = false for every input that you wish to use as a package source. I’m starting to think that maybe we should have a separate notion entirely to complement flakes. Something that is very similar to a flake.nix but isn’t directly accessible to the outside world the way subflakes are currently.

    Maybe there could even be a few different types of interal subflakes (or modules if you prefer), all hidden from the outside world by default and cleanly referencable from the top-level flake.nix. A sources.nix for packages sources, a mod.nix for interal library functions, a pkgs.nix for logically related package definitions, etc. Each one should be known to Nix in advance and easily referenced in a flake url. Maybe you could name the file whatever you want, and specify it’s module type in the flake uri itself, e.g. "pkgs:some/path/to/myPkgsModule.nix".

    Solving this problem nicely might put the argument to rest that flakes only make sense if we decentralize nixpkgs. A solution of this kind would allow us to keep the monorepo in tact, while having a clear and idiomatic modularization paradigm.

  • Another annoyance I’ve run into is the restrictions on specifying inputs themselves. I realize these restrictions are in place for a very good reason, i.e. to prevent impurity from leaking in. But really, all I want to do is specify inputs in a less verbose way. One solution might be to allow inputs to be optionally specified in a list, or imported from a inputs.nix or inputs.toml file in the toplevel that only contains a list of flake uris. So instead of inputs.someInput.url = "git:some/git/ref";, it could be inputs = [ "git:some/git/ref" "github:owner/repo/branch" ];.

    It seems like a missed opportunity to go to the trouble of defining a nice uri spec for flakes, only to require such a verbose declaration before you can actually use it. Combined with the different module types mentioned in the previous bullet point, we could avoid having to repetively specify things like flake = false by giving sane defaults to inputs of a given type.

I’ll leave it at that for now, as this has taken longer than I initially planned and I’m gettng pretty tired, but I hope we can all come together on this and produce a final product we can all be glad to use. I may update this list later with some additional insights, but hopefully it’s enough to get the conversation started.

10 Likes
  • An additional point is that flakes, unlike some of their language cousins like e.g. pyproject.toml do not make a distinction between development dependencies and build / runtime dependencies. This leads to the unfortunate situation where a mere chore dependency (e.g. on numtide/devshell) injects itself into the downstream dependency tree, unnecesarily. I’m not sure how much of an issue this is, but I’ve noticed it as a conceptual shortcoming that has come up in more involved discussions.

  • Further, flakes handle inputs by version controlled commit hashes, but at the same time do not (yet) have a builtin notion of patching. This cuts their vcs-enabled composability just short of the true power of patching. While this can be worked around, this is unergonomical, since in the particular fast-moving environment in which flakes are applied, patching is oftentimes a prime necessity until such patches are upstreamed proper.

  • Thirdly, flakes operate two notions of reusability: the “pinned” version in the form of self.packages and the “unpinned” version in the form of either self.overlays or self.packages combined with the caller making use of inputs.follows. Both notions optimize for different scenarios. But the fact that they are so entwined — with the unpinned notion even exhibiting a “double” API — makes them harder to apropriately reason about. In adition, the current flakes specification does not hint nor enforce any version boundaries for the unpinned notion. This might lead to hard to fix and subtle incompatibilities, especially in deeper nested dependency trees, that are entirely left for the end user to discover. I don’t really know a good answer to this aspect, but I still wanted to bring it up for discussion.

I love flakes and want them to shine like :sparkles: . Is a rename out of scope? :wink:

7 Likes

I am quite neutral wrt. flakes. I think they offer some nice benefits, such as: a standard interface for Nix repositories and much better enforcement of purity. However, there are some things that I find clunky:

  • Inputs url and follows are stringly-typed.
  • That package sets are specified per platform. I understand why this is done, but the fact that almost everyone is using flake-utils shows that it is suboptimal. @domenkozar proposes to make system a flake input rather than output: Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.
  • Packages (outside legacyPackages) can only be one level deep. This may be ok for package sets, but is annoying for some particular use cases. E.g. I have a Nix repo with programs + models (as in machine-learned models). It’s really nice to have some structure such as models.<modelName>.{wrapper,docker}.
9 Likes

I wrote on and on about how I’m dismayed about flakes and the lack of roadmap and my inability to evangelize Nix in the meantime, but it feels a bit off-topic from this focus on specific issues. If I could have one present for the whole year, it would literally just be a roadmap with a loose timeline for flakes in Nix.

Anyway,

On the other hand, it’s clear there are some warts that make using flakes quite unattractive for long timer nixers, and their complaints are far from irrelevant. Many respected members of the community seem to have taken up quite a negative view on flakes, and their stance seems justified in many respects.

What are these? (Edit, to clarify, that’s not direct at anyone in particular, nor am I asking folks to speak on someone else’s behalf. It’s just that I also have gotten this impression, but my understanding of it is somewhat nebulous and I’m eager to understand better in the hopes of there being a best path forward.)

9 Likes

I wrote on and on about how I’m dismayed about flakes and the lack of roadmap and my inability to evangelize Nix in the meantime, but it feels a bit off-topic from this focus on specific issues. If I could have one present for the whole year, it would literally just be a roadmap with a loose timeline for flakes in Nix.

I don’t think this is misplaced at all here. In fact, that’s my primary concern with flakes. I really like that feature, I regularly contribute code and use it for personal stuff. But I still think it should not be advertised for production use or to newcomers as soon as it’s unstable:

  • In (the now-closed) RFC49 there was a discussion about whether Nix is the best choice for flake configuration or if e.g. TOML is better (just like there’s a Cargo.toml in basically every Rust project). It was suggested to re-consider that in later flake iterations. Since I’m not aware of a statement from e.g. @edolstra this basically means that we’re all at risk of having to rewrite our flake.nix expressions at some point.
  • The fact that there’s no clear roadmap also leaves me with the (of course subjective) impression that there’s no actual plan for a release, but instead a lot of new stuff is added (not just flakes, also for CAS & many internal refactorings). In fact, the diff is already larger as the one between 1.11 and 2.0:
    $ gd upstream/1.11-maintenance  upstream/2.0-maintenance |wc -l
    79329
    $ gd upstream/2.3-maintenance  master | wc -l
    114613
    
    I mean, I like flakes, I like working on improving it and all that, but I’m fairly skeptical that it will get into a stable state quickly and thus releasable.

So, this is just my personal impression, but to me it seems as there’s no plan - or at least no transparently communicated plan - on what’ll happen next.

9 Likes

How can we get @edolstra to weigh in on the subject of a roadmap for flakes?

2 Likes

What are these? (Edit, to clarify, that’s not direct at anyone in particular, nor am I asking folks to speak on someone else’s behalf. It’s just that I also have gotten this impression, but my understanding of it is somewhat nebulous and I’m eager to understand better in the hopes of there being a best path forward.)

Well I left this part out explicitly, so as not to call anyone out. I’ve heard various comments ranging in depth from “I hate flakes” to more involved arguments about how they will cause a split in nixpkgs, which is probably the concern I’ve heard echoed the most. I was hoping to encourage those with strong opinions to weigh in themselves, rather than attempting to speak on their behalf.

There are also a lot of warts, like the few I mentioned up top, and I’ve got a few more I’ll probably add later on. It looks like the lack of a clear roadmap is a big one for many people, myself included. I was focused more on technical shortcomings in my OP so I’m glad you brought this up as it’s a really important point.

3 Likes

You mean a package source (repo) which hasn’t a flake.nix file i suppose?

I’ve had a discussion about Flakes with a few people in private.

To maximize the chances of Flake feedback be taken into the account, I’d really suggest splitting the discussion to one specific aspect/point. If everything is brought up in one issue, it speaks about the Flakes in general instead of limiting the discussion to a specific issue.

Writing the system input proposal and talking to people it feels to me that the underlying issue about Flakes is that flakes are the first attempt of “convention over configuration”, but only in the aspect of the CLI, but not also the specification of the Nix file.

That gives Flakes a huge design space, that each of us will find a different way, as when things get opinionated, there are lots of opinions.

My take would be: let’s get Nix 2.4 release out and make another iteration of the design in the upcoming months. This will provide feedback from even more users and allowing us to make another round of improvements. It’s a lot better to learn from current design now, rather than hold on to it forever (sunk cost fallacy).

The fact that we are reluctant to advise the use of flakes is by design, it’s marked as experimental and still unreleased. What would help is to define what experimental flag means so that expectations can be held accordingly.

9 Likes

Preface: I like some of the changes that the current Nix unstable development provides but I fear the whole changes that “we” are trying to get into 2.4 are too big already. We’ve had several parts of the code changed in drastic ways. The bug budget for a stable release has IMHO been exhausted a while ago.

I had these comments for a while already but only ever brought them up in many private conversations but never put it out there in “public”.

Here it goes:

I for one think we should circle back a bit on what flakes are supposed to fix and identify issues with the current solutions (random default.nix files…) and what approaches we can take to make the situation better. As Domen pointed out this also means defining what an experimental feature is.

A first step to agreeing on a proper Flakes implementation is to decide what it should solve. Solving “all of the problems” at once will probably not lead to an ideal situation. What are those problems anyway? Once there is an agreement on what it is we want to solve we can start experimenting with solutions and incrementally make the world a better place.

The whole RFC did go sideways and didn’t have a clear direction. The implementation changed as the RFC was going forward. Changing code while discussion is ongoing causes lots of churn on those involved. This is equally true for those trying to follow the RFC as well as those brave souls implementing it.

Flakes, as implemented right now, is trying to do many things but it doesn’t feel well rounded for usage from beginner to advanced scenarios. What was the goal here? Is a huge monorepo (with sub(-sub*)-flakes) a valid use case? Is anyone not using any of the support libraries that are required to write half-maintainable flake.nix files for more than just x86_64-linux? What does it actually include?

Since defining and implementing a standardized file format for flakes isn’t enough there is also the whole side-quest of overhauling the CLI. I am aware that flakes - in its current form - want to be as hermetic as possible and thus sets a few evaluator options to make it so. For an experimental initial implementation we might as well implement flake build as a wrapper around nix-build flake.nix --option ... -A .... until the file format and the general direction of flakes are decided and agreed upon.

In order to properly iterate on the Flakes implementation quickly (with everyone in the community having a chance) it would have been much better to start out by implementing Flakes logic in pure Nix instead of the Nix C++ codebase.

Nothing would have been wrong with import <nix/experiements/flakesN.nix> { inputs = { ... }; outputs = { ... }; until we are happy with UI & UX of flakes. For reasons of efficiency and future UX improvements it could then be moved into the Nix C++ code - if required at all. Before the actual implementation is “stable” we could have bundled experimental flake files with Nix in point releases. Importing those would also “unsupported” and should throw a warning on every single use.

Features such as lockfiles can and could be implemented as well. We already have command line tooling to generate “lockfiles” (nix-prefetch-git .. | jq .. > lock.json). Obviously in the end the tooling should be integrated with the Nix installation.

When iterating - locally - on the flakes format all you would have to do is exchange that path that the flake definition is imported from. Currently you must be fluent in C++ and understand all the indirections the code, the fetchers, the new multi-inheritance CLI classes, … are taking. At this point it should be obvious that contributing non-trivial changes to Nix requires a lot of dedication whilst we can see in Nixpkgs that we have plenty of people that are willing to write Nix.

Perhaps the lockfiles story should have been tackled after the initial version anyway. Nix users are used to pin their dependencies explicitly. Pinning dependencies also relates to the whole story around Nix having it’s own (flake) registry. Who does it serve? Does it just safe us the github:nixos/ prefix when referring to nixpkgs? Is that really worth the feature? There was a whole sub-discussion on the RFC that was not taken seriously as it was officially not part of it but yet is an essential part of how flakes fetches inputs. What is gained by introducing yet another notation for declaring inputs? Couldn’t we fix whatever was wrong with the fetchers? What is gained by having the inputs be an attribute set instead of actual Nix code? Why do we have to take them into the inputs = { ... }: function instead of using regular Nix code that used to be good enough? I feel the overall community doesn’t know why that was done or even if it is a good way it is now. It certainly serves the new CLI.

As a general note I am impressed on how well the Rust community does this. There have been RFCs to define a general direction of a feature and then there is follow-up work to actually flesh out the implementation AFTER the direction is clear. What happened here is nothing like that. We just get an RFC while we discuss an already existing implementation just to amend the implementation.

13 Likes

I definitely agree that it can be overwhelming and somewhat unproductive to discuss all the issues with the current implementation all at once. But at some point we’ve got to distill these issues down into some form of RFC so, as @andir mentioned very eloquently, we can have a clear direction for the devs to move toward.

I myself have been very frustrated with my own person lack of C++ experience, and have tried and mostly failed to address some outstanding issues. The best I can hope for right now is to push the conversation forward and I’d be more than happy to help draft an RFC to bring everything together once we work out a clear direction for flakes.

It has been difficult to discern where best to try and address these issues since not all of the core Nix devs seem to frequent discourse, but I thought it might be a good place to start. The current situation of flakes being unstable for several years seems to have caused a lot of confusion, exhaustion and frustration for all parties, and I’d really love to see some concrete steps taken to finally move towards a merged release.

Even if the ultimate decision is simply to completely scrap flakes and move forward with a completely different solution, I’d like to see that codified as soon as possible.

After all, the longer we wait, the bigger that diff is going to grow…

6 Likes