Blog

No more bundle exec: using the new RUBYGEMS_GEMDEPS environment variable

Placeholder Avatar
Leo (Jialiang) Liang
November 11, 2015

TL;DR

RUBYGEMS_GEMDEPS is a ‘new’ environment variable for RubyGems(>=2.2.0). With this line of code in your .zshrc or .bashrc:

export RUBYGEMS_GEMDEPS=-

You don’t need to type bundle exec anymore. However, RubyGems does not fully support Gemfile syntax yet and still has some fairly serious bugs waiting to be fixed with RUBYGEMS_GEMDEPS, so don’t use this feature in your daily development environment at the moment. Now, if you are still interested, read on for more info :)

The Issue with binstub

Back in the day when Rails 3 had just been released, many Rails developers were thrilled by the introduction of bundler, a gem that helped our beloved Rails applications resolve gem dependencies in the project, letting it install and use the correct version of each gem. With bundler, Rails applications always use the expected gems (and versions thereof), which makes us a bunch of happy developers.

But those happy days didn’t last very long. Soon, we found that when we run commands like rails or rake, they use binstubs generated by RubyGems, and don’t use the information from the Gemfile or Gemfile.lock files.

For example, let’s say there are three versions of rack in my development environment, like this:

gem list rack #=> rack (1.6.4, 1.5.5, 1.5.4)

And I have a Gemfile like this in my project:

ruby "2.2.2" source "https://rubygems.org" gem 'rack', '~>1.5.4'

and the Gemfile.lock looks like this:

GEM remote: https://rubygems.org/ specs: rack (1.5.4) PLATFORMS ruby DEPENDENCIES rack (~> 1.5.4) BUNDLED WITH 1.10.6

Ideally, when I run rackup --version, the result would be:

#=> Rack 1.2 (Release: 1.5.4)

But instead it returns:

#=> Rack 1.3 (Release: 1.6.4)

That is not what I want; I would expect it use rack 1.5.4, not the other version. Luckily, the smart developers who built bundler had already foreseen this issue and provided the command bundle exec for us to use; meaning that we can run binstubs generated by bundler with this command: bundle exec rackup --version, and bundler would pick correct version of rack for us:

#=> Rack 1.2 (Release: 1.5.4)

Some of us live with it, but some don’t, because we still need to type bundle exec every time we want to run a command. The latter group found some ways to get around this issue, like using rubygems-bundler, and adding a project-specific binstub folder to the top of $PATH. But we all wish that one day, RubyGems can support the Gemfile and Gemfile.lock files, so that we can just type rackup without any setup required.

Obviously, the RubyGems team is aware of this issue, and they have finally introduced the RUBYGEMS_GEMDEPS environment variable in rubygems 2.2.0.

RUBYGEMS_GEMDEPS

According to RubyGems: > RubyGems can install a consistent set of gems across multiple environments using gem install -g when a gem dependencies file (gem.deps.rb, Gemfile or Isolate) is present. If no explicit file is given RubyGems attempts to find one in the current directory. > > When the RUBYGEMS_GEMDEPS environment variable is set to a gem dependencies file the gems from that file will be activated at startup time. Set it to a specific filename or to “-“ to have RubyGems automatically discover the gem dependencies file by walking up from the current directory. In short, once you put this line in your ~/.bashrc or ~/.zshrc file:

export RUBYGEMS_GEMDEPS=-

Then RubyGems will look for a Gemfile in the current directory, and use the correct version of gems for you. To prove this, I ran the same command that I ran before, and got following output:

rackup --version #=> Rack 1.2 (Release: 1.5.4)

RubyGems is smart enough to pick the version in Gemfile.lock, not only the Gemfile. So now we can get rid of rubygems-bundler, and we no longer need project binstub folders in the $PATH. Great! ;) Moreover, note that the latest version of rubygems (2.4.8) still works with Ruby 1.8.7, so you can even use RUBYGEMS_GEMDEPS in ruby 1.8.7!

But…

Wait, if this function is so great, how come it still isn’t being widely used by the Ruby community? After all, rubygems 2.2.0 came out at the end of 2013. Unfortunately, that is because RUBYGEMS_GEMDEPS still has some fairly serious bugs waiting to be fixed. One issue I found is that if there are git repos in your Gemfile, many gem related commands will become really slow. For example, with the following Gemfile: ```

ruby “2.2.2” source “https://rubygems.org”

gem ‘multi_json’, git: “https://github.com/intridea/multi_json.git” gem ‘tilt’, git: “https://github.com/rtomayko/tilt.git” gem ‘rack’, git: “https://github.com/rack/rack.git” gem ‘vcr’, git: “https://github.com/vcr/vcr.git” gem ‘shoulda-matchers’, git: “https://github.com/thoughtbot/shoulda-matchers.git” ```

You can see that most of the commands take more than 15s to run:

time bundle #after all gems installed #=> bundle 1.02s user 0.46s system 9% cpu 15.658 total

time gem --version #=> gem --version 0.68s user 0.Matenia Rossidess system 7% cpu 14.536 total

time gem list tile #=> gem list tilt 0.85s user 0.44s system 4% cpu 29.202 total

You can find more information about this here.

Conclusion

I don’t suggest that you use this feature in your daily development environment at the moment. However, I do believe that sometime in the near future, RUBYGEMS_GEMDEPS will be ready, and will be the best approach for handling the binstub issue. So I encourage you to give it a try, and submit any issues you find here!