Where developers come to connect, share, build and be inspired.

40

Using RVM the right way (and never typing "bundle exec" again)...

10001 views

First: Grab JewelryBox.app so you can easily flip through and see what's in all your gemsets.

Second: Set up RVM as it was meant to be. cd override and all.

Third: Your @global gemsets for MRI variants should have only the following gems:

  • Bundler
  • Rake
  • RVM

Nothing else.

For JRuby, there will be a couple others, like jruby-launcher, jruby-openssl, and bouncy-castle-java, but keep the environment MINIMAL!

Notice that I very explicitly did not put Foreman in here. If you do so, then you'll need to add bundle exec in all your Procfile entries.

You can omit Bundler if you wish, and install it into the per-project gem environment, but then you need a network connection handy just to kick-start a new Bundler-based project. Probably not worth the hassle.

Fourth: Stop inter-project version chaos by giving each project you have a .rvmrc file that looks like this:

rvm use --create ruby-X.Y.Z-pWWW@projectname

This will confine version chaos to being per-project. You'll never have a project bleed over onto others and create version chaos ever again.

Fifth: Stop intra-project version chaos by doing gem cleanup after every bundle install, or bundle update.

Sixth: In projects where you need it, add Foreman to the Gemfile, in a group off to the side. Something like this:

gem 'foreman', groups: 'cli'

Voila! No more version hell, no more "bundle exec", and that's that.

Addendum You may be wondering what purpose Rake servers in the global gemset. Simply put: This lets you avoid having a high overhead to launching Rake as your Gemfile grows in size and complexity. You can choose to load the Bundler environment by adding a block to the top of your Rakefile, but you needn't load the whole world in the process:

require 'rubygems'
require 'bundler'
Bundler.setup(:default, :rake)

This will load any gems in the default group (no group specified), and any in the :rake group, but won't create hassles by loading anything else. You can specify whichever groups you want here.

Comments

  • Blank-mugshot
    MrJoy

    rbenv doesn't even try to address things like gemsets. The attitude being that Bundler is a better way to go for that. Bundler by itself is insufficient -- it's only useful for telling you what a given project needs and is locked down to, while maintaining that information and resolving dependencies as the project evolves. It really does nothing to compartmentalize you from a polluted gem environment unless you use "bundle exec", which is awkward, annoying, and intrusive. Gemsets by themselves are insufficient as they only solve the problem of environment pollution, not version lockdown and dependency management for projects. These are solutions to two different (but related) problems.

    When I saw that rbenv was punting on gem sets entirely, I pretty much flipped the bozo bit on the project, as to me it seems that they rather missed the point. It came off to me as a reactionary project by people who find overriding the cd command to be aesthetically displeasing, and are willing to contort their workflow for the sake of purity -- even if it means not solving real problems as well as they could be. The page still contains some rather amusing FUD about how "error prone" overriding the cd command is. (Yes, I'm sure it was tricky to get it right, but once Wayne did, that's not exactly code that suffers a high rate of change. To date I have never once encountered a problem from cd being overridden, and should the day come, I can always say "builtin cd "...)

    It's not "rbenv magic" I'm worried about, it's that for things to work properly, I need to generate binstubs in every project I'm working on when setting it up, .gitignore those binstubs, and have some means of ensuring my path points at the binstubs directory early (prepended rather than appended), for whichever project I happen to be working on at the moment. I could just prepend "./bin" to my path of course but that doesn't work if I cd around within a project dir (not to mention possible side effects). It's just clunky and awkward when you have 70+ projects (not an exaggeration) using a variety of rubies, and very different versions of overlapping sets of gems. This approach is simple and I don't have to worry about version hell at all. No corner-cases, no gotchas, no user-error when switching projects -- it just works.

    That said, if rbenv is working well for you, great! Go ahead and use it! This snippet was not meant to incite a riot about rbenv vs RVM. It's for those who have chosen RVM and want to know how to use it effectively without having to layer on more cruft (gems, binstubs, and the like) to solve accidental byproducts of its misuse or of their misunderstandings of it.

  • Blank-mugshot
    MrJoy

    Simple:

    1) That doesn't solve all dependency muddling issues. 2) That requires mucking with the path even more. RVM already does that, so why not take advantage of what it's already doing?

    Binstubs are a hack to get around a polluted gem environment.

  • Blank-mugshot
    aaronjensen

    @MrJoy: By the way, the only time (in my experience) you should need to run gem regenerate_binstubs is when you install rubygems-bundler for the first time to a global gemset. And then in that case, yes you must:

    rvm all-gemsets do gem regenerate_binstubs
    

  • Avatar
    mikhailov

    WHY U NO USE Bundler binstubs?

  • Avatar
    mikhailov

    I don't think it's a hack, it's simple Unix way, the same as Rbenv. One problem = one solution.

    If you don't want Rbenv magic, just use PATH = /usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH

    And yes, I don't like RVM for some reasons.

  • Photo_on_08.01.2013_at_04.15
    sheerun

    +1 for rbenv, sorry :)

  • Blank-mugshot
    MrJoy

    Again, if you like rbenv, go ahead and use it. If you don't use RVM, why are you bothering to comment here? This tip was posted for people who have need of / a preference for RVM.

  • Blank-mugshot
    aaronjensen

    gem install rubygems-bundler gem regenerate_binstubs https://github.com/mpapis/rubygems-bundler

  • Blank-mugshot
    MrJoy

    aaron: The problem with that is that it breaks with some gems, and you need to keep binstubs up to date with each project. So, you still wind up with conflicts and problems unexpectedly when you forget a step. The solution I proposed minimizes the number of manual steps involved when switching from project to project, or updating the dependencies of a project.

  • Blank-mugshot
    aaronjensen

    @MrJoy, what does it break? you only need to regenerate_binstubs once. It is kept up to date automatically. RVM now ships with rubygems-bundler getting installed by default, so on fresh machines you literally have to do nothing but install rvm, and I've personally never had it break w/ gems.

  • Blank-mugshot
    aaronjensen

    Though I will say that it's preferred to not have to run bundle exec (or run through rubygems-bundler) from a performance perspective. There's an extra 1s or so that it takes to get through bundle exec.

    Also, I would suggest bundle clean --force rather than gem cleanup. It works better in my experience.

  • Blank-mugshot
    aaronjensen

    Also, I'm not able to get this technique to work for executables that come with gems installed via git in the Gemfile. Those still seem to require bundle exec

  • Blank-mugshot
    MrJoy

    @aaronjensen: I forget which gems were breaking under rubygems-bundler unfortunately, but I know some were.

    I'll look at "bundle clean --force" -- the biggest problem with "gem cleanup" is if you're switching branches on a project and move BACKWARD version-wise wrt gems... I've found "rvm gemset empty" to be a handy way to approach that, but of course it slows things down like crazy when you have to do a bundle install again.

    The only time I've seen bundle exec be required for a git-backed gem is when the gem stupidly tries to use Bundler to load its own dependencies. Of course it could be the case that this is the scenario in which rubygems-bundler breaks, and I'm just not remembering it...

  • Blank-mugshot
    MrJoy

    @aaronjensen: After some further testing, you're right -- git-based gems are a problem, and either binstubs, bundle exec, or rubygems-bundler + "gem regenerate_binstubs" are needed to address that.

    Using a very heavy Gemfile for testing, it looks like the fastest results are produced by doing "bundle install --binstubs", and calling "bin/", followed in distant third place by the rubygems-bundler approach, followed by bundle exec.

    However, it seems to be problematic to use rubygems-bundler from the global gemset in the context of multiple per-project gemsets -- it seems the "gem regenerate_binstubs" must be done per-project. I need to look into that more and refine this protip a bit.

    Thanks for the info!

  • Blank-mugshot
    aaronjensen

    @MrJoy: no problem. FWIW, binstubs can be automatically added to your path by RVM if you:

    chmod +x $rvm_path/hooks/after_cd_bundler
    

    Also, you may want to look into bundle install --standalone if you want the fastest startup times, it dumps a setup.rb that loads much quicker than require ''bundler/setup'does (which binstubs still does). To my knowledge there aren't any binstub or rubygems-bundler equivalents that will take advantage of it though so you'd be in uncharted territory.

    Personally, I use rubygems-bundler and I include things I run frequently and that are not git based in my .noexec.yaml:

    exclude:
      - rspec
      - rails
      - rake
    

  • Blank-mugshot
    karlgl

    Thanks for the informative post!

Add a comment