I’ve been using flakes since I started using NixOS about 4 years ago so it’s been a lot about how I’ve interfaced with Nix, but when I was implementing a POC of generating derivations for all terraform providers(and versions) by mangling the data from the OpenTofu registry i hit a roadbump since that repo is 276MB without history so every time i changed one byte in a Nix file and reevaluate it’ll copy 276MB to store and I thought that was really stupid so I worked around it by writing a default.nix with npins instead that could parse nix and json from the filesystem instead. While 276MB doesn’t sound like much, imagine that every time you press save, and I was doing this from a Chromebook which has eMMC SSD so it is both small, slow and has useless write endurance so I thought I’ve got to start unflaking my system.
But since I’ve been using flakes I just couldn’t bother unflaking my system right now and it’s also quite practical to have a flake to give others so i put it off for awhile. But today I looked into flake-compat as a way of getting around flakes and it was really close to doing what I wanted straight away, I just had to apply a tiny patch to it like this and i was evaluating my config from the filesystem!
diff --git a/flake-compat2.nix b/flake-compat2.nix
index df5128d9..e33aa94c 100644
--- a/flake-compat2.nix
+++ b/flake-compat2.nix
@@ -5,7 +5,7 @@
# containing 'defaultNix' (to be used in 'default.nix'), 'shellNix'
# (to be used in 'shell.nix').
-{ src, system ? builtins.currentSystem or "unknown-system" }:
+{ src, impureEval ? false, system ? builtins.currentSystem or "unknown-system" }:
let
@@ -126,8 +126,20 @@ let
isShallow = builtins.pathExists (src + "/.git/shallow");
in
- { lastModified = 0; lastModifiedDate = formatSecondsSinceEpoch 0; }
- // (if src ? outPath then src else tryFetchGit src);
+ (
+ if !impureEval then
+ {
+ lastModified = 0;
+ lastModifiedDate = formatSecondsSinceEpoch 0;
+ }
+ // (if src ? outPath then src else tryFetchGit src)
+ else
+ {
+ lastModified = 0;
+ lastModifiedDate = formatSecondsSinceEpoch 0;
+ outPath = src;
+ }
+ );
This way I can use the flakes CLI to output and created a default.nix like this:
let
# Our own patched flake-compat that evaluates impurely from disk
flake-compat = import ./flake-compat.nix;
flake = flake-compat { src = (builtins.toString ./.); };
in
flake.outputs
Now sure this still evaluates all flake inputs from store and nixpkgs, but since we easily can craft our own nixpkgs instance within a flake and I could point that import at any path i want and it should be able to eval nixpkgs from filesystem too.
I just figured this out today but I’m quite excited because it means we can be compatible with flakes without evaluating from store. I can publish as a flake and with this default.nix so flake users can consume my packages (if i had any of value), my nixosConfig and whatever while being able to iterate on my machine without flakes and the iteration speed of evaluation from filesystem.
If you wanna reap the benefits you must add some arguments to your nixos-rebuild so it looks like this:
nixos-rebuild build --file $flakepath --attr "nixosConfigurations.\"$hostname\"" # nixos-rebuild applies the last bit of the attrpath
I don’t really have any outstanding issue with flakes that isn’t related to evaluating from store and this pretty much solves it.
I imagine that flake.lock format is pretty stable and unlikely to change an awful lot so it’s unlikely flake-compat would break one day either, the code to extract is is complicated but it’s not impossible considering I solved the source eval from store patch in no time. I must say I was really impressed to se nvd diff not have any changes when using flake-compat vs building with flakes.
I would love some thoughts and feedback it feels like I must’ve missed something, otherwise I think iterating on flake-compat could be a pretty valid community effort (so we can implement our “follows” in Nix instead of “flake inputs nix”). I don’t really mind the nix flake cli experience to be honest I think it’s good enough and if i must use it to update my lockfile (until someone implements a compatible tool) I’m fine with that.
Appreciate if we stay away from the flakes vs non-flakes discussion here since this pretty much seems to splat two flies with one smack as we say in Swedish
Cheers
EDIT: lix-project/flake-compat: Turns Nix flakes into normal Nix expressions - Lix Systems Lix has a flake-compat implementation with argument copySourceTreeToStore
already