nix aside1, I recently shifted to using asdf to manage different language versions.

1git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.8.1

The main reason to prefer asdf over competing language specific options like rvm or pyenv or nvm and company is simply uniformity of the interface. This can be coupled with zinit snippet OMZ::plugins/asdf for loading the completions. Note that the installation steps can take a while especially if openssl is being installed.

Configuration

Actually one weird aspect of asdf is that it pollutes $HOME by default instead of respectfully storing its configuration in $HOME/.config/asdf like every other program. To change this; exporting ASDF_CONFIG_FILE works. Essentially a zsh configuration with zinit would look like this2:

1if [[ ! -d ~/.asdf ]]; then
2    mkdir ~/.asdf
3    git clone https://github.com/asdf-vm/asdf.git ~/.asdf
4fi
5
6export ASDF_CONFIG_FILE="~/.config/asdf/asdfrc"
7zinit snippet OMZ::plugins/asdf

asdf has an option; legacy_version_file = yes which is meant to ensure that existing solutions (e.g. .nvmrc or .node-version) are compatible. There is also $HOME/.tool-versions for trickle-down language versions; that is language-version pairs defined in such files are activated for the directory and lower sub-directories. The rest of a reasonable configuration (described here) might look like:

1legacy_version_file = yes
2always_keep_download = no

Ruby

This plugin leverages ruby-build and so can take a pick up an extended set of environment variables; it supports .ruby-version files as well.

1asdf plugin add ruby
2asdf list all ruby
3asdf install ruby 3.0.2
4asdf global ruby 3.0.2 # Convenience

NodeJS

This plugin respects .nvmrc and .node-version files.

1asdf plugin add nodejs
2asdf global nodejs latest
3asdf install nodejs latest

Python

Builds with python-build.

1asdf plugin add python
2asdf install python 3.9.7
3asdf global python 3.9.7

Direnv

I’m a huge fan of direnv; and a fantastic little plugin (with neat hyperfine benchmarks) for asdf removes a layer of shim indirection while playing nicely with direnv.

1asdf plugin-add direnv
2asdf install direnv latest
3asdf global direnv latest

This comes with an associated set of shell profile instructions:

1# File: ~/.zshrc
2# Hook direnv into your shell.
3eval "$(asdf exec direnv hook zsh)"
4# A shortcut for asdf managed direnv.
5direnv() { asdf exec direnv "$@"; }

Along with a commiserate $HOME/.config/direnv/direnvrc requirement:

1source "$(asdf direnv hook asdf)"

Now every new .envrc can start with use asdf which will now speed up evaluations.

Conclusions

This method appears to be more robust than remembering the various idiosyncrasies and logic of a host of other tools.


  1. This was during the build plan dynamism RFCs which themselves were symptomatic of the {yarn,composer,node,*}2nix issues ↩︎

  2. This structure can be seen in my own Dotfiles as well ↩︎