/nix-direnv

A fast, persistent use_nix implementation for direnv [maintainer=@Mic92]

Primary LanguagePythonMIT LicenseMIT

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.enableNixDirenvIntegration = 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"
  ];
}

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 1.4.0; then
  source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/1.4.0/direnvrc" "sha256-4XfVDjv75eHMWN4G725VW7BoOV4Vl3vAabK4YXIfPyE="
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.

Flakes support

nix-direnv also comes with a flake alternative. The code is tested and works however since future nix versions might change their api regarding this feature we cannot guarantee stability after an nix upgrade. Likewise use_nix the use_flake implementation will prevent garbage collection of downloaded packages and also for flake inputs.

You can run nix flake new -t github:nix-community/nix-direnv to get this project template. or just add:

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

in case the project already comes with a flake.nix. Optionally if you do not want flake.nix to be part of the current directory repo, you can specify an arbitrary flake expression as parameter such as:

use flake ~/myflakes#project

Storing .direnv outside the project directory

A .direnv directory will be created in each use_nix project, which might interact badly with backups (e.g. Dropbox) or IDEs.

Therefore it's possible to override a function called direnv_layout_dir in $HOME/.config/direnv/direnvrc or in each project's .envrc.

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

# $HOME/.config/direnv/direnvrc
: ${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
    )}"
}

During direnv setup direnv_layout_dir can be called multiple times and with different values of $PWD (when other .envrc files are included). Therefore cache its results in dictionary direnv_layout_dirs.

Manually re-triggering evaluation

In some case nix-direnv does not detect if imported file has changed and still provides the old cached values. An evaluation can be triggered by updating your default.nix, shell.nix or flake.nix, depending on what is used:

# choose one
$ touch default.nix
$ touch shell.nix
$ touch flake.nix

Shell integration

To quickly add a default.nix/flake.nix to a project you can put the following snippets in your bashrc/zshrc

nixify() {
  if [ ! -e ./.envrc ]; then
    echo "use nix" > .envrc
    direnv allow
  fi
  if [[ ! -e shell.nix ]] && [[ ! -e default.nix ]]; then
    cat > default.nix <<'EOF'
with import <nixpkgs> {};
mkShell {
  nativeBuildInputs = [
    bashInteractive
  ];
}
EOF
    ${EDITOR:-vim} default.nix
  fi
}

flakifiy() {
  if [ ! -e flake.nix ]; then
    nix flake new -t github:nix-community/nix-direnv .
  elif [ ! -e .envrc ]; then
    echo "use flake" > .envrc
    direnv allow
  fi
  ${EDITOR:-vim} flake.nix
}

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