Simplify your workflow with a Version Manager like asdf

22.06.2022Raffael Schneider
DevOps asdf kontextswitching Version Manager

The life of a developer is not always easy. In addition to the fact that a developer has to deliver results, he also has the obligation of leaving behind an understandable, maintainable codebase.

Possibilities of context switching ↔️

Before you can switch from one context to another, two contexts must be able to exist in parallel and independently of each other. This assumes that contexts can be isolated. Exactly this context isolation is a driver of many thrusts in the IT world. There’s a need to isolate certain aspects of a given system in such a way that something can be easily replicated again in order to then achieve a desired end state without being influenced by external factors.

First, entire operating systems were isolated and called hardware virtualization. Of course, I am saying this in a very simplified way and leaving out many sub-concepts so as not to have to go into too much detail. The next step was virtualization, when isolation of the environment became possible at the operating system level. This is colloquially called containerization. Nonetheless, within a virtualized environment, the prevailing conditions, i.e. which software packages are installed, are paramount. A mechanism is needed to manage these packages. Package managers can perform this function, provided that no manual installation steps are used to manage them.

However, with environmental isolation via hardware or OS-level virtualization, there is still a lot of operational overhead involved. It is precisely in such situations that alternative methods provide a remedy. What I’m getting at here is that with the help of a so-called version manager like asdf you’ve got exactly one software tool with which you can bypass this operational overhead.

A version manager solves one of the most tedious problems of every developer’s daily business, namely the harmonization of the developer environment. There are other alternatives like the Nix Package Manager, but today we’re going to look at asdf as a small but mighty tool that’s especially useful for version management of local development environments.

asdf - The (easy) version manager 😇

asdf may be an unassuming or even uninspired name, but it’s a perfect nod to how transparent and accessible it can be as a developer’s tool. asdf is primarily a version manager that handles automatic versioning. asdf recognizes where you are in a directory or project and decides which version of a given toolset is appropriate for that.

If you have configured asdf correctly and know which levers you have to pull in order to be able to use it, you no longer need to worry about development versions and software environments. The really great thing about asdf is simply how easy version management is and how easy it is to expand its setup according to your needs and requirements.

asdf coming soon

The principle behind how asdf works is quite simple. There is a hidden .asdf directory in the user’s home directory. It contains the asdf entry point as a ~/.asdf/asdf.sh shell script. Once the path of this shell script has been added to the shell, one can invoke asdf. In addition to the entry point, there are other folders, such as the ~/.asdf/plugins directory, which contains all available runtimes.

asdf knows the global and local versions for different runtimes. Local versions are declared whenever a .tool-versions text file exists for a given directory and its subdirectories. It simply contains the runtime designation, a unique designation that corresponds to the corresponding asdf plugin, and its version.

1
2
3
#<target-dir>/.tool-versions
elixir 1.13.2-otp-24
erlang 24.3

Global versions are versions declared for all other directories via the asdf CLI.

How To and Best Practices 🧐

asdf is not only “straightforward” in its use, it is also very easy to install. Here I’ll do a standard install from asdf and install an Elixir runtime using the plugin. Then I declare a global version for the Elixir runtime of 1.13.2-otp-24 and a local version of 1.12-otp-23.

Installation

Installation on MacOS.

1
$ brew install asdf

For GNU/Linux or Unix-like, simply via the GitHub repo.

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

Just like brew, the above command puts the repo in a dot directory in the home directory, say ~/.asdf.

That then has to be in the .bashrc or in the .zshrc.

1
$ . $HOME/.asdf/asdf.sh

Here we make sure that the asdf.bash file is also under completions/.

1
$ . $HOME/.asdf/completions/asdf.bash

Get started

Once asdf is installed, we can actually start using it.

Plugin Installation

We’ll Elixir as an example, since we have to set up a Phoenix project anyway.

1
$ asdf plugin add elixir

Version Installation

So now asdf understands Elixir. Now we can provision any versions, that is, language runtimes for it.

1
2
3
4
5
6
7
$ asdf install elixir main-otp-24
==> Checking whether specified Elixir release exists...
==> Downloading main-otp-24 to /Users/rschneider/.asdf/downloads/elixir/main-otp-24/elixir-precompiled-main-otp-24.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 6434k  100 6434k    0     0  34.3M      0 --:--:-- --:--:-- --:--:-- 35.7M
==> Copying release into place

For this example, let’s install an older version of Elixir. To make the whole thing a little more realistic, we use an older OTP version, namely the 23 version.

1
2
3
4
5
6
7
$ asdf install elixir main-otp-23
==> Checking whether specified Elixir release exists...
==> Downloading main-otp-23 to /Users/rschneider/.asdf/downloads/elixir/main-otp-23/elixir-precompiled-main-otp-23.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 6267k  100 6267k    0     0  22.3M      0 --:--:-- --:--:-- --:--:-- 22.9M
==> Copying release into place

Define contexts

Now we put a global and several local versions.

1
$ asdf global elixir main-otp-24

If I’m in a random directory now, I can check the Elixir version as follows.

1
2
3
4
$ elixir --version
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1]

Elixir 1.14.0-dev (94bab44) (compiled with Erlang/OTP 24)

There is a 3rd party project using the 23 OTP version of Elixir that I found in a GitHub repo and will now use as an example.

1
$ asdf local elixir main-otp-23

This command places a .tool-versions file in the target directory.

1
elixir main-otp-23

Now the Elixir OTP version in this directory should also be adjusted.

1
2
3
4
$ elixir --version
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1]

Elixir 1.14.0-dev (94bab44) (compiled with Erlang/OTP 23)

✅ Hint for Elixir

With Elixir, it is important to note that an Erlang toolchain is always installed alongside Elixir. Since the Erlang toolchain also provides the OTP runtime, the Elixir version must always be compiled for the same target runtime, i.e. the same OTP platform. In this case we would have to downgrade the Erlang version to a 23 version in the local .tool-versions.

Now comes the crux. Experience has shown that all versions of Erlang are compiled. Now let’s install Erlang version 23.3. The corresponding repository is drawn from it and then compiled. This could take a while.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
❯ asdf install erlang 23.3
asdf_23.3 is not a kerl-managed Erlang/OTP installation
No build named asdf_23.3
Downloading 23.3 to /Users/rschneider/.asdf/plugins/erlang/kerl-home/archives...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   652  100   652    0     0  10086      0 --:--:-- --:--:-- --:--:-- 11438
100 94.7M  100 94.7M    0     0  28.4M      0  0:00:03  0:00:03 --:--:-- 27.6M
Extracting source code
Building Erlang/OTP 23.3 (asdf_23.3), please wait...
...

Once it’s successfully built (on my Macbook Pro with the new M1Max processor, this took about five minutes), you can include Erlang as an entry in .tool-versions as follows:

1
2
elixir main-otp-23
erlang 23.3

Now the first line of elixir --version should say “Erlang/OTP 23”. So you can use an OTP-23 capable Elixir runtime.

1
2
3
4
❯ elixir --version
Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1]

Elixir 1.14.0-dev (94bab44) (compiled with Erlang/OTP 23)

The version is also displayed in the prompt:

Best Practices 👌

In the standard version of asdf, certain files are created in the .asdf directory. What is not created by default though, is a runtime configuration. However, this is not in the .asdf directory but in the home directory. Certain settings can be adjusted there. It’s certainly best practice to know these settings and how to customize asdf’s behavior to suit your needs.

It is best to create the runtime configuration as a ~/.asdfrc file in the home directory. Here that’s described in the official documentation.

In this .asdfrc file you can adjust the following four parameters:

1
2
3
4
legacy_version_file = no
use_release_candidates = no
always_keep_download = no
plugin_repository_last_check_duration = 60

If you want to leave open the possibility of a fallback for a legacy version, you would have to set the first property to “true” like this: legacy_version_file = true.

Extensibility

There is also a relatively well-documented asdf-Plugin-Template that you can use to write your own plugin. This can also be in a private Git repository, since you can specify the Git repository as the last parameter when using a plugin. The scheme would be as follows:

1
2
$ asdf plugin add <name> [<git-url>]
$ asdf plugin add mytemplate https://github.com/b-nova-openhub/my-template.git

asdf at b-nova

Since the community can also write its own asdf plugins, there now are a large number of asdf plugins. The list is maintained in the official GitHub-Repo in asdf-vm/asdf-plugins.

We use asdf for the following runtimes:

  • asdf-graalvm
  • asdf-hashicorp (terraform)
  • asdf-java

Conclusion 🙌

asdf is a nice open source project, which simplifies a central part of the developer’s everyday life to a quasi “no-brainer”. Especially in today’s DevOps landscape, in which it is not always necessary to containerize all projects for local development, asdf is an ingenious filler and serves its purpose extremely well.

Pros and cons

  • 👍 Easy installation and setup
  • 👍 Lots and lots of plugins for pretty much every runtime, software package and programming language
  • 👍 Many versions
  • 👎 Some older or very specific versions need to be compiled, which can be time-consuming

asdf | Offzielle Webseite

asdf-vm/asdf | GitHub Repository

The future-proof solution to manage your Flutter versions: global, FVM, or asdf-vm? | iainsmith.me

asdf and Docker for Managing Local Development Dependencies | pawelurbanek.com