Version managers have been around in the ruby ecosystem for quite some time. You switch (cd) to a project using a different ruby version, and voilà, you are magically using the desired version of ruby when running ruby, rails or any other binary that runs ruby eventually. This is not only handy, but simply a necessity given that the code you write needs to run against a certain version of ruby in production.
Since Elixir is a fairly new language, we can expect interesting features to be added continuously for the time to come, and we need to make sure the code we write works in the environment it will run on in production. Hence, switching Elixir versions should be just as easy as switching ruby versions has been for us in the past. However, since newer versions of Elixir make use of features of the underlying Erlang runtime which is changing as well, switching Elixir versions sometimes makes switching Erlang necessary as well.
In the past couple of days I have looked at several possible solutions for this and want to share my experiences with you.
Mechanisms used by version managers
To accomplish their goal, version managers use variations of these mechanisms:
- The process environment is changed in the user’s shell. This may be as simple as prepending a path to the PATH variable used by the shell for looking up binaries to execute.
- Replacement binaries called shims investigate the environment or a configuration file and dispatch to the desired version
Note that a version manager may use a combination of these two, setting a custom environment to be picked up by shims, for example when the shell changes the directory for you. A version manager may offer
- installation of different versions
- switching the version explicity for the duration of a shell session
- automatic switching (when cd’ing into a directory) using configuration files
and some offer more features specific to the tool they target.
Automatic switching is what makes day-to-day work within different projects simple and reproducable. Explicit switching offers flexibility, overriding the target version of a project for as long as I need to try something out.
Requirements for switching Elixir and Erlang
I really do not want to live without automatic switching of tools. Having to type a command to switch a version is already annoying (and error-prone), but having to do it twice because Erlang needs to be switched together with Elixir is just too much.
However in CI, I want the flexibility of explicit switching. Porting a large codebase to a newer version of ruby usually starts by switching over the CI and letting it run a couple of days, before everything else is switched over. In addition, I do not want to force a new version onto every developer of the team immediately, given that people need to get stuff done and installing a new version on a developer machine is not always as trivial as it should be. Hence I want to switch to a particular version explicitly in my build jobs.
Being able to build and install a new version and using it right away (in the same shell session) is also very nice to have. The build job I made a while ago for building newer versions of ruby and installing bundler (a ruby executable) on all build slaves has saved us a tremendous amount of time.
Oh, and it needs to support bash, because that is available on every machine I work with and always the default shell.
evm is a nice and tiny version manager for Erlang. It allows to switch versions for a shell session, but supports no version file. It can install versions.
kerl seems to build and switch Erlang versions and also deploy OTP releases. I was intimidated by the number of features and configuration it offers and did not evaluate it further.
kiex installs Elixir versions and allows switching between for a shell session. It does not support a version file.
exenv looks like it is no longer being maintained.
Erlang plus Elixir
I really wish I could solve all my requirements with a single version manager, because that would seriously simplify the setup. Several projects target more than one technology and deserved a closer look. Sadly I haven’t found any of them to meet my requirements (but wait for the nice hack at the end of the article).
erln8 Installs and switches Erlang and Elixir.
Its dispatch mechanism relies on a version file in the current directory or above, which prevented Elixir from being installed because the installation process cd’s to a path below /tmp.
asdf has a plugin mechanism to support any kind of target, providing Erlang and Elixir (and ruby). Very promising! I could however not get it to switch tools for just a shell session.
No combination of version managers I evaluated offered an immediate solution to my requirements. I source-dived into different version managers and found out that all I needed was installation and switching for the duration of a shell session. Automatic switching turns out to be easily implemented on top, also it feels less fragile to take control of the auto-switching process instead of
different managers fighting over my shell.
These are the managers I currently use:
- evm for Erlang
- kiex for Elixir
- chruby, as before, for ruby
I removed chruby’s auto-switching from my shell setup and replaced it with a few lines of code which manage all three of them. So far I am really happy with my new setup!
Here is how I build my Elixir environment inside the CI. This gist is a build job which installs versions of Erlang and Elixir, then hex, rebar and dialyzer, which it also builds the PLT for.
I learned a lot during the process, and even got some improvements to evm merged in. If you like, check out xvm if it works for you. Also, I would like to hear how you are dealing with switching Elixir/Erlang versions!