ASDF - One version manager to manage Javascript, Python, Rust and more!

Bharat Kalluri / 2021-10-17

ASDF is a extendable version manager which supports python, ruby, golang, elixr, rust and many others.

Usually when working on multiple projects, you will not only probably need support for different languages but also be required to run multiple versions of the same language. I have a couple of NodeJS projects which run on the LTS version and some other projects run on the latest release.

To solve this problem, there are projects like NVM which manages NodeJS versions, PyEnv for managing python versions etc.. If you are a polyglot programmer, this won't scale well and you'll be left with a lot of version managers. The first version manager I used is NVM, later installed pyenv for python and rustup for Rust. This starts getting tedious real fast.

need a better solution


ASDF VM (VM stands for version manager here, not virtual machine) is a beautifully simple version manager which allows having global version and directory specific versions for tools. ASDF codebase does not internally have code for any tool or language. It is more of a meta tool which will utilize code from plugins and orchestrate version management. To install a particular tool, the first step would be to install the corresponding plugin.

To see what tools can be installed using asdf, run plugin list all

asdf plugin list all

Installing NodeJS with ASDF

Step one would be to add the nodejs plugin.

asdf plugin add nodejs

before setting up a version as a global version, that particular version needs to be installed. Here is how the latest nodejs version can be installed

asdf install nodejs latest

Now it can be set to be the global version

asdf global nodejs latest

When you do where node, the output will be ~/.asdf/shims/node. And node --version will show v16.11.1 (the current latest version).

Let us assume another project cannot run on the latest node version yet, and expects to be run on the LTS version(14.18 as of now). In that case all that needs to be done is

cd # As an example, I'm using the blog's git repo as the project
asdf install nodejs 14.18.1
asdf local nodejs 14.18.1

and now node --version will output 14.18.1 and everywhere else node will default back to the latest 16.11.1

That is the power of using a version manager. This will heavily simplify the entire process of managing multiple versions of the same languages.

The same steps can be repeated for python, ruby, rust, elixir, erlang etc... ASDF supports most of the popular programming languages and many other tools! (to see what it supports, run asdf plugin list all)

What's in the box!

How does all this work?

whats in the box

Let's start exploring from the global version. If you look at the home folder, you will find a file called .tool-versions. In my case, this is what it says

nodejs 16.11.1
python 3.10.0
rust 1.55.0

So this is what defines what the global version of multiple installed tools. If a local version is set up for a directory, there is a file created called .tool-versions in the directory which has the language and the version. So this is how ASDF understands what version to use where.

But how does the installation of plugins work? And where are these download versions stored?

How do plugins work?

To understand that, let us look at how plugins are created. There is great documentation over here

Looks like ASDF expects 3 fundamental scripts, bin/list-all, bin/download and bin/install. As the name says, they are simple scripts which just do one thing and one thing well.

Let us look at the bat plugin to understand what is actually going on. bat is an alternative to cat written in rust. It is just a simple binary, this makes it easier to understand how ASDF internally works.

In the list-all file, a fn called list-all-versions is called. Which basically runs this command

git ls-remote --tags --refs "" |
    grep -o 'refs/tags/.*' | cut -d/ -f3- |
    sed 's/^v//'

This command gets all the tags that repo has, searches, cuts and displays the releases versions. So that is how versions are listed when the command asdf list all bat (asdf list all <tool_name_here>) works.

Now, to the install script. The install script I'm referring to lives here

A couple of points to note here before I jump in. ASDF passes on some env variables which are heavily used during any installations. Let's suppose I run asdf install bat 0.12.1, then ASDF sets the $ASDF_INSTALL_PATH to ~/.asdf/installs/bat/0.12.1. This is where the files are stored. Here is the core fn which runs the install

install_bat() {
  local version=$2
  local install_path=$3
  local bin_install_path="$install_path/bin"
  local download_url; download_url="$(get_download_url "$version")"

  mkdir -p "${bin_install_path}"

  local bin_path="${bin_install_path}/bat"
  echo "Downloading bat from ${download_url}"
  curl -sSL "$download_url" -o "${install_path}/bat.tar.gz"
  tar xzf "${install_path}/bat.tar.gz" -C "${install_path}"
  mv "${install_path}/bat-v${version}-$(get_arch)-$(get_platform)"/* "${install_path}"
  mv "${install_path}/bat" "${bin_path}"
  chmod +x "$bin_path"
  rm -rf "${install_path}/bat.tar.gz" "${install_path}"/bat-*

The version and the installation path come from ASDF. There is also a custom bin install path set. This will be useful later on. The download URL is retrieved using a simple custom function which takes in the version, arch and the platform. Creates a GitHub URL and downloads that tar file. That tar file is later un compressed and the bat binary is moved to the bin folder.

The reason the bin path is important is that, ASDF creates a shim by default for everything in $install_path/bin.

What exactly is a shim?

A shim is a lib that transparently intercepts and handles all the operations elsewhere.

So, which bat says ~/.asdf/shims/bat. But the binary is just a placeholder to the actual binary over at the installation path!

This was an overview of how ASDF internally works to manage multiple versions of tools.

The end!

Hand crafted by Bharat Kalluri