Richard North’s Blog

Practical direnv

This post was writ­ten in 2015. Since then, I’ve con­tin­ued to use direnv and still rely on it heav­ily. In 2020, I wrote about how my setup has evolved into a more pow­er­ful setup - please check out my more prac­ti­cal direnv post!

direnv is a nifty lit­tle shell tool I came across re­cently which looks ex­tremely use­ful for de­vel­op­ers who work across mul­ti­ple lan­guages or pro­jects.

direnv au­to­mat­i­cally up­date en­vi­ron­ment vari­ables when you en­ter/​exit par­tic­u­lar di­rec­tory trees. Much as you might have HOME and PATH vari­ables set glob­ally in your .profile/.bashrc/etc file, direnv al­lows you to con­trol these on a per-di­rec­tory ba­sis.

This post ex­plores a few sim­ple ex­am­ples for us­age in Python, Go and Java; see be­low for fur­ther re­sources.

For Mac users, in­stal­la­tion is eas­i­est us­ing Homebrew (brew install direnv).

Automatic Python vir­tualenv switch­ing #

For Python pro­jects virtualenv pro­vides sep­a­ra­tion of de­pen­den­cies, but more than once I’ve found my­self for­get­ting to ac­ti­vate/​de­ac­ti­vate when mov­ing around be­tween pro­jects. Direnv again makes this easy and fool­proof:

layout python

When you en­ter the folder sub­tree direnv will au­to­mat­i­cally pick up an ex­ist­ing virtualenv en­vi­ron­ment and ac­ti­vate it.

For ex­am­ple:

$ 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 warn­ing oc­curs be­cause direnv has de­tected a new or changed .envrc file - a good se­cu­rity pre­cau­tion.

$ 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 vir­tualenv in­side a with py­crypto in­stalled:

$ pip freeze
	pycrypto==2.6.1
	wheel==0.24.0

Changing di­rec­tory to b is enough to au­to­mat­i­cally switch vir­tualenv.

$ 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 di­rec­tory won’t prompt for direnv allow, and al­lows seam­less changes of vir­tualenv:

$ cd ../a
	direnv: loading .envrc
	direnv: export +VIRTUAL_ENV ~PATH
$ pip freeze
	pycrypto==2.6.1
	wheel==0.24.0

Automatic set­ting of GOPATH #

If you’re de­vel­op­ing in Go, direnv can help au­to­mat­i­cally main­tain a sep­a­rate GOPATH for each in­de­pen­dent pro­ject. This can be use­ful where you’re us­ing dif­fer­ent ver­sions of de­pen­den­cies across pro­jects, and wish to main­tain some sep­a­ra­tion - with­out re­mem­ber­ing to change GOPATH your­self every time you switch pro­ject.

Quite sim­ply, place the fol­low­ing in .envrc at the root of each Go pro­ject:

layout go

Direnv will do the rest au­to­mat­i­cally; set­ting GOPATH and also up­dat­ing PATH to in­clude $GOPATH/bin.

Different ver­sions of a tool for dif­fer­ent pro­jects #

While I’m al­most al­ways work­ing with Java 8, for legacy rea­sons I’m also work­ing on a Java 7 code­base at the mo­ment. There’s noth­ing in­trin­si­cally wrong with us­ing JDK 1.8 for JDK 1.7 de­vel­op­ment, but there are some risky ar­eas I’d rather avoid. Additionally we have a need to use a fixed Gradle ver­sion rather than us­ing the sys­tem one (or, cu­ri­ously, the Gradle wrap­per).

So, I set my gen­eral pur­pose JDK to 1.8 by plac­ing 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 over­ride the Java and Gradle ver­sion specif­i­cally for the legacy pro­ject by plac­ing 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 work­ing with Java any­where in­side my home folder, direnv en­sures that I’m us­ing Java 1.8 au­to­mat­i­cally. However, if I run Java, Gradle, or check the JAVA_HOME/GRADLE_HOME en­vi­ron­ment vari­ables any­where un­der ~/projects/something, it’s go­ing to be the cor­rect, older ver­sion.

Note: this ex­am­ple could be refac­tored into a cus­tom use func­tion, but I wanted to start with some­thing that would be rea­son­ably fa­mil­iar look­ing to most *nix users.

Further read­ing #

← Home