インストールされていないBundlerグループのspringをエラーなくスキップ
binstubがBundlerの requested_specs を参照するよう変更され、インストール対象外のグループに含まれるgemはspringの読み込みをスキップするようになりました。これにより、spring をdevelopment/testグループにのみ追加しているプロジェクトで、productionサーバーからコンソールを起動する際に発生していた Gem::LoadError が解消されます。
背景
#662 で導入されたbinstubは、RAILS_ENV 環境変数をもとにspringの起動を制御する設計でした。しかし RAILS_ENV による判定は、springがインストールされているかどうかの確認としては不十分でした。
具体的な問題は、spring をdevelopment/testグループにのみ配置し、productionサーバーではインストールしていない構成で起動する際に発生します。開発者がproductionサーバーにログインして ./bin/rails console -e production を実行した場合、RAILS_ENV 環境変数が設定されていなくても -e production のような引数を使う構成では RAILS_ENV チェックをすり抜け、インストールされていない spring の読み込みを試みて Gem::LoadError が発生していました。
つまり RAILS_ENV の値がspringの「実際にインストールされているか」を保証しないため、より信頼性の高い検出方法への変更が必要でした。
技術的な変更
lib/spring/client/binstub.rb の1行変更により、springの検索対象が Bundler.locked_gems.specs から Bundler.definition.requested_specs に切り替わりました。
変更前:
Bundler.locked_gems.specs.find { |spec| spec.name == "spring" }&.tap do |spring|
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
gem "spring", spring.version
require "spring/binstub"
変更後:
Bundler.definition.requested_specs.find { |spec| spec.name == "spring" }&.tap do |spring|
Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
gem "spring", spring.version
require "spring/binstub"
locked_gems.specs はロックファイル(Gemfile.lock)に記録された全gemを返します。一方 definition.requested_specs は、bundle config set without <group> などで除外されたグループを考慮した上で、現在の環境で実際にインストールが要求されているgemだけを返します。この差異により、除外グループに含まれる spring はリストに現れず、&.tap のsafe navigation operatorによって後続のrequireがスキップされます。
テストコードでは without_gem ヘルパーも拡張されています。従来は gems/ ディレクトリのgemディレクトリのみを退避していましたが、specifications/ ディレクトリの .gemspec ファイルも合わせて退避・復元するよう修正されました。また、bundle config set without debug でグループを除外した状態でbinstubが正常動作することを確認する新しいテストケースも追加されています。
設計判断
requested_specs を使う方式が採用されましたが、PR本文ではrescue節を追加する方式も候補として言及されています。rescue方式は Gem::LoadError の発生原因を問わず広く捕捉できる一方、原因が隠蔽されるリスクがあります。今回の requested_specs 方式は、Bundlerが持つグループ除外の情報を直接利用するため、「そもそもrequireを試みない」という意図がコードから明確に読み取れます。
また、requested_specs はBundlerの公開APIであり、bundle install --without や bundle config set without の設定を正しく反映するため、CI/CDパイプラインやコンテナ環境など、環境変数に依存せずBundlerの設定でgemグループを管理している構成に対しても適切に機能します。
まとめ
ロックファイル上の全specではなくBundlerが実際に要求するspecを参照するという1行の変更により、RAILS_ENV に依存しない信頼性の高いspring検出が実現されました。spring を特定グループにのみ配置する運用パターンでも、binstubがエラーなく動作する設計となっています。