Richard North's Blog

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 #

← Home