bundle installで参照するGemfileの場所はどのように決まる?

結論

bundle install 実行時は、

  • まずコマンド実行時の階層のGemfile, gems.rbの存在を確認
  • 存在しなかったら上の階層を探しに行く、を探せるまで繰り返す

開発環境

> bundle -v
Bundler version 2.3.17

モチベーション

bundle install 実行時に、直下にGemfileが無いのにも係わらず実行されたことに驚いた。
試した感じ親階層のGemfileを参照しているように見えるが、実際の動きを確認する。
あとiOSアプリ開発Rubyはちょくちょく絡んでくるのでRubyソースコードを読めるようになりたい。

Bundlerの公式ドキュメント

This defaults to a Gemfile(5) in the current working directory. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find Gemfile.lock and vendor/cache relative to this location. https://bundler.io/v2.3/man/bundle-install.1.html

  • デフォルトで現在の作業ディレクトリのGemfileを参照
  • 一般的にBundlerはGemfileの場所がプロジェクトのルートにあると想定し、その場所にGemfile.lockとvendor/cacheを見つけようとする

コード

エントリーポイントから辿ると、rubygems/bundler/lib/bundler/shared_helpers.rb#search_up(*names) に辿り着いた。

# https://github.com/rubygems/rubygems/blob/eba5fc5fccc822f7cb859c7c51f657c3ffdec507/bundler/lib/bundler/shared_helpers.rb#L260
current = File.expand_path("..", current)

ここで、今見ている階層にGemfile, gems.rbが無ければ上の階層を見るようにしている。

杞憂?

コード見た感じ+手元で試した感じ、bundle install時にgit管理関係なく上の階層を探し続けてしまうのでパスを指定したほうが精神衛生的に良さそう。

> tree -f 
.
├── ./Gemfile
├── ./Gemfile.lock
└── ./sub_d  # Git管理しているルートのディレクトリ
    └── ./sub_d/hoge.txt

例えば上記のディレクトリ構成の時に、./sub_d ディレクトリがGit管理しているルートのディレクトリの場合、意図せずに./sub_dbundle installすると一個上の無関係のGemfileが参照されてしまう。本来ならインストールの処理は行わせたくない。…こんなディレクトリ構成ではあまり組まないか。杞憂っぽい。とはいえ一個上の階層見るのはやや予想に反して非明示的なので、自分はパス指定する形を取りたい。

> tree
.
├── Gemfile
├── Gemfile.lock
└── root_d
    └── hoge.txt

> cd root_d 

> bundle config set --local path './../Gemfile'

> bundle install
Fetching gem metadata from https://rubygems.org/.......

気になったコード

# https://github.com/rubygems/rubygems/blob/eba5fc5fccc822f7cb859c7c51f657c3ffdec507/bundler/lib/bundler/shared_helpers.rb#L221_L223
def gemfile_names
      ["gems.rb", "Gemfile"]
end

Rubyはガッツリ書いたことが無いので作法を知らないが、配列の宣言を定数/変数で行わずにメソッドを用意していることが気になった。引数無いし、固定の値を返すので定数で良いのでは?このメソッドが宣言されているモジュールという概念には定数を宣言できるみたい*1なので、定数を宣言しても良さそう。

参考