Direnv with nix run
direnv
is a useful tool to automatically set environment variables per
project and it also has a nix integration!
I'm used to define an .envrc
file in my projects which would expose some
tools to work with the project.
For example I'm using zola to write this
blog. I don't need the zola
command in my user environment all the time it's
only useful when working on the blog. With direnv
I could set this up with
two files in the project root directory:
shell.nix
:
let
pkgs = import <nixpkgs> {};
in
pkgs.mkShell {
buildInputs = [
pkgs.zola
];
}
.envrc
:
use_nix
After running direnv allow
this would happen when going in the project directory:
$ zola
The program ‘zola’ is currently not installed. You can install it by typing:
nix-env -iA nixos.zola
$ cd blog
direnv: loading .envrc
direnv: export +AR +AS +CC +CONFIG_SHELL +CXX +HOST_PATH +IN_NIX_SHELL +LD +NIX_BINTOOLS +NIX_BINTOOLS_WRAPPER_x86_64_unknown_linux_gnu_TARGET_HOST +NIX_BUILD_CORES +NIX_BUILD_TOP +NIX_CC +NIX_CC_WRAPPER_x86_64_unknown_linux_gnu_TARGET_HOST +NIX_ENFORCE_NO_NATIVE +NIX_HARDENING_ENABLE +NIX_INDENT_MAKE +NIX_LDFLAGS +NIX_STORE +NM +OBJCOPY +OBJDUMP +RANLIB +READELF +SIZE +SOURCE_DATE_EPOCH +STRINGS +STRIP +TEMP +TEMPDIR +TMP +TMPDIR +buildInputs +builder +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +name +nativeBuildInputs +nobuildPhase +out +outputs +patches +phases +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system ~PATH
$ zola
zola 0.9.0
Vincent Prouillet <hello@vincentprouillet.com>
...
We can see that the $PATH
was updated so that zola
is now available. That's
great but a lot of other other environment variables that we don't care about
were defined. Indeed direnv
uses nix-shell
behind the scenes to populate
the environment but nix-shell
is designed to debug the build of derivations
not just bring some tools in the $PATH
. This problem is discussed
here.
It's also not very fast, almost half a second on my machine:
$ cd blog
direnv: loading .envrc
real 0m0.442s
user 0m0.333s
sys 0m0.068s
direnv: export +AR +AS +CC +CONFIG_SHELL +CXX +HOST_PATH +IN_NIX_SHELL +LD +NIX_BINTOOLS +NIX_BINTOOLS_WRAPPER_x86_64_unknown_linux_gnu_TARGET_HOST +NIX_BUILD_CORES +NIX_BUILD_TOP +NIX_CC +NIX_CC_WRAPPER_x86_64_unknown_linux_gnu_TARGET_HOST +NIX_ENFORCE_NO_NATIVE +NIX_HARDENING_ENABLE +NIX_INDENT_MAKE +NIX_LDFLAGS +NIX_STORE +NM +OBJCOPY +OBJDUMP +RANLIB +READELF +SIZE +SOURCE_DATE_EPOCH +STRINGS +STRIP +TEMP +TEMPDIR +TMP +TMPDIR +buildInputs +builder +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +name +nativeBuildInputs +nobuildPhase +out +outputs +patches +phases +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system ~PATH
So for this use case I thought of an alternative approach which involves nix run
. Since nix
v2 nix run
is available. It doesn't replace nix-shell
completely but it could be used in this situation.
Running nix run nixpkgs.zola
would open a new shell an make zola
available
in $PATH
. By checking the environment we can confirm it does only that:
$ env | sort > env.current
$ nix run nixpkgs.zola
$ env | sort > env.after
$ diff -u env.current env.after
-PATH=/home/eon/bin:/run/wrappers/bin:/home/eon/.nix-profile/bin:/etc/profiles/per-user/eon/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin
+PATH=/nix/store/d4wzwmy02r2d4jpk02ha6xi2nm5vk0li-zola-0.9.0/bin:/home/eon/bin:/run/wrappers/bin:/home/eon/.nix-profile/bin:/etc/profiles/per-user/eon/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin
-SHLVL=4
+SHLVL=5
Next how we could integrate this with direnv
is quite trivial. By default
nix run
spawns a new shell but it's possible to specify a different command.
In this case we just want the $PATH
variable exported by nix run
. The .envrc
file contains now (and of course we can get rid of the shell.nix
file):
export $(nix run nixpkgs.zola -c env | grep ^PATH)
This run a bit faster that the nix-shell
version:
$ cd blog
direnv: loading .envrc
real 0m0.313s
user 0m0.256s
sys 0m0.027s
direnv: export ~PATH
And doesn't pollute the environment!