Skip to main content

Switching from pyenv, rbenv, goenv and nvm to asdf

·885 words·5 mins

So, there was a time when I was only developing applications using Python. And so I found out about virtual environments. And then after a couple of months, I discovered pyenv.

It also came to a time I had to work on multiple projects that uses different versions of nodejs and searched something similar so I installed nvm.

Then, I was required to work on a Ruby project so I installed rbenv.

And since golang was one of the new shiny objects that kinda got my interest, I went on and installed goenv.

Everything was fine until I kinda felt my file got bloated and somehow nvm was kinda slowing down my shell startup.

I did do some optimizations such as lazy loading nvm and used a fish plugin called fish-nvm.

And then one day, I found out about asdf-vm.

At first, I was quite hesitant to install it since it would kinda disrupt my work flow with python projects since I heavily use pyenv virtualenv <version> <name>.

But I did feel that the benefits of using asdf-vm outweighs the cons so I went ahead and proceeded.

Installation #

$ git clone ~/.asdf --branch v0.7.8

asdf has a pretty good documentation to be honest. Despite brew being my go to package manager, I chose git as my installation method. My reason is that asdf requires me to add this line in my

source (brew --prefix asdf)/

and the brew --prefix adsf is just too slow compared to just having this

source $HOME/.asdf/

Next, for the auto completions just run this command in your terminal:

$ mkdir -p ~/.config/fish/completions; and cp ~/.asdf/completions/ ~/.config/fish/completions

Restart your shell so that PATH changes take effect!

You can checkout a more detailed documentation here:

Before, my looked something like this:

# pyenv
set -gx PYENV_ROOT $HOME/.pyenv
set -gx PYTHON_BUILD_ARIA2_OPTS "-x 10 -k 1M" # Use aria2c when downloading
contains $PYENV_ROOT/bin $fish_user_paths; or set -Ua fish_user_paths $PYENV_ROOT/bin
status --is-interactive; and pyenv init - | source
status --is-interactive; and pyenv virtualenv-init - | source

# goenv
status --is-interactive; and goenv init - | source

# rbenv
status --is-interactive; and rbenv init - | source

# Set nvm aliases and add to path
set -gx nvm_alias_output $HOME/.node_aliases
contains $nvm_alias_output $fish_user_paths; or set -Ua fish_user_paths $nvm_alias_output


# pyenv (asdf still uses pyenv under the hood)
set -gx PYTHON_BUILD_ARIA2_OPTS "-x 10 -k 1M" # Use aria2c when downloading

# asdf
source $HOME/.asdf/

Installing pyenv, rbenv, goenv, and nvm replacements #

Again, most of the things I put here just came from the documentation:

pyenv replacement #

$ asdf plugin add python
$ asdf install python latest:3 # At the moment of writing this, it installed 3.8.4
$ asdf global python 3.8.4 # This sets python 3.8.4 as our default python version

rbenv replacement #

$ asdf plugin add ruby
$ asdf install ruby latest # We can omit the version number. Currently installs 2.7.1
$ asdf global ruby 2.7.1

goenv replacement #

$ asdf plugin add golang
$ asdf install golang latest # 1.14.6
$ asdf global golang 1.14.6

nvm replacement #

$ asdf plugin add nodejs
$ asdf install nodejs 12.18.2
$ asdf global nodejs 12.18.2

Using asdf global creates a file under your HOME directory called .tool-versions

$ cat ~/.tool-versions
python 3.8.4
ruby 2.7.1
golang 1.14.6
nodejs 12.18.2

This lets asdf know which versions to use. And of course, in contrast to global, there is also the local keyword that creates another .tool-versions. This is useful when projects require different version.

$ cd ~/project1
$ asdf local python 3.7.5
$ python --version # Uses the python version specified in .tool-versions
$ cd ~/project2
$ python --version # Uses the python version specified in ~/.tool-versions

asdf has a lot of plugins available. You can check them out here:

Extras #

In order to accommodate my work flow when I was still using pyenv virtualenv, I created a function that behaves quite similar to pyenv virtualenv.

$ touch ~/.config/fish/functions/

And paste the following:

function venv --argument-names 'python_version' --description 'Create virtualenv named the same as current directory'
  set -l python_bin

  if not test -n "$python_version"
    # Use default python version set by asdf
    set python_bin ($HOME/.asdf/bin/asdf which python)
    set python_bin $ASDF_DIR/installs/python/$python_version/bin/python

  set -l venv_name (basename $PWD | tr . -)

  if not test -e $python_bin
    echo "Python version `$python_version` is not installed."
    return 1

  echo Creating virtualenv `$venv_name`
  $python_bin -m venv $HOME/.virtualenvs/$venv_name
  source $HOME/.virtualenvs/$venv_name/bin/

Whenever I’m inside a python project, I just need to type venv or venv <python_version> and it will automatically create a virtualenv under ~/.virtualenvs using the current directory name.

In order to automatically activate the virtualenv when cding to a project, do the following:

$ touch ~/.config/fish/conf.d/

And paste the following:

function __auto_venv --on-variable PWD --description "Automatically activate python venv"
  set -l venv_name (basename $PWD | tr . -)

  if test -d $HOME/.virtualenvs/$venv_name
    source $HOME/.virtualenvs/$venv_name/bin/

Cool! Not only did I reduce the lines in my, I could also notice a decreased startup time which is a good thing!

You can also check out my dotfiles in my GitHub repository:

I hope this article helped you! You can leave a comment below and I’ll try to answer them as fast as I can.

Thanks for reading! 🎉