adios-wrappers provides an interface for wrapping programs in Nix, and a collection of preconfigured wrappers.
There are several other community projects focused around wrappers, such as
lassulus/wrappers and
nix-wrapper-modules. However, adios-wrappers is unique for
its use of Adios, an alternate module system designed to be fast and
lazy.
Warning
Adios itself is likely to have breaking changes, although they will almost always come with warnings and a migration period. All major changes will be listed in the changelog.
A major reason why NixOS rebuilds are so slow is because of lib.evalModules. Nix is a lazy language, which means in
most circumstances, code/data that isn't used is never evaluated. However, lib.evalModules takes very little advantage
of this. Any module is able to "mutate" another one, which means that to evaluate a single module, Nix has to evaluate
all the other modules to look for mutations.
This means that as a module set grows, it will get slower and slower1. This could hypothetically be avoided with a more
minimal baseModules list, but lib.evalModules isn't designed to handle module inputs explicitly, and any cycles
would lead to infinite recursion. It would also be a massively breaking change to any module set.
This fatal flaw applies to any repo that relies on lib.evalModules - such as
NixOS,
home-manager, nixvim,
stylix, treefmt-nix, and many more.
So, if a real fix would be a breaking change, why don't we just design a better module system? That's where Adios comes in. In Adios, modules explicitly state their dependency relationships, and are therefore fully lazy. If you don't use a module, it will never be evaluated.
Adios is less documented, weirder, and harder to use than lib.evalModules. But by using it, adios-wrappers can
promise to never regress in performance, no matter how many modules it has.
Most NixOS / Nix users configure their dotfiles via home-manager. This works similarly to stow or chezmoi, but changes are applied on rebuild, rather than through an external command. Unfortunately, this creates a very slow feedback loop for applying any changes, as Nix has to evaluate and build an entire generation. The configured programs are also non-portable to other systems, as the system needs to have home-manager installed already.
Wrappers work differently. Instead of declaring files to be symlinked to the home directory, wrappers simply wrap
around a single derivation to embed some program config. By wrapping pkgs.git, we can get a new derivation,
git-wrapped. This provides a custom Git binary that knows about our config!
By using wrappers, we get:
- Faster iteration, as wrappers only have to evaluate and build what you're currently working on. Most adios-wrappers users use a devshell for their wrappers, and can achieve 1-2 seconds of evaluation time.
- Portability, as the wrapped program can be run on any machine with Nix on it. If you expose the wrappers via a flake,
you can even use
nix run github:your-repo#your-program.
For more info on the benefits of wrappers, see this blogpost.
See the example config here, which contains both a flake.nix and a wrappers.nix. Even if you
have an existing flake, the relevant parts of the example flake can simply be copied into yours.
Note: while the examples here are npins-specific, any source pinning tool should work similarly.
Start out by pinning the relevant sources:
npins init # Only if you don't already have an `npins/` folder
npins add github llakala lladios -b main --name adios
npins add github llakala adios-wrappers -b mainOnce you've done that, see the example npins-based config here. This contains both a wrappers.nix
and shell.nix.
Once you have adios-wrappers as an input, you'll need to start using it to define your wrappers. A guide on this is provided here.
Also see the website, which works like search.nixos.org and lets you view
which options a module provides.