bundler (1.16.1)のコードリーディングをしました。今回は bundle exec のコードを追ってみます。

exe/bundleはBundler::CLI.startを呼び出します

Bundler::CLIはThorを継承しています

execコマンドを実行すると#execが実行されます。#execはBundler::CLI::Exec#runを実行します。

Bundler::CLI::Exec#runはBundler::SharedHelpers.set_bundle_environmentで環境変数を操作してから、Bundler.whichでコマンドのパスを検索してkernel_execで実行します(rubyのシェバンが付いていてdisable_exec_loadがfalseの場合はkernel_loadを実行します)

Bundler.whichは引数のファイルが存在しているか、実行可能かどうかを現在のパスや環境変数PATHから判別して存在すれば引数のファイル名をそのまま返します。

kernel_execはその名の通りexec、kernel_loadはloadします。

Bundler::SharedHelpers.set_bundle_environmentは以下の環境変数の値を書き換えます

  • BUNDLE_BIN_PATH
  • BUNDLE_GEMFILE
  • BUNDLER_VERSION
  • PATH
  • RUBYOPT
  • RUBYLIB

Bundler::SharedHelpers.set_envメソッドでは元の値をプレフィックス(EnvironmentPreserver::BUNDLER_PREFIX)を付けて環境変数に保存してから、ENVを上書きしています。

PATHに #{Bundler.bundle_path}/bin が設定されることで、bundle installしたライブラリの実行ファイルを実行できます。また、RUBYOPTに -rbundler/setup が設定されるので、ruby実行時にbundler/setupがrequireされることになります。

bundler/setupはBundler.setupを呼び出します

Bundler.setupはBundler::Runtime#setupを呼び出します。#definitionを呼び出したタイミングでBundler.configureが呼ばれ、GEM_PATHとGEM_HOMEが設定されています。

Bundler::Runtime#setupでは$LOAD_PATHの操作を行います。

specsはGemfileから取得した依存ライブラリの情報です。ここからload_pathsを取得し、$LOAD_PATHに追加します。これでRubyスクリプト上でbundlerでインストールした依存ライブラリのrequireができるようになります。

おまけ1:Bundler.with_clean_env

Bundler.with_clean_envを使うとbundle execやbundler/setupで設定した環境変数をリセットしてスクリプトを実行できます。

original_envはENVのbundlerの設定前の値になります。設定前の判別はプレフィックス EnvironmentPreserver::BUNDLER_PREFIX がついている EnvironmentPreserver::BUNDLER_KEYS の環境変数から復元します。

Bundler::EnvironmentPreserver#restoreで元のENV値を取得しています

with_envは与えられたハッシュ値でENVをreplaceしブロックをyield後、backupからもとの環境変数に復元します。

おまけ2:bundleで設定される環境変数を確認