A fast, persistent use_nix/use_flake implementation for direnv [maintainer=@Mic92 / @bbenne10]
Find a file
2022-05-01 06:54:00 -04:00
.github Bump cachix/install-nix-action from 16 to 17 2022-04-11 04:01:05 +00:00
scripts scripts/update-checksums.sh: also update template 2021-12-16 10:14:51 +01:00
template update fetchurl checksum 2022-04-25 09:47:09 -04:00
tests Make test messages consts rather than class state 2022-04-03 20:45:35 -04:00
.gitignore add project template 2021-05-29 09:43:52 +02:00
default.nix don't make direnvrc exectuable 2021-10-20 10:14:51 +02:00
direnvrc use_nix: introduce initial generic nix argument parsing 2022-04-29 08:50:13 -04:00
flake.lock flake.lock: Update 2022-05-01 10:19:04 +00:00
flake.nix Convert ci tests to nix flake app test-runner 2022-04-03 13:30:25 +02:00
LICENSE Initial commit 2019-09-27 08:43:48 +01:00
README.md update fetchurl checksum 2022-04-25 09:47:09 -04:00
run-tests.nix Convert ci tests to nix flake app test-runner 2022-04-03 13:30:25 +02:00
shell.nix flake.nix: Import shell.nix as devShell 2021-12-06 15:19:49 +11:00

nix-direnv

Test

A faster, persistent implementation of direnv's use_nix, to replace the built-in one.

Prominent features:

  • significantly faster after the first run by caching the nix-shell environment
  • prevents garbage collection of build dependencies by symlinking the resulting shell derivation in the user's gcroots (Life is too short to lose your project's build cache if you are on a flight with no internet connection)

Installation

There are different ways to install nix-direnv, pick your favourite:

  • via home-manager (recommended)
  • via configuration.nix in NixOS
  • with nix-env
  • from source
  • with direnv source_url

Via home-manager

In $HOME/.config/nixpkgs/home.nix add

{ pkgs, ... }:

{
  # ...other config, other config...

  programs.direnv.enable = true;
  programs.direnv.nix-direnv.enable = true;
  # optional for nix flakes support in home-manager 21.11, not required in home-manager unstable or 22.05
  programs.direnv.nix-direnv.enableFlakes = true;

  programs.bash.enable = true;
  # OR
  programs.zsh.enable = true;
  # Or any other shell you're using.
}

Optional: To protect your nix-shell against garbage collection you also need to add these options to your Nix configuration.

If you are on NixOS also add the following lines to your /etc/nixos/configuration.nix:

{ pkgs, ... }: {
  nix.extraOptions = ''
    keep-outputs = true
    keep-derivations = true
  '';
}

On other systems with Nix add the following configuration to your /etc/nix/nix.conf:

keep-derivations = true
keep-outputs = true

Via configuration.nix in NixOS

In /etc/nixos/configuration.nix:

{ pkgs, ... }: {
  environment.systemPackages = with pkgs; [ direnv nix-direnv ];
  # nix options for derivations to persist garbage collection
  nix.extraOptions = ''
    keep-outputs = true
    keep-derivations = true
  '';
  environment.pathsToLink = [
    "/share/nix-direnv"
  ];
  # if you also want support for flakes (this makes nix-direnv use the
  # unstable version of nix):
  nixpkgs.overlays = [
    (self: super: { nix-direnv = super.nix-direnv.override { enableFlakes = true; }; } )
  ];
}

Then source the direnvrc from this repository in your own $HOME/.direnvrc

# put this in ~/.direnvrc
source /run/current-system/sw/share/nix-direnv/direnvrc

With nix-env

As non-root user do the following:

nix-env -f '<nixpkgs>' -iA nix-direnv

Then add nix-direnv to $HOME/.direnvrc:

source $HOME/.nix-profile/share/nix-direnv/direnvrc

You also need to set keep-outputs and keep-derivations to nix.conf as described in the installation via home-manager section.

From source

Clone the repository to some directory

$ git clone https://github.com/nix-community/nix-direnv $HOME/nix-direnv

Then source the direnvrc from this repository in your own .direnvrc

# put this in ~/.direnvrc
source $HOME/nix-direnv/direnvrc

You also need to set keep-outputs and keep-derivations to nix.conf as described in the installation via home-manager section.

Direnv source_url

Put the following lines in your .envrc:

if ! has nix_direnv_version || ! nix_direnv_version 2.0.1; then
  source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.0.1/direnvrc" "sha256-5tSiHl8q9TnqoJ7Wizgal7sOUcKxiBR+7YSSqOmt7hg="
fi

Usage example

Either add shell.nix or a default.nix to the same directory:

# save this as shell.nix
{ pkgs ? import <nixpkgs> {}}:

pkgs.mkShell {
  nativeBuildInputs = [ pkgs.hello ];
}

Then add the line use nix to your envrc:

$ echo "use nix" >> .envrc
$ direnv allow

If you haven't used direnv before, make sure to hook it into your shell first.

Using a non-standard file name

You may use a different file name than shell.nix or default.nix by passing the file name in .envrc, e.g.:

$ echo "use nix foo.nix" >> .envrc

Flakes support

nix-direnv also comes with an alternative use_flake implementation. The code is tested and does work but the upstream flake api is not finalized, so we we cannot guarantee stability after an nix upgrade.

Like use_nix, our use_flake will prevent garbage collection of downloaded packages, including flake inputs.

Creating a new flake-native project

This repository ships with a flake template which provides a basic flake with devShell integration and a basic .envrc.

To make use of this template, you may issue the following command:

$ nix flake new -t github:nix-community/nix-direnv <desired output path>

You can view the template here.

Integrating with a existing flake

$ echo "use flake" >> .direnvrc
$ direnv allow

The use flake line also takes an additional arbitrary flake parameter, so you can point at external flakes as follows:

use flake ~/myflakes#project

Advanced usage

Under the covers, use_flake calls nix print-dev-env. The first argument to the use_flake function is the flake expression to use, and all other arguments are proxied along to the call to print-dev-env. You may make use of this fact for some more arcane invocations.

For instance, if you have a flake that needs to be called impurely under some conditions, you may wish to pass --impure to the print-dev-env invocation so that the environment of the calling shell is passed in.

You can do that as follows:

$ echo "use flake . --impure" > .envrc
$ direnv allow

Storing .direnv outside the project directory

By default, every direnv-enabled directory will contain a .direnv directory. .direnv acts as a pure cache and is fully reproducible. To that end, we do not recommend tracking this directory or its contents, even in the scenario that the project tracks the a .direnvrc.

It is possible to override a function called direnv_layout_dir in ~/.config/direnv/direnvrc (or in each project's .direnvrc or .envrc).

The following example will create a unique directory name per project in ~/.cache/direnv/layouts/:

# Place in "$HOME"/.config/direnv/direnvrc

# Two things to know:
# * `direnv_layour_dir` is called once for every {.direnvrc,.envrc} sourced
# * The indicator for a different direnv file being sourced is a different `$PWD` value
# This means we can hash `$PWD` to get a fully unique cache path for any given environment

: ${XDG_CACHE_HOME:=$HOME/.cache}
declare -A direnv_layout_dirs
direnv_layout_dir() {
    echo "${direnv_layout_dirs[$PWD]:=$(
        echo -n "$XDG_CACHE_HOME"/direnv/layouts/
        echo -n "$PWD" | shasum | cut -d ' ' -f 1
    )}"
}

Watching additional files

To minimize the number of evaluations, nix-direnv maintains a list of files to check for changes when deciding if an update of the cached environment is required. By default, use_flake watches

flake.nix
flake.lock
devshell.toml

use_nix watches

default.nix
shell.nix

To trigger an evaluation when other nix files change, register them by calling nix_direnv_watch_file PATH [PATH...] from .envrc.

nix_direnv_watch_file module.nix
nix_direnv_watch_file mod1.nix mod2.nix
use flake

Shell integration

See the wiki for helpers to quickly setup a direnv setup in a new project.

Known Bugs

At the moment nix-direnv depends on GNU Grep and a modern Bash version. This might lead to problems on macOS. As a work-around we suggest that macOS users install direnv/grep via Nix or Homebrew.

Why not use lorri instead?

  • nix-direnv has flakes support.
  • High CPU load/resource usage in some cases: When nixpkgs in NIX_PATH is pointed to a directory, i.e. a git checkout, Lorri will try to evaluate nixpkgs everytime something changes causing high cpu load. Nix-direnv compromises between performance and correctness, and only re-evaluates direnv if either the project-specific default.nix / shell.nix changes, or if there is a new commit added to nixpkgs. A re-evaluation can be also triggered by using touch .envrc in the same project. A different problem is that it might trigger mass-rebuilds when the same nixpkgs checkout is pointed to something like staging.
  • No additional daemon or services required: The codesize is small enough that it can be vendored into a project itself.

Other projects in the field