Review: Ruby Installers and Ruby Switchers
In this post I review the most popular Ruby installers (making it easier to install a Ruby) and Ruby switchers (to switch between different Rubies conveniently).
I contributed to all 3 Ruby installers when adding support for TruffleRuby, and so I have experience both with their codebase and their usage.
I am not reviewing Ruby installers or switchers for Windows as I have no experience with them.
TLDR: feel free to jump to the Conclusion and Recommendation.
Ruby Installers
RVM
RVM is probably the oldest and certainly the Ruby installer with the most features. That’s actually a doubled-edged sword, as it means RVM has a much larger codebase than all the others (25000 SLOC of Bash), and that makes it hard to maintain and to keep correct (note: I’m not counting test SLOC). Some features like gemsets seem much less needed nowadays as there is Bundler.
To give an idea, when I added support for TruffleRuby in RVM,
I had many uncertainties, and the large codebase plus a lot of global variables made it really tricky.
As a result, there were multiple bugs and a lot more effort than for other Ruby installers.
Also, RVM actually changes standard Ruby executables like rake
,
which I really dislike as a Ruby implementer because it tends to break things in subtle ways (this behavior was later disabled when installing TruffleRuby).
To be fair, RVM served me well at the time other Ruby installers/switchers did not exist.
RVM supports a lot of platforms, as well as ancient Rubies. More platforms can be nice for beginners, but platform-specific code eventually always breaks due to incompatible changes in operating systems (e.g., package names).
Pros:
- Most features
- Automatically installs system packages for many platforms
- Support the most Rubies, even very old ones (some with patches)
- Binary builds of some CRuby versions on some platforms
- Supports building
-head
versions
Cons:
- The built Ruby can differ quite a bit from a standard build, which can cause extra issues
- Huge (25000 SLOC of Bash), hard to maintain, and due to that many bugs
ruby-build
ruby-build is the best-maintained Ruby installer.
Active maintainers include @hsbt
from the CRuby core team, others and myself.
It offers a wide choice of Ruby versions.
It also offers -dev
versions, which is incredibly useful to let users check Ruby implementation bug fixes, or simply to try the latest/fastest/best build of a Ruby implementation.
The design is really straightforward (one definition per Ruby version), and as a result it’s much more reliable.
Users update ruby-build
regularly as that is the way to get new versions. This is great to also ship bug fixes quickly.
The release process is entirely automated (kudos to @mislav
).
ruby-build
does not install system packages for you,
but documents them on their wiki, which I find is much more future-proof.
Pros:
- Actively maintained
- Small, 1390 SLOC of Bash
- Supports building
-dev
versions
Cons:
- Does not install system packages on its own (but that is more future-proof, and building Rubies tend to fail clearly when there is missing dependency)
ruby-install
ruby-install is the simplest Ruby installer.
It has an interesting design where installing new versions does not require to update ruby-install
.
Instead, ruby-install --latest
is able to fetch the latest versions from ruby-versions and install them.
This is another double-edged sword.
On one hand it’s very elegant, and super easy to update.
On the other hand, if any other change is needed (e.g., different system packages, the download URL changed, etc), that actually requires ruby-install
changes, and users seem less used to update ruby-install
.
ruby-versions
only contains Ruby versions and checksums, not URLs or packages, which IMHO feels too limited.
As an example, even though I made a PR to address a changed dependency the day it was reported,
it took 8 months to have a ruby-install
release with that (to be fair, there was an easy workaround).
ruby-install
does not support development builds by design, which is IMHO a big limitation.
Pros:
- Tiny, 866 SLOC of Bash
- Elegant design
- Super easy to install the latest Ruby releases, if they don’t require
ruby-install
changes
Cons:
- Does not support
-dev
builds - Infrequent releases
- Users are not used to update
ruby-install
- Cuts a few corners in the name of simplicity
Ruby Switchers
RVM
RVM is both a Ruby installer and Ruby switcher so it appears here too. The points above really apply to both so I will not repeat them here.
The gemset feature tends to IMHO add more complexity than it helps, by making switching a 2-dimensional operation (ruby, gemset) instead of just 1 dimension (ruby). Also gemsets have symlimks in their path (not expanded, that’s how RVM knows which gemset is active), and this can occasionally cause issues.
Pros:
- Most features
Cons:
- Overrides
cd
- Loads thousands of lines of Bash in your shell
- Many features which are of little use nowadays
rbenv
rbenv is a simple Ruby switcher.
It uses small shims (executables) to always redirect to the currently-selected Ruby.
There is an overhead of that, in the order of 11 ms on my machine (pretty small, considering that ruby -e 0
takes 38 ms on my machine for ruby-3.0.1).
OTOH, it guarantees it will never pick the wrong Ruby which is obviously important.
For me, the killer feature of rbenv
is it does not mess with RubyGems.
Specifically, it does not set GEM_HOME
or GEM_PATH
(unlike the other Ruby switchers), and
so the gems corresponding to the selected Ruby are always correctly used.
That also means it expects that Rubies are installed in user-writable locations (or one uses gem install --user-install
or set Bundler path
),
but I think that’s a reasonable assumption for a Ruby switcher.
asdf is a multi-language switcher with a very similar design to rbenv
and it uses ruby-build
.
Pros:
- Just works
- Also works for non-Bash shells like
fish
- Reliable, does not mess with
GEM_HOME
- Well maintained, least number of open issues
- Small, 1500 SLOC of Bash
Cons:
- Small overhead on startup (~11 ms) due to shims
which ruby-related-executable
is not enough to show the actual executable path, one needsrbenv which
instead
chruby
chruby is a minimal Ruby switcher.
In fact, it’s so small it fits in a 100 lines of Bash.
After all, switching Rubies only requires changing PATH
and that’s it.
Unfortunately, chruby
does one additional thing which is to set GEM_HOME
.
That can be convenient to install gems when a Ruby is installed in a non user-writable location.
Unfortunately the way it’s done is incorrect for non-CRuby.
I made a PR to fix this but 18 months after it was merged there is still no release.
So at this point I feel very disappointed about the maintenance of chruby.
I still like chruby
though, and I use a branch which simply doesn’t set GEM_HOME
.
That’s just 75 SLOC of Bash, one cannot get much simpler for switching Rubies.
This is probably the most future-proof Ruby switcher out there.
FWIW, I made another PR to chruby
to use a similar approach, but still set GEM_HOME
if the default Gem home is not writable.
After lots of efforts, and even getting consensus on the related RubyGems issue for the general approach,
the PR is still not merged so I have not much hope for that (maybe it is time to hard fork chruby
…).
Pros:
- Very simple and minimal, 149 SLOC of Bash
Cons:
- Sets
GEM_HOME
incorrectly on non-CRuby - Requires to be loaded in the shell (there is chruby-fish though)
- Too rare releases, not addressing bugs promptly
Conclusion and Recommendation
In summary:
- I recommend to use
ruby-build
over RVM orruby-install
as IMHO it is a better Ruby installer in about every way. - I recommend to use
rbenv
over RVM orchruby
as it is the only Ruby switcher not messing with gem paths.
The rbenv
/ruby-build
combo is fairly natural and clearly the best maintained.
Alternatively, you can try ruby-build
+ my branch of chruby
which does not set GEM_HOME
.
This is what I use and it works perfectly from my experience.
I would recommend against RVM because of the sheer complexity and the huge codebase which is basically impossible to keep bug-free.
Note that while I might criticize various softwares in this blog post, I have a lot of respect for their authors, and I know it is a hard job to maintain such software (written in Bash, one of the worst programming languages).