I’ve tried juggling virtualenvs by hand. Who has the time to manually activate and deactivate when jumping between projects?
I’ve tried language-specific version managers. Sometimes they work. Sometimes you have to wait double-digit seconds for re-shimming.
I’ve tried using tool-specific wrappers like tfenv. The cognitive burden increases exponentially with each one added to the pile.
But none of them solve the entire problem. Maybe this project needs a specific version of Python. Maybe that one is stuck on a weird version of Terraform. Yet another is using a deprecated Helm version, but the package manager only has the latest patch in that series.
Direnv does a lot out of the box, but its strength is configuring projects to use an available version of a tool, it does not provide those versions itself. Some languages have competent version manager tools that are worth using, but operational tools like terraform and kubectl generally lack version managers.
That’s where asdf comes in. Not only is it a joy to type, when combined with direnv it’s actually fast. Putting tool paths into a direnv include means no more waiting for slow re-shimming, running commands manually, or re-hashing your shell cache.
It’s as simple as:
brew install direnv asdf
direnv hook
asdf plugin add direnv
asdf direnv setup --version system
You can then add use asdf
to your .envrc
file, and the plugin will modify PATH based on .tool-versions
, e.g.:
$ echo "use asdf" >> project/.envrc
$ direnv allow project
$ cat << EOF > project/.tool-versions
helm 2.16.3
kubectl 1.20.15
terraform 0.14.11
EOF
Now, not only is the python virtualenv automatically activated and deactivated, so are those tools:
> $ cd project
direnv: loading ~/project/.envrc
direnv: using asdf
direnv: loading ~/.cache/asdf-direnv/env/2471675625-973451084-2702573808-1004245725
direnv: using asdf terraform 0.14.11
direnv: using asdf kubectl 1.20.15
direnv: using asdf helm 2.16.3
direnv: export ~PATH
But what about virtual environments?
asdf is great for managing tool versions, but some tools have baggage. Like python virtual environments. For that, we’ll need something a bit more specialized: pyenv.
$ brew install pyenv
$ echo _PYENV_DIR="$(pyenv prefix)" >> ~/.zshrc
$ source ~/.zshrc
direnv “layout” mode places files (such as virtualenvs) into the ~/.direnv
folder within the project:
$ echo "layout pyenv 3.10.10" >> project/.envrc
$ direnv allow project
$ cd project
direnv: loading ~/project/.envrc
direnv: export +PYENV_VERSION +VIRTUAL_ENV ~PATH
$ which python
/redacted/project/.direnv/python-3.10.10/bin/python
Python packages can be managed normally from here via pip
. Similar plugins exist for node, ruby, etc, and can be found in the direnv stdlib man page.