Flake For Non-Nix Projects
The ideal outcome of using Nix or home-manager is to have as many things as possible under their control.
Among these things are developer tools for coding. You may need cargo
, and rust-analyzer
for Rust projects;
ipython
, and python-lsp-server
for Python projects, etc.
Of course, the simplest solution is to have them all installed and configured in home-manager or the OS level. But, over time, you'll end up with a ton of stuff.
Maybe that won't bother you. Fine, but this approach is at odds with some convention of the Nix community. You
see, there's this command called nix-shell
, or nix develop
, which sets up a environment with certain
packages, with the expectation that you only need them sometimes, usually within the context of developing
a certain software project.
So, arguably, a better alternative to installing everything you possibly need is to keep only the essential
tools you always need, regardless of what you are working on. Things like neovim
, git
, ripgrep
, etc.
When a project demands things such as rust-analyzer
, running nix develop
should set it up for you.
How do we implement that? In a imaginary world where every single software project is built with a flake.nix,
the devShell
property should provide everything the software project owner expect you to need. And you just
cd
into the root of the project, run nix develop
, and you are off to the races.
Several problems:
- running
nix develop
is repetitive, and therefore, annoying. - the owner of a flake.nix may have drastically different taste for the best dev tools for their project (bet!).
- we don't live in a world built with Nix flakes.
Yikes.
To solve 1
, enter direnv. It's a piece of software that let you create a environment for a directory,
and automatically switch to it when you enter said directory. The "environment" could contain envvars, and,
you guessed it: a Nix developer shell. If you have a flake.nix, add use flake
in your project directory's
.envrc
file, direnv will automatically call nix develop
the next time you cd
in. Neat!
To make this work in practice, you'll want to add .envrc
and .direnv/
to your global git ignore list, as
they are personal preferences that probably shouldn't end up in git history.
Ok, we are so close to solve problem 2
, and 3
now. In short, nix develop
may set up a environment,
but it may not be the environment that suits you the best. nix-direnv extends direnv to save us:
use flake path/to/flake#target
... with this in .envrc
, direnv will set up the Nix environment according to path/to/flake#target
. That
could point to any location on your hard drive! You can have a flake.nix whose devShell
has cargo
, and
rust-analyzer
. You can have another with ipython
, and python-lsp-server
. Mix and match to your liking to
the infinite degree...
For now, I've decided to give nix-direnv a try. Alongside my home-manager configuration, I've also included extra flakes for generic Python/Rust projects, and specific projects that may require a mix of tools. A lot of the project I work on don't use flake as their package manager. With this approach, I get to customize my setup for them each, and stay in the comfort of Nix and home-manager.
direnv and nix-direnv can be configured together with home-manager. To achieve everything mentioned in this article, including direnv's shell integration (Fish, for me), it's as simple as
# In home manager config...
programs.direnv = {
enable = true;
nix-direnv = {
enable = true;
enableFlakes = true;
};
}
To recap, having the snippet above in my home-manager setup, I now can enter any project's root directory and
add a .envrc
file with the content use flake ~/src/dotfiles/direnvs/python
.
~/src/dotfiles/direnvs/python
contains a flake.nix
(and a flake.lock
) that has the devShell
value
I like for all Python projects. When I cd
into this project, (nix-)direnv
will read from that devShell
and set every tool listed under there. The tools are cached in a .direnv
directory so when I return here,
the setup is basically instantaneous. Since I make git
to ignore .envrc
, and .direnv
no matter where
they are, this project I'm working on is unaffected by all this.