features icon indicating copy to clipboard operation
features copied to clipboard

Install only one of `rvm` or `rbenv` for managing ruby versions

Open nevans opened this issue 1 year ago • 5 comments

Is there any benefit to installing both rvm and rbenv?

Context

rvm does so much: it installs plugins that change the behavior of rubygems and the gem command, it overrides and updates a bunch of environment variables, it updates umask, it edits the ruby binary, and it performs various other mutations on your environment whenever you load your shell or change directories.

Unfortunately, if you load rbenv or place its shims earlier in the PATH than rvm, then some truly weird situations arise. E.g: which gem reports the rbenv shim, but running gem uses the rvm installation. And rvm can "infect" the rbenv installation with its own gem plugins, and other similar clashes. This creates an unstable system with arcane error messages that can be incredibly difficult and frustrating to debug.

Even worse, the rvm documentation for how to uninstall rvm simply doesn't work inside devcontainers that use the ruby feature. After the rvm files have been removed and various /etc files have been edited, the rbenv installation might still be broken because the devcontainer-feature.json sets the GEM_HOME and GEM_PATH environment variables to rvm directories.

In situations (such as GitHub Codespaces) where the default image has both rvm and rbenv installed, this can run into trouble with user's dotfiles, which are often configured to check for rbenv and prefer it over rvm.

Workarounds

Unfortunately, setting GEM_HOME and GEM_PATH to empty strings is not the same as unsetting them. And it isn't possible to simply unset Dockerfile ENV vars. (Is it possible to unset devcontainer.json env vars?)

We can manually override those variables to something else in our own devcontainer.json files, but that still effectively breaks rbenv: rbenv uses shims to load the correct versions of ruby or gem, and those installed versions provide their own sensible dynamic defaults. This is especially useful when switching between branches with different ruby versions, when testing multiple ruby versions with a shell script that sets RBENV_VERSION, when using multiple repositories, or when multiple directories in a single repository have different .ruby-version files. Setting GEM_HOME and GEM_PATH to a static path can break all of these scenarios.

I often run into this problem on public GitHub Codespaces. My most common workaround is to simply work locally and not use a devcontainer or the Codespace. If I'm just doing a quick fix, I'll remove rbenv from my dotfiles. But when I'm using the devenv for a little bit longer, I'll workaround by first sudo rming all of the rvm dirs and then call unset GEM_HOME; unset GEM_PATH from my shell prompt.

Various proposals

The simplest approach is probably to simply not install rbenv inside the ruby feature. It's already broken, so removing it would stop wasting people's time with long, confusing, disappointing debugging sessions.

However, both anecdotally and based on github stars and forks, rbenv is more popular than rvm. It is also simpler and easier to debug. Personally, I'd rather see rbenv promoted as the default. Users should still be able to install rvm and I think it should still work, more or less. rvm breaks rbenv but rbenv doesn't break rvm. However this might not be considered backward compatible, and probably requires a major version bump.

IMO, the most important thing would be to simply remove the following lines from devcontainer-feature.json: https://github.com/devcontainers/features/blob/1869e5931cfe0517f75d58cb70863a6b4874c487/src/ruby/devcontainer-feature.json#L29-L30 Then, if a user wants to disable rvm, all they would need to do is delete or comment out /etc/profile.d/rvm.sh. And I think that a manually installed rvm could still work, so long as everything is able to load the environment from /etc/profile.d/rvm.sh.

There might be some reason that rvm is strongly preferred, e.g: installations are faster because it downloads binaries and bit-twiddles them, rather than compile them. In that case, it might be reasonable to use the "mini rvm" integration that is available for chruby: https://rvm.io/workflow/chruby. This uses mrvm to install ruby versions and chruby to switch. I believe this mrvm approach could also be made to work with rbenv, although I've never attempted this myself.

If any of these proposals sound like they might be acceptable, or if you have another better alternative in mind, I can create a draft PR for you to review.

nevans avatar Jun 29 '23 22:06 nevans