For Ruby you have rvm, rbenv, ry and a dozen other version managers.
For Elixir you have kiex and exenv.
For Erlang you have evm, erln8 and kerl.
For Node.js you have nvm and nvmw.
You get the idea. There are version managers for almost every language out there. Each of these languages have specific environmental requirements that must be met, each one slightly different from the others. But there are a lot of similarities between these tools. Most of them alter your PATH
environment variable based on which version you have selected. They might also set a few other custom environment variables needed for the language to function properly. Some are more invasive and replace shell commands, like cd
, with their own versions that handle version switching. But at the core, all of them alter your shell environment so that different executables are used based on the version you select. This is a common characteristic of all these version managers.
Using all these version managers also adds a lot of overhead as each is working to customize your environment. You have to ensure you have the version manager installed and configured correctly. You also have to ensure that the version managers don’t interfere with each other. Since they are all altering the shell environment there is a good chance they will affect each other in some way.
So what’s the solution to all this? Are we stuck juggling different version managers for everything? I did a lot of searching and asked a lot of questions online and found nearly a dozen open source projects out there aimed at solving this problem. But in this post I want to focus on one that I found to be particularly good at solving this issue - asdf.
Note that I was looking for a tool to help manage software versions in my development environment on my laptop. I don’t normally run different versions of the same VM or interpreter on production servers. Only in development do I have a need to for multiple language versions to be installed. As a result, all my thoughts in this post are about asdf in development, not production.
##What is asdf?
Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more
asdf is an extendable version manager with support for Ruby, Node.js, Elixir and Erlang (and now Lua). It was created by Akash Manohar (@HashNuke) and was designed to replace all the language-specific version managers. It’s also 100% shell script, which makes it easy to install and relatively portable. asdf doesn’t include support for any language directly. Each language is supported via a plugin that contains all the language specific version management details. asdf’s plugin system makes it easy to install, upgrade and remove plugins as needed. The plugin system also makes it easy to add support for new languages by third parties. asdf plugins are just plain old git repositories with a few shell scripts in them. Installation of a plugin can be done with the asdf plugin-add
command.
Installation of different language versions is very easy as well. Versions can be set globally or on a per-directory basis with a .tool-versions
file, which can define the language versions to be used for a directory. If you were working on a Ruby and Elixir project, you would normally set the Ruby version in one place (e.g. the .ruby-version
file) and the Elixir version in another (e.g. the .exenv-version
file). With asdf you create a .tool-versions
file and define all the language version requirements for the project in it.
##Generic Version Managers Like This Already Exist, What Makes asdf Different?
asdf has several advantages over the other version managers available:
- Simplicity - asdf is by far the most simple and easy to install
- Complete set of version management features - asdf has simple commands to install, list, and remove software versions. Commands are also integrated with the
.tool-versions
file. There are also commands for adding, upgrading and removing plugins. - Simple method of setting defaults - All defaults can be set by creating a
.tool-versions
file in your home directory. If a subdirectory you are in doesn’t contain a.tool-versions
file asdf crawls up the directory tree looking for one. If it gets to your home directory without finding one it uses the defaults you have set there. - Extendable - Plugins are easy to install and write and can be designed to do almost anything you want.
Below I list a few of the more popular projects along with some of the issues I see with them and some of the features asdf has that they lack. These are good projects with a lot of great people behind them. This comparison isn’t to bash the alternatives, but rather to compare their differences. Most of these projects offer more flexibility than asdf in some areas, and as a result often lack some of the features present in asdf.
- Autoenv - https://github.com/kennethreitz/autoenv
- Written in Bash and Python
- overrides the
cd
command. - Lacks plugins and installation commands. You have to write bash files on a per-directory basis.
- Autoenv for Zsh - https://github.com/Tarrasch/zsh-autoenv
- Written in Zsh and Perl
- Lacks plugins and installation commands. You have to write bash files on a per-directory basis. Only works in Zsh.
- smartcd - https://github.com/cxreg/smartcd
- Written in Perl and Bash
- overrides the
cd
command. - Lacks plugins and installation commands. You have to write bash files on a per-directory basis.
- direnv - https://github.com/direnv/direnv
- Written in Go and Bash
- Customizes environment on a per-directory basis with a .envrc file
- Lacks plugins and installation commands. You have to write bash files on a per-directory basis (.envrc).
- Environment Modules - http://modules.sourceforge.net/
- Written in C
- Customizes environment on a per-directory basis
- Lacks plugins and installation commands.
- Nix - https://nixos.org/nix/
- Written in Perl and C++
- Customizes environment on a per-shell basis
- You have to create Nix profiles for each different project environment you need. Profiles need to be created using nix commands.
While features of many of these tools are similar to asdf, none match the simplicity and easy of use of asdf. asdf is 100% shell script, whereas the alternatives are partially implemented in other languages like Python, Go and C. The addition of another language often makes installation more difficult and usually adds several more dependencies to the tool. Many of these tools also require you to write shell scripts to setup the environment for each project which is something I find very tiresome when setting up small projects.
##But asdf Doesn’t Support the Language I Use!
There is a plugin API that is extremely simple and very well documented (I can attest to this since I developed the Lua plugin for asdf). Development of a plugin for a new language or framework is straightforward and can be done in a couple of hours.
##How Does It Work?
asdf uses shims to ensure the right executable is run. Shims are stored in a directory that is part of your PATH
. When you execute a shim that shim figures out your current directory and looks for a .tool-versions
file in it. If one exists it reads the version information and determines which one of the real executables should be executed. For example, suppose you have ruby 2.2.4 and ruby 1.9.3 installed. Each install includes an irb
executable but neither are on your PATH
. If you are in a project that uses Ruby 2.2.4 (because it’s defined in your .tool-versions
file), the irb
executable in the 2.2.4 install is the executable that should be run. The shim will see that your in a project that requires Ruby 2.2.4 and look for the irb
executable in that Ruby install and execute it. If for some reason you didn’t have Ruby 2.2.4 installed the executable wouldn’t be found (since that Ruby installation doesn’t exist on your machine) and ruby 2.2.4 not installed
will be printed by the shim. Since this only happens when you run an executable the asdf overhead is kept to a minimum. Of course there are many more details in the source code that I won’t bother to mention here.
##How Do I Use It?
####Installation Installation is simple:
$ git clone https://github.com/HashNuke/asdf.git ~/.asdf
# For Ubuntu or other linux distros
$ echo '. $HOME/.asdf/asdf.sh' >> ~/.bashrc
# OR for Max OSX
$ echo '. $HOME/.asdf/asdf.sh' >> ~/.bash_profile
Then install the dependencies:
- On OSX install
automake autoconf openssl libyaml readline libxslt libtool unixodbc
with homebrew. - On Ubuntu install
automake autoconf libreadline-dev libncurses-dev libssl-dev libyaml-dev libxslt-dev libffi-dev libtool unixodbc-dev
with apt-get.
That’s it! You should be able to view asdf help by running asdf
in the shell.
####Usage
Say we want to install ruby 2.2.4 for a project we are going to build. First we have to install the ruby plugin (you will only need to do this once):
$ asdf plugin-add ruby https://github.com/HashNuke/asdf-ruby.git
Now we can install ruby 2.2.4:
$ asdf install ruby 2.2.4
Next we just cd
into our project directory and create a .tool-versions
file inside it containing the ruby version:
$ echo 'ruby 2.2.4' >> .tool-versions
That’s it! Now we can use all the executables that came with the Ruby 2.2.4 install in our project.
##Summary
I have been using asdf exclusively at work and at home for almost 4 months now. I have been able to uninstall a lot of software and simplify my dotfiles a lot since switching to asdf. Overall it has provided a big a boost to my productivity. Now I don’t even think about version management most of the time. With the few Erlang version managers available I was always struggling to come up with a good strategy for Erlang version management. With asdf, Erlang version management just works. I hope you will take a look at the project and give it a try. You won’t regret trying it.
##Resources
- asdf: https://github.com/HashNuke/asdf
- asdf-lua: https://github.com/Stratus3D/asdf-lua
- http://cazrin.net/blog/2016/my-favourite-version-management-tool/