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!