Yury Velikanau's Blog

Adventures with Ruby and Rails

Bundler in Subshells

Few weeks ago I was trying to make Integrity work with Ruby 1.9.2 and Bundler. It’s a well known CI tool, but kind of abandoned. I thought making it work with Ruby 1.9.2 could be tough, however the real problem was in Bundler.

Integrity use Bundler to manage its dependencies. When Integrity runs a build it opens new subshell where your project is building.

Here is how Integrity does it

1
2
3
4
5
6
7
8
9
10
def run(command)
cmd = normalize(command)
@logger.debug(cmd)
output = ""
IO.popen(cmd, "r") { |io| output = io.read }
Result.new($?.success?, output.chomp)
end

The issue arise when your project use Bundler too. Who doesn’t? In this case your project is trying to use Integrity’s Gemfile which is not that you wanna do. Integrity should use its own Gemfile as well as your project should use its own.

This happens because Bundler change your environment to do what it does. It sets BUNDLE_GEMFILE variable which points to Integrity’s Gemfile. Even when Integrity runs your project in subshell this variable is there, because subshell inherit its parent environment.

Looking for solution on the web you can find recommendations to use Bundler.with_clean_env method, however this was working solution for old Bundler versions I guess. With modern versions it doesn’t help, because this method doesn’t cleanup BUNDLE_GEMFILE variable. Moreover, Bundler sets and doesn’t cleanup two more variables - RUBYOPT and BUNDLE_BIN_PATH. So unless you have these variables in subshell you’ll keep using Integrity’s gems.

To avoid this I went almost the same way as with_clean_env does - replace current environment with the one you want and restore it when command is finished, in the same time removing those three variables.

1
2
3
4
5
6
7
8
9
BUNDLER_VARS = %w(BUNDLE_GEMFILE RUBYOPT BUNDLE_BIN_PATH)
def with_clean_env
bundled_env = ENV.to_hash
BUNDLER_VARS.each{ |var| ENV.delete(var) }
yield
ensure
ENV.replace(bundled_env.to_hash)
end

So now run method should look like this

1
2
3
4
5
6
7
8
9
10
11
12
def run(command)
cmd = normalize(command)
@logger.debug(cmd)
output = ""
with_clean_env do
IO.popen(cmd, "r") { |io| output = io.read }
end
Result.new($?.success?, output.chomp)
end

and your build will be using its own Gemfile.

This made Integrity work with Bundler. However if you faced something similar in your project using subshells you could do the same trick.

Good news that with Bundler 1.1 we’ll probably have new method to really clean environment from Bundler.

In regards to Integrity, there is still some issues related to Bundler and RVM, however it works in simple cases.

If you wanna help in reviving Integrity, please don’t hesitate to test it and report bugs, or help in development :)

My fork of Integrity lives here.

Comments