tl;dr: Ruby's Bundler environment is passed on to system calls, which may not be what you may want as it changes gem and binary lookup. Use Bundler.with_original_env
to restore the environment's state before Bundler was launched. Do this whenever you want to execute shell commands inside other bundles.
Example outline
Consider this setup:
my_project/Gemfile # says: gem 'rails', '~> 3.0.0'
my_project/foo/Gemfile # says: gem 'rails', '~> 3.2.0'
And, just to confirm this, these are the installed Rails versions for each of the bundles:
~/my_project$ bundle show rails
.../gems/rails-3.0.20
~/my_project$ cd foo && bundle show rails
.../gems/rails-3.2.13
Now you will usually just use bundle exec
to run stuff, which is good as it makes sure you are using the gems from the bundle. Here is how it would look like:
~/my_project$ bundle exec ruby -e "require %(rails) ; puts Rails.version"
3.0.20
~/my_project$ cd foo && bundle exec ruby -e "require %(rails) ; puts Rails.version"
3.2.13
So far, so good.
FYI, we're using %(...)
instead of '...'
here because we don't want to escape quotes for the example below.
Bundler + shell execution from Ruby = fun
Now let's see what happens when we do the above by executing the second command via Ruby:
~/my_project$ bundle exec ruby -e 'system %(
cd foo && bundle exec ruby -e "require %(rails) ; puts Rails.version"
)'
3.0.20
Well, that is not what we want. That should have been 3.2.13
since we're saying bundle exec
from the 3.2 bundle.
Why it happens and how to fix it
To change some gem and binary lookup, Bundler defines these environment variables:
BUNDLE_BIN_PATH
BUNDLE_GEMFILE
RUBYOPT
Basically, that environment is being passed on the child calls, so when you say bundle exec
and then inside make system
calls.
So Bundler's environment still prevails when running shell commands from Ruby. On the one hand, this is good as you'd expect your bundle to deliver all you need. However, it will come bite you when you try to run code that has its own bundle.
If you need to do exactly that, Bundler supplies a method that will give you the environment state from before Bundler added its variables, which is just what we need: Bundler.with_original_env
.
~/my_project$ bundle exec ruby -e 'Bundler.with_original_env { system %(
cd foo && bundle exec ruby -e "require %(rails) ; puts Rails.version"
) }'
3.2.13
If you want a completely Bundler-free environment, you can use Bundler.with_unbundled_env
on modern Bundler (2.1+). If you started with an environment that already held env variables related to Bundler, this will restore the original environment with such keys discarded as well. On older Bundlers, this method was called Bundler.with_clean_env
.