Here’s a short interlude: I was having the worst time with this error, partly because the error message is pretty misleading, and partly because I’m an idiot. One of these problems could be solved…the other has to be worked around 😉
So, the issue was that we were trying to run one Ruby script from another via the “shell-out” mechanism. There are a couple of ways to do this (Here is a good overview), but we’re using the good old
`Backticks` as we are not concerned about security for now (there’s no user input, everything is hardcoded). But when running the “inner” script, we get this error:
/home/mt/.rvm/gems/ruby-2.4.2/gems/bundler-1.17.1/lib/bundler/definition.rb:32:in `build': /home/mt/Development/crowdfunder_scraper/Gemfile not found (Bundler::GemfileNotFound)
when using the “inline” Gemfile syntax, or when using a normal Gemfile:
`require': cannot load such file -- httpx (LoadError)
one of our defined gems would be mysteriously missing. When
cd-ing into the “inner” directory and running the script, it works, of course.
This is of course the abridged version. I was spending a whole lot of time fiddling around, trying to pinpoint the problem. A misleading thought was that the issue might stem from rvm and bundler not properly loading in the sub-shell environment, so a lot of
cd-ing around within the backticks was tried:
cd ruby && ./scrape_crowdfunder
To make a tedious story short, the hint that was finally pointing to the solution was that discovering (after converting both “outer” and “inner” scripts to a normal Gemfile again) that the same issue also occurs when running the inner script from the outer directory (both containing Gemfiles):
crowdfunder_scraper $ ./ruby/scrape_crowdfunder ./ruby/scrape_crowdfunder:6:in `require': cannot load such file -- httpx (LoadError)
So what seems to happen is that the “inner” script keeps the gems loaded from the “outer” environment. The same thing happens when running via Backticks. Googling “ruby subshell inherits gems?” finally has the solution: Use Bundler.with_clean_env ! Go ahead and read that article, it explains the issue quite well. Basically, bundler sets up a couple of ENV variables when a Gemfile is encountered, and within the same shell and directory, doesn’t change it again. When putting the shell-out backticks within that method’s block, all is well.
So just some additional notes here: Since that article was written, the Bundler method was renamed to Bundler.with_original_env .
And also, I made a small git repository to demonstrate and test the issue for me and anyone else: https://github.com/MGPalmer/bundler_env_error_test
In it, we have the same setup as my problem: An “outer” and an “inner” directory, both having a Gemfile in it. The outer Gemfile actually requires no gems at all, the inner one wants
In both dirs we have a script, the outer one simply prints a message and then shells out to the inner script, once within
Bundler.with_original_env‘s block, and once without it. The former call works, the latter one reproduces the problem that the inner script can’t find the gems it wants:
bundler_env_error_test $ ./wrangler Let's get wranglin'. _______________________ | Moo moo I'm a cow yo. | ----------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || Traceback (most recent call last): 1: from ./cow:5:in `<main>' ./cow:5:in `require': cannot load such file -- cowsay (LoadError)
So there we go. Another one of these stumbling blocks when developing. I learned a little more about how Bundler works, but there was so much wasted time, a pity.
A somewhat amusing coda to this – after changing the test code from the last article to use
with_original_env , the issue was solved as described above. But then suddenly, the “inner” Ruby script didn’t pick up the provided
CROWDFUNDER_PROJECTS_URL env variable value anymore. For a short time I wasn’t sure I was using Ruby’s accessor to the ENV correctly, but I then realized that
with_original_env was doing exactly what it’s saying on the tin – it resets the ENV, wiping out what we added to it within the script 😀
I realized that it’s a much cleaner interface anyway to simply add the url as a command-line parameter, and switched the code to that. So, next time: Finally finishing the tests.