Practical direnv
This post was written in 2015. Since then, I’ve continued to use direnv
and still rely on it heavily. In 2020, I wrote about how my setup has evolved into a more powerful setup - please check out my more practical direnv post!
direnv
is a nifty little shell tool I came across recently which looks extremely useful for developers who work across multiple languages or projects.
direnv
automatically update environment variables when you enter/exit particular directory trees. Much as you might have HOME
and PATH
variables set globally in your .profile
/.bashrc
/etc file, direnv
allows you to control these on a per-directory basis.
This post explores a few simple examples for usage in Python, Go and Java; see below for further resources.
For Mac users, installation is easiest using Homebrew (brew install direnv
).
Automatic Python virtualenv switching #
For Python projects virtualenv
provides separation of dependencies, but more than once I’ve found myself forgetting to activate/deactivate when moving around between projects. Direnv again makes this easy and foolproof:
layout python
When you enter the folder subtree direnv will automatically pick up an existing virtualenv
environment and activate it.
For example:
$ mkdir a b
$ echo "layout python" > a/.envrc
$ echo "layout python" > b/.envrc
$ cd a
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.
This warning occurs because direnv has detected a new or changed .envrc
file - a good security precaution.
$ direnv allow
direnv: loading .envrc
Running virtualenv with interpreter /usr/local/bin/python
New python executable in /private/tmp/a/.direnv/python-2.7.10/bin/python2.7
Also creating executable in /private/tmp/a/.direnv/python-2.7.10/bin/python
Installing setuptools, pip, wheel...done.
direnv: export +VIRTUAL_ENV ~PATH
$ pip install pycrypto
Collecting pycrypto
Installing collected packages: pycrypto
Successfully installed pycrypto-2.6.1
We now have a virtualenv inside a
with pycrypto installed:
$ pip freeze
pycrypto==2.6.1
wheel==0.24.0
Changing directory to b
is enough to automatically switch virtualenv.
$ cd ../b
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.
$ direnv allow
[SNIP]
direnv: export +VIRTUAL_ENV ~PATH
$ pip freeze
wheel==0.24.0
Subsequent changes of directory won’t prompt for direnv allow
, and allows seamless changes of virtualenv:
$ cd ../a
direnv: loading .envrc
direnv: export +VIRTUAL_ENV ~PATH
$ pip freeze
pycrypto==2.6.1
wheel==0.24.0
Automatic setting of GOPATH #
If you’re developing in Go, direnv
can help automatically maintain a separate GOPATH
for each independent project. This can be useful where you’re using different versions of dependencies across projects, and wish to maintain some separation - without remembering to change GOPATH
yourself every time you switch project.
Quite simply, place the following in .envrc
at the root of each Go project:
layout go
Direnv will do the rest automatically; setting GOPATH
and also updating PATH
to include $GOPATH/bin
.
Different versions of a tool for different projects #
While I’m almost always working with Java 8, for legacy reasons I’m also working on a Java 7 codebase at the moment. There’s nothing intrinsically wrong with using JDK 1.8 for JDK 1.7 development, but there are some risky areas I’d rather avoid. Additionally we have a need to use a fixed Gradle version rather than using the system one (or, curiously, the Gradle wrapper).
So, I set my general purpose JDK to 1.8 by placing this in my home ~/.envrc
file:
# On OS X, discover JAVA_HOME for a major JDK version by using the java_home utility
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
# direnv helper to add this to PATH. export PATH=...:$PATH also works.
PATH_add $JAVA_HOME/bin
And override the Java and Gradle version specifically for the legacy project by placing this in my ~/projects/something/.envrc
:
export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
# PWD is always set to the directory in which this .envrc file was found
# In this case, our legacy version of gradle is in ~/projects/something/legacy-gradle
export GRADLE_HOME=$PWD/legacy-gradle
# Update paths as previously
PATH_add $JAVA_HOME/bin
PATH_add $GRADLE_HOME/bin
Now, for working with Java anywhere inside my home folder, direnv ensures that I’m using Java 1.8 automatically. However, if I run Java, Gradle, or check the JAVA_HOME
/GRADLE_HOME
environment variables anywhere under ~/projects/something
, it’s going to be the correct, older version.
Note: this example could be refactored into a custom
use
function, but I wanted to start with something that would be reasonably familiar looking to most *nix users.
Further reading #
- direnv.net is an OK starting point, but I felt didn’t do enough to show the power of the tool.
- The direnv man page provides some other simple examples.
- The direnv standard library provides convenience wrappers for Python, Go, Node, Ruby, Perl and Nix, and is worth looking at.
- Rachid Belaid has done a more extensive writeup specifically around using direnv for Go development.