maljub01/rbenv-bundle-exec

how does it interract with rbenv-binstubs?

Closed this issue · 11 comments

rails 4+ comes with a few binstubs and it seems to make sense to let rbenv-binstubs to invoke those first (and whatever else one might put there) and then use rbenv-bundle-exec on everything else.

I wouldn't recommend having this setup, but you shouldn't face any issues if you have rbenv-bundle-exec and rbenv-binstubs on the same system.

I just tried it locally before posting this reply. There will be more redirections before rbenv runs the correct executable, but other than that everything should work just fine.

Is there any specific reason you would:

  • Want to have both rbenv-bundle-exec and rbenv-binstubs on the same machine?
  • Want to make sure the binstubs provided by rbenv-binstubs get executed?

The default binstubs are provided by rails, so it seemed like a good idea to run them. I haven't verified but it's likely that they are equivalent to normal bundle exec, however is it not conceivable that binstubs could be used for other purposes beyond bundler? They can be part of the source and customized, in which case you would really want to run them.

I did some more digging into this issue, and here's what I've found:

When you run a rails command inside a rails application (ex. rails --version), here's what happens:

  1. Assuming your $PATH is set up correctly, your shell will run the rbenv binstub ~/.rbenv/shims/rails. This binstub catches your rails invocations and executes them through rbenv exec.
  2. After going through rbenv's internal logic, the binstub generated by RubyGems gets executed: ~/.rbenv/versions/$RUBY_VERSION/bin/rails. This binstub is used by RubyGems to decide which gem to load (incl. which version) & the binary to run.
  3. The rails executable is found to be provided by railties and gets executed: ~/.rbenv/versions/$RUBY_VERSION/lib/ruby/gems/$RUBY_LOAD_VERSION/gems/railties-$RAILTIES_VERSION/bin/rails. Now the railties gem has control over what happens next.
  4. Rails then requires rails/cli, which is located at: ~/.rbenv/versions/$RUBY_VERSION/lib/ruby/gems/$RUBY_LOAD_VERSION/gems/railties-$RAILTIES_VERSION/lib/rails/cli.rb
  5. Because we're inside a rails application, cli.rb calls Rails::AppRailsLoader.exec_app_rails. This is defined in: ~/.rbenv/versions/$RUBY_VERSION/lib/ruby/gems/$RUBY_LOAD_VERSION/gems/railties-$RAILTIES_VERSION/lib/rails/app_rails_loader.rb
  6. The app loader then executes ./bin/rails, the binstub in question here.
  7. Rails makes sure it executes within your Gemfile's locked context by requiring bundler/setup from ./config/boot.rb

rbenv-bundle-exec modifies rbenv's internal logic (at step 1) to execute your commands through bundle exec. Bundler's own internal logic handles finding the correct rails executable and execution will continue from step 3 exactly the same way. This means that whether you choose to run rails using ./bin/rails, bundle exec rails, or just by running rails with rbenv-bundle-exec installed you should get the same results.

You are certainly right about the fact that binstubs could be used for other purposes beyond the ones we're dealing with here. In fact, binstubs are also used by spring as well.

Nevertheless, I believe rbenv-bundle-exec should restrict itself to the simple act of forwarding ruby commands to bundle exec where appropriate and with minimal surprises. I would love to hear more opinions on this if you or anyone else disagrees, but I think rbenv plugins are better off following the unix philosophy of doing one thing and doing it well.

I believe that any extra functionality that might be needed would be better off implemented either internally by the application or gem itself (like what rails does) or in a separate rbenv plugin.

Please let me know what you think, and thank you so much for taking the time to post and discuss this.

From you post it looks like rails tries very hard to do the same (right) thing regardless of how it's run, but that's not universal. In general the presence of a binstub telegraphs the intent for it to be run rather than the generic bundle exec, and there is no limit to how a binstub can be customized, so the ideal behavior is to run binstubs if present and then fall back to bundle exec. This would ideally be done by a single plugin because it represents a single responsibility (running scripts in the context of an app and its bundle, without any cumbersome prefixes) and two unrelated plugins are not likely to operate optimally in concert.

If you have an app that you need to run through a binstub, then rbenv-bundle-exec already lets you run ./bin/stub instead of bundle exec ./bin/stub, assuming the executable has the right shebang.

rbenv-bundle-exec just saves you from having to also type bundle exec. That is its single responsibility. As for making sure a gem or application loads its own context properly, that would be the responsibility of the particular gem in question, which is exactly why rails does extra work to make sure that that's the case.

avoiding bundle exec is not the end goal, it's avoiding any command prefixes. If avoiding bundle exec command is useful then avoiding ../../bin/commad (depending on where you are in the project tree) certainly is as well, both are annoying.

No, the gem has no such responsibilities, if a binstub has been created for a given gem in a given project, then it's the user's responsibility to run the binstub, which is why a plugin that runs binstubs and falls back to bundle exec would be useful.

Avoiding bundle exec is the end goal for this plugin, just as the name implies. I'm not arguing against your end goal of avoiding any prefixes, all I'm saying is that it is not part of this project's scope, and shouldn't be made part of it.

On a more productive note though, I think what you're describing is more of a feature request for rbenv-binstubs rather than rbenv-bundle-exec. There's nothing to be done to achieve the desired integration you're talking about from rbenv-bundle-exec's side. However, what you want is to have rbenv-binstubs not only replace invocations of gem with ./bin/gem but also to replace invocations of bundle exec gem with bundle exec ./bin/gem as well. If they implement this, then you'll have the integration you want and a setup with both plugins would work exactly as you described.

I see that you opened Purple-Devs/rbenv-binstubs#18 so I'm going to go ahead and close this.

I am throwing this back over the wall, as it is out of scope for rbenv-binstubs and I have not seen any real world examples where creating a binstub is a problem.

@ianheggie, I read your replies to Purple-Devs/rbenv-binstubs#18 and I completely agree with you.

@bughit, sorry for this, but without a real world use case, your best option to see this implemented is to create a separate plugin that combines both strategies, or leverages both plugins like I described earlier.