Melting flakes
I use nix in every git repository to document the contex of the
project. I lost count at the amount of times it saved me by removing
the “bootstraping” phase of revisiting an old project.
Unfortunatelly, because I used flakes, this came at a
cost: every project has its own version of nixpkgs, and
in practice, because for now
nix uses input-addressed
derivations, you end up with multiple copies of “the same” package.
At some point I had 6 instances of nixpkgs#ghc, each
taking up
console (docs)
$ nix path-info --recursive --size --closure-size --human-readable nixpkgs#ghc
# Path size closure-size
...
/nix/store/k493jzz83044mqayvlb6247l35780kxy-ghc-9.10.3 1.8G 3.0GNot to mention nixpkgs#haskell-language-server
console
$ nix path-info --recursive --size --closure-size --human-readable nixpkgs#haskell-language-server
# Path size closure-size
...
/nix/store/ymvsfvbhw5kvw639cv8k447v1v6jbsc4-haskell-language-server-2.13.0.0 110.9M 7.5G
/nix/store/ps0yfb2bvv33srn6fcnwfiw8rg7w5g7s-haskell-language-server-2.13.0.0 896.0 7.5GStrictly speaking, I have no need for multiple ghc/hls versions.
Pre-nix I just used the most recent without any major issues. Most
software cannot assume everyone has nix-adjacent
package managers, so most of the time there is some leeway
when it comes to version pinning. Lets abuse this a bit
I decided that my build times and disk space are more important than “first try” deterministic builds, so I implemented a fallback strategy:
- Pin system’s
nixpkgsin flake registry - Use the registry to override flake inputs automatically and have
a simple toggle for switching back to
flake.lock - Provide a simple way to update
flake.lockto point to the samenixpkgsrevision as the current system.
I find this very similar to how most flake inputs use
inputs.nixpkgs.follows = "nixpkgs", but instead, this
is done automatically!
If you use flakes for your system configuration, chances are, you
already have nixpkgs pinned to the revision you used
for building the system (see nixos
option).
Spoiler alert! This will not suffice
Mostly, I only want this on devShells since that is
my main pain point. I use nix-direnv to
automatically activate development shells. The result is a
.envrc file in project repository:
.envrc
use flakeWe can pass arguments to this line. More specifically, we want
--override-input option:
.envrc
use flake . --override-input nixpkgs flake:nixpkgsThis works, but we don’t want to force this
impurity on other people! So what I ended up doing
is overriding use_flake function in my
direnvrc file like so:
$XDG_CONFIG_HOME/direnv/direnvrc
# store original version of use_flake
eval "_$(declare -f use_flake)"
use_flake() {
_nix_direnv_warning "Using system nixpkgs!"
# Inject --override-input argument on every `use flake` invocation
_use_flake "${@:-.}" --override-input nixpkgs flake:nixpkgs
}If I try to cd into flake enabled repository with
.envrc, I automatically use my systems
nixpkgs. But, since this might backfire, I added a
kill-switch with variable NIX_DIRENV__FEELING_SPICY and
moved everything to my configuration:
zsh
export NIX_DIRENV__VARS="$XDG_CONFIG_HOME/direnv/vars"
nix-spicy-off() {
echo "export NIX_DIRENV__FEELING_SPICY=0" > "$XDG_CONFIG_HOME/direnv/vars"
}
nix-spicy-on() {
echo "export NIX_DIRENV__FEELING_SPICY=1" > "$XDG_CONFIG_HOME/direnv/vars"
}direnvrc
eval "_$(declare -f use_flake)"
use_flake() {
source "$XDG_CONFIG_HOME/direnv/vars"
watch_file "$XDG_CONFIG_HOME/direnv/vars"
if [[ ''${NIX_DIRENV__FEELING_SPICY:-0} -eq 1 ]]; then
_nix_direnv_warning "SPICY! Using system nixpkgs"
_use_flake "''${@:-.}" --override-input nixpkgs "flake:nixpkgs"
else
_use_flake "$@"
fi
}If I now try to run
console
nix flake update nixpkgs \ # We only want to update nixpkgs
--override-input nixpkgs flake:nixpkgs \
--output-lock-file "flake.lock" # --override-input implies --no-write-lock-filethe flake.lock is now updated, but with input type
that only really works on my machine: type: path. To
fix this, I configured system flake repository manually:
registry = {
self.flake = inputs.self;
nixpkgs.to = {
owner = "NixOS";
repo = "nixpkgs";
rev = inputs.nixpkgs.rev;
type = "github";
};
};By doing so, flake:nixpkgs repository now resolves
to github:NixOS/nixpkgs/<rev> when using the update command. Problem solved! I also
added an alias for this command to my zshrc: