Edited at

bundler 1.11.0 の変更で spring が起動しない件について

More than 3 years have passed since last update.

rails c で以下の様な例外が発生して起動しない事があります。これは、spring が子プロセスを立ち上げるところで発生する例外です。

/Users/koshigoe/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- bundler/setup (LoadError)

from /Users/koshigoe/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/commands.rb:33:inv `<module:Spring>'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/commands.rb:4:in `<top (required)>'
from /Users/koshigoe/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
from /Users/koshigoe/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/application.rb:77:in `preload'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/application.rb:143:in `serve'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/application.rb:131:in `block in run'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/application.rb:125:in `loop'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/application.rb:125:in `run'
from /Users/koshigoe/tmp/spring-debug/vendor/bundle/gems/spring-1.6.1/lib/spring/application/boot.rb:18:in `<top (required)>'
from /Users/koshigoe/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
from /Users/koshigoe/.rbenv/versions/2.2.3/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
from -e:1:in `<main>'

bundler の変更によって、 Bundler.with_clean_env のスコープ(ブロック)では ENV['RUBYLIB'] から bundler のパス(path/to/bundler-x.y.z/lib) が取り除かれる様になりました。なお、bundler のパスが ENV['RUBYLIB'] に追加されているのは bundler の仕業です。

spring では、子プロセスが require 'bundler/setup' する場面があります。この子プロセスは Bundler.with_clean_env の中で Process.spawn されて生まれています。

ここで、bundler を gem install でインストールし、アプリケーションが依存する gem を bundle install --path vendor/bundle でインストールしている場合を考えます(BUNDLE_DISABLE_SHARED_GEMS: '1')。bundler 管理下の GEM_HOME (vendor/bundle) には bundler がいないため、vendor/bundle しか見ない場合は require 'bundle/setup'LoadError となってしまいます。

さて、vendor/bundle に bundler があれば例外は避けられるのでしょうか?

$ GEM_HOME=vendor/bundle gem install bundler --no-document

$ bundle exec rails c
Running via Spring preloader in process 45717
Loading development environment (Rails 4.2.5)
(001): >>

予想通り、 LoadError 例外は発生しなくなりました。

spring 側の変更で対応する場合はどうするのが良いのでしょうか。乱暴ですが ENV['RUBYLIB'] に path/to/bundler-x.y.z/lib を追加してしまうのが簡単でしょう。Bundler.with_clean_env で取り除いたものを戻すというあたりが気持ち悪いですね。

diff --git a/lib/spring/application_manager.rb b/lib/spring/application_manager.rb

index 8b34b9f..2699ba2 100644
--- a/lib/spring/application_manager.rb
+++ b/lib/spring/application_manager.rb
@@ -92,13 +92,18 @@ module Spring
def start_child(preload = false)
@child, child_socket = UNIXSocket.pair

+ bundler_lib_path = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR).grep(%r{/bundler-\d[^/]*/lib\z}).first
Bundler.with_clean_env do
+ rubylib = (ENV["RUBYLIB"] || "").split(File::PATH_SEPARATOR)
+ rubylib.unshift bundler_lib_path if bundler_lib_path
+
@pid = Process.spawn(
{
"RAILS_ENV" => app_env,
"RACK_ENV" => app_env,
"SPRING_ORIGINAL_ENV" => JSON.dump(Spring::ORIGINAL_ENV),
- "SPRING_PRELOAD" => preload ? "1" : "0"
+ "SPRING_PRELOAD" => preload ? "1" : "0",
+ "RUBYLIB" => rubylib.join(File::PATH_SEPARATOR)
},
"ruby",
"-I", File.expand_path("../..", __FILE__),

spring 側で require 'bundler/setup' しているという事は、spring は bundler に依存しているという事だと思ってしまうのですが、RUNTIME DEPENDENCIES に bundler がないので、自分は何かを見落としている様な気がしています。

テストが書ければ PR を作ってみようかとも思うのですが、spring 本体のテストコードでは、テストコード(テスト用アプリ)が依存する gem を test/apps/gems/2.2.3/gems/ 以下に bundler も含めて配置する仕組みになっています。bundler とアプリケーションの依存 gem を分けて配置する構成のテストを書ける気がしません。

以下、まとめ。


  • 敗北を認めよう

  • bundler 1.10.6 を使ってれば大丈夫

  • bundler 1.11.x でも vendor/bundle に入れちゃえば大丈夫(--path vendor/bundle)

  • 多分 bundler install --system という選択肢もある


2016/02/07 追記

コメントにて、v1.6.3 で修正された事を教えていただきました。

-I オプションでパスを指定したんですね。

取り急ぎ、v1.6.3 を使って rails c が起動できる事が確認できました。