How to Manage Multiple Runtime Versions With a Single Tooling

A quick walkthrough on asdf CLI

This article was published on Jul 08, 2021, and takes approximately 6 minute(s) to read.

Hey, devs.

Today I want to present to you a simpler and (IMO) better way to have installed different versions of the same runtime in your machine with a single tool called asdf.

The tool itself is relatively simple, the docs are well written and you won’t have trouble getting started asap.

Here I will just make a summary and present you some gotchas I had after a while using it.

The Hell of Runtime Versions

Back in 2013 when I was learning development in college, I remember having a lot of trouble running some projects because they rely on a specific Java version.

“Oh, this one uses Java 5, I need to download the runtime. But wait, I already have Java 6 in my machine… damn”

This was very overwhelming, especially because I didn’t have much experience and back then my professors and colleagues didn’t know a better way of easing this problem.

In the first company I started work as a software developer, all backend apps were written in ruby and well… the exact same problem.

To avoid consuming the production/staging environment while I was coding my client-side application, I needed to run a couple of servers in my machine and, again, they required different ruby versions.

Luckily the backend team had a wiki page recommending to use a tool called RVM (Ruby Version Manager), which as the name already explains itself, is a CLI to control multiple ruby versions in our machine.

And it worked pretty well, to be honest.

After a while coding front-end applications, I started to see more often the same problem happening with node js.

This project uses node 7. But I have node 10 installed... Oh no…

When I googled to find a similar solution as rvm, I found NVM (Node Version Manager), which does the same thing as rvm but for the Node runtime.

Ok, now I have 2 CLIs to handle the same problem.

It was then I started to study Go and thought: “oh, here we go again… another CLI”.

After I complained about that to a colleague he said:

Why the heck don't you use asdf for all these languages?

And my mind explodes because of course, if I was bothered dealing with that, someone in the plant was bothered as well, and most importantly: he/she had already created a tool that solves this problem.

The Master CLI: asdf

asdf is a CLI tool that solves the runtime version management in a well-architected and elegant way: by being a single tool for all runtimes.

The idea is that asdf is a core application that does the handling heavy-lifting of providing CLI options, being hooked via terminal, etc. but instead of also solving the version available for every single runtime that exists, it delegates this to third party plugins.

asdf workflow

This means that asdf does not care about any runtime like java, go, deno, rust, or whatever, but it provides an abstract interface where someone in the community can simply create and maintain a plugin that provides all the information to download the version X for example.

If you’re familiar with Front-end development, this is pretty much like babel does. It handles our code but you can write your plugin that hooks there and do something else without creating a burden to the Babel’s team to maintain the code itself.

The following sections will show how the tooling works. If you want to try, you need to make sure that you have asdf properly installed and configured in your bash, fish, or zsh terminal configuration.

asdf installation guide

Plugins

As I mentioned, a plugin is a specific runtime manager. In that sense, the asdf-ruby plugin only deals with how to download ruby runtime, which version was released, etc. Installing a plugin is relatively easy but I strongly recommend you check the plugin’s repository and see if there are more instructions needed before adding it.

Check here all plugins available for asdf

In general, the instruction follows the same basic way of installing, running asdf plugin add <plugin-name> <git-url>. The git-url isn’t required but recommended just to be sure you’re consuming for the correct repository:

asdf plugin add nodejs
asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git

Now, my asdf will have 2 plugins: one capable to manage node versions and another to manage ruby versions.

Versions

The plugin itself won’t add any version by default. It’s time to finally install some versions we want to use in some projects.

For this example I want to have in my machine: Latest 10.2 version of node Latest 14 version of node Exact 2.6.5 version of Ruby Latest 2.7 version of Ruby

For all these versions I'll run asdf install <plugin-name> <version>. So all I need to do is running in the terminal:

asdf install nodejs latest:10.2
asdf install nodejs latest:14
asdf install ruby 2.6.5
asdf install ruby latest:2.7

Obs.: depending on your environment you might need to install some stuff before being able to install a runtime, like ruby for example that requires having libssl-dev, etc, in a Linux machine before installing successfully.

Note that since in some cases we didn't care about the exact version we simply specified a latest: prefix before the version number and this will end up installing the latest version available for that number.

Now if we want to know the versions installed, we can simply run asdf list <plugin-name>:

asdf list nodejs
 10.24.1
 14.17.3
 
asdf list ruby
 2.6.5
 2.7.4

Defining a runtime version

There are 2 cases where you want to define a version: for global and local scope.

Global

We always need to define a version for the global context. Personally, I like to use the latest LTS version of NodeJS (today 14.17) by default and fallback down depending on the project

To do that all I need to do is running asdf global <plugin-name> <version>:

asdf global nodejs 14.17.3

Now if I check my node version it'll show this specific version:

node -v
v14.17.3

Local

And now, back to the main problem, we're trying to solve: running a specific version for a specific project.

For specifying a local version, all we need to do is to run asdf local <plugin-name> <version>:

asdf local nodejs 10.24.1

By doing that, asdf will create a file in the folder you’re called .tool-versions that contains the following content:

nodejs 10.24.1

Now, every time I run yarn start for example (which will invoke somehow node runtime) because asdf is intercepting the call and finds this file, it’ll use version 10 instead of my global 14.

Tip: if for some reason you don't want to commit the .tool-versions file you can either add it in the .gitignore of your project or if this rule will be applied to all projects, setting this ignore globally like I explain here

Removing a plugin

Let's say now you left the company and will no longer need ruby versions in your machine.

All you need to do is by simply removing the plugin with asdf plugin remove <plugin-name>:

asdf plugin remove ruby

BY doing that, the ruby plugin and all versions we've installed in the previous steps will be completely removed from our machine.

Compatibility

If you already are using another tooling and want to migrate to asdf without having to remove the existing version file, in your $HOME path you can create a file called .asdfrc and add the flag legacy version file:

legacy_version_file = yes

More info: https://asdf-vm.com/#/core-configuration?id=tool-versions

Conclusion

I hope now it’s clear to you how awesome asdf is not only to solve this cumbersome version problem but also in terms of using a very robust plugin mechanism for the correct problem.

Any questions or comments, please reach out on Twitter.

Cheers.

References