direnv is definitively a Go tool, in the context of it is written in Go, but it is not a development tool like the ones I covered before in previous posts (like versions, ifacecodegen, counterfeiter or retool), however it is one of those tools that you should consider using when developing programs (in any programming language really, not only Go), specially if you build programs that have different binary dependencies or any other dependencies that could be configured via environment variables.
Whats is direnv?
According to the official page:
direnv is an extension for your shell. It augments existing shells with a new feature that can load and unload environment variables depending on the current directory.
The key takeaway of that description is the powerful ability to load/unload environment variables depending on where you’re are located at when interacting with your terminal.
Why is using direnv relevant?
Consider the following hypothetical scenario: You’re building something that happens to depend on a tool to generate some code, let’s call it
tool (v1), after a while you starting working on a new project, this new project uses the same tool but a different version
tool (v2) but compared to v1 it generates code in a different way. Having
v1 installed will conflict with
v2 (and the same will apply the other way around) because both tool generate incompatible artifacts.
In the context of Go, handling dependencies like that is easily managed using Go Modules, however what about our actual runtime needs? What do I mean by that? If we depend on
tool (v1) then we need to have it installed and available in our
PATH, same story with
How can we handle this conflict? More importantly, how do we go about having those tools sandboxed?
This is where
direnv really shines, it allows us to sandbox things that happen to use environment variables as configuration options, the most common example of this is creating concrete
PATH variables to happen to be relative to your working directory, the idea is that only the binaries installed inside our folder will be used. Having this configuration allows
direnv to clearly define a boundary that is only applicable to the folder we are in.
The official Github repository includes specific steps for installing it and using it, it works like this:
- Create a new
.envrcfile wherever you want the configuration to apply for current directory and other subdirectories, and
- Configure that
.envrcto define the directives you want to apply.
For example, using our hypothetical scenario from above, when working on
project1 (which uses
tool (v1)), on the parent directory we could define the
.envrc as following:
PATH_add bin export GOBIN=$PWD/bin
- To update
PATHto point to
bin/in the current working directory and,
- To also export
GOBINto point to that path as well.
The idea is that when we
tool (v1) it will get installed under
bin/ and also when we try to execute it will be available under the same path.
If we create a similar
project2 (which uses
tool (v2)), the results will be similar. In practice what this means is that now both projects can refer to different versions of the same tool without conflicting to each other. That is a powerful feature.
I can’t recommend
direnv enough, it’s such a simple yet powerful tool that should improve your development experience.
Give it try, you won’t be disappointed.