railsを起動するとgemはどのように読み込まれてんのか
railsを起動するとgemはどのように読み込まれてんのか
rails new new_application
をした後、
bundle install
のようにrailsアプリを動かすのに必要なgemをインストールするわけだけど、 インストールした後、
rails server
で起動した後にどのように gemをインストールされるかを追ってみる。
1. bin/rails
railsコマンドが実行されると、config/applicationが呼ばれる。
#!/usr/bin/env ruby APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands'
2. config/application
一行目で、config/bootが呼ばれる。 そのあとにBundler.requireが呼ばれている。
require File.expand_path('../boot', __FILE__) require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) ・・・省略
3. config/boot
config/bootでは主にbundler/setupを呼んでいる。
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' # Set up gems listed in the Gemfile.
4. bundler-1.11.2/lib/bundler/setup.rb
Gemfileの確認を行い、OKならBundler.setupを実行。
if Bundler::SharedHelpers.in_bundle? require "bundler" if STDOUT.tty? || ENV["BUNDLER_FORCE_TTY"] begin Bundler.setup rescue Bundler::BundlerError => e puts "\e[31m#{e.message}\e[0m" puts e.backtrace.join("\n") if ENV["DEBUG"] if e.is_a?(Bundler::GemNotFound) puts "\e[33mRun `bundle install` to install missing gems.\e[0m" end exit e.status_code end else Bundler.setup end # Add bundler to the load path after disabling system gems bundler_lib = File.expand_path("../..", __FILE__) $LOAD_PATH.unshift(bundler_lib) unless $LOAD_PATH.include?(bundler_lib) Bundler.ui = nil end
5. bundler-1.11.2/lib/bundler.rb
load.setupを実行。
def setup(*groups) # Just return if all groups are already loaded return @setup if defined?(@setup) definition.validate_ruby! if groups.empty? # Load all groups, but only once @setup = load.setup else load.setup(*groups) end end
load = Runtimeインスタンスですね。
def load @load ||= Runtime.new(root, definition) end
6. bundler-1.11.2/lib/bundler/runtime.rb
specってのが、GemfileやGemfile.lockをパースしてオブジェクトにした
各gemの情報(依存関係も含む)になってる。
例えば、railsが依存しているsdocはjsonとrdocに依存しているけれど、
jsonやrdocまで$LOAD_PATHが通る感じ。
sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0)
def setup(*groups) groups.map!(&:to_sym) # Has to happen first clean_load_path specs = groups.any? ? @definition.specs_for(groups) : requested_specs setup_environment Bundler.rubygems.replace_entrypoints(specs) # Activate the specs specs.each do |spec| unless spec.loaded_from raise GemNotFound, "#{spec.full_name} is missing. Run `bundle` to get it." end if (activated_spec = Bundler.rubygems.loaded_specs(spec.name)) && activated_spec.version != spec.version e = Gem::LoadError.new "You have already activated #{activated_spec.name} #{activated_spec.version}, " \ "but your Gemfile requires #{spec.name} #{spec.version}. Prepending " \ "`bundle exec` to your command may solve this." e.name = spec.name if e.respond_to?(:requirement=) e.requirement = Gem::Requirement.new(spec.version.to_s) else e.version_requirement = Gem::Requirement.new(spec.version.to_s) end raise e end Bundler.rubygems.mark_loaded(spec) load_paths = spec.load_paths.reject {|path| $LOAD_PATH.include?(path) } $LOAD_PATH.unshift(*load_paths) end setup_manpath lock(:preserve_bundled_with => true) self end
一応、require 'bundler/setup'が、ちゃんと$LOAD_PATHに変化を与えているかを検証してみた。
irb(main):002:0> $LOAD_PATH => ["/Users/onody/.rbenv/rbenv.d/exec/gem-rehash", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/site_ruby/2.2.0", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/site_ruby/2.2.0/x86_64-darwin15", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/site_ruby", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/vendor_ruby/2.2.0", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/vendor_ruby/2.2.0/x86_64-darwin15", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/vendor_ruby", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/2.2.0", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/2.2.0/x86_64-darwin15"] irb(main):003:0> require 'bundler/setup' => true irb(main):004:0> $LOAD_PATH => ["/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/web-console-2.3.0/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/uglifier-2.7.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/turbolinks-2.5.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/spring-1.6.4/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/sdoc-0.4.1/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/sass-rails-5.0.4/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/tilt-2.0.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/sass-3.4.21/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rdoc-4.2.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rails-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/sprockets-rails-3.0.4/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/sprockets-3.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/mysql2-0.4.3", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/mysql2-0.4.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/jquery-rails-4.1.0/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/jbuilder-2.4.1/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/multi_json-1.11.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/concurrent-ruby-1.0.1/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/coffee-rails-4.1.1/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/railties-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/thor-0.19.1/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/coffee-script-2.4.1/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/execjs-2.6.0/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/coffee-script-source-1.10.0/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/byebug-8.2.2", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/byebug-8.2.2/lib", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/bundler-1.11.2/lib/gems/bundler-1.11.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/binding_of_caller-0.7.2", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/binding_of_caller-0.7.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/debug_inspector-0.0.2", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/debug_inspector-0.0.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/bundler/gems/acts_as_bits-26fc7abed0af/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/activerecord-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/arel-6.0.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/activemodel-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/actionmailer-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/mail-2.6.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/mime-types-2.99.1/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/activejob-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/globalid-0.3.6/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/actionpack-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rack-test-0.6.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rack-1.6.4/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/actionview-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rails-html-sanitizer-1.0.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/loofah-2.0.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rails-dom-testing-1.0.7/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rails-deprecated_sanitizer-1.0.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/nokogiri-1.6.7.2", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/nokogiri-1.6.7.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/mini_portile2-2.0.0/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/erubis-2.7.0/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/builder-3.2.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.5.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/tzinfo-1.2.2/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/thread_safe-0.3.5/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/minitest-5.8.4/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/extensions/x86_64-darwin-15/2.2.0-static/json-1.8.3", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/i18n-0.7.0/lib", "/Users/onody/Projects/test/rails_test/vendor/bundle/ruby/2.2.0/gems/rake-10.5.0/lib", "/Users/onody/.rbenv/rbenv.d/exec/gem-rehash", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/bundler-1.11.2/lib", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/site_ruby/2.2.0", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/site_ruby/2.2.0/x86_64-darwin15", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/site_ruby", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/vendor_ruby/2.2.0", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/vendor_ruby/2.2.0/x86_64-darwin15", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/vendor_ruby", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/2.2.0", "/Users/onody/.rbenv/versions/2.2.2/lib/ruby/2.2.0/x86_64-darwin15"]
ちゃんとなっている。
7. bundler-1.11.2/lib/bundler.rb
3〜6までのステップで、bundler/setupでは
Gemfile,Gemfile.lockを解析して$LOAD_PATHに追加することがわかった。
その後、2の処理に戻ってみると、
Bundler.requireをして、読み込んでいるのだけど、
それを追ってみよう。
def require(*groups) setup(*groups).require(*groups) end
ここでは、setup(前に出てきたBundler::Runtime)のrequireメソッドを呼んでいる。 ポイントはgroup。Gemfileで指定したgroupのみをrequireしているところ。
group :test do gem 'faker' gem 'rspec' end
8. bundler-1.11.2/lib/bundler/runtime.rb
以下でdependenciesを一つづつrequireしています。 ややこしいのが、 dependenciesがこのrailsが依存しているgem情報で、 specが各gemがさらに依存しているgemの情報です。 これらはGemfile.lockを見るとわかると思います。
def require(*groups) groups.map!(&:to_sym) groups = [:default] if groups.empty? @definition.dependencies.each do |dep| # Skip the dependency if it is not in any of the requested # groups next unless (dep.groups & groups).any? && dep.current_platform? required_file = nil begin # Loop through all the specified autorequires for the # dependency. If there are none, use the dependency's name # as the autorequire. Array(dep.autorequire || dep.name).each do |file| # Allow `require: true` as an alias for `require: <name>` file = dep.name if file == true required_file = file begin Kernel.require file rescue => e raise e if e.is_a?(LoadError) # we handle this a little later raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end end rescue LoadError => e REQUIRE_ERRORS.find {|r| r =~ e.message } raise if dep.autorequire || $1 != required_file if dep.autorequire.nil? && dep.name.include?("-") begin namespaced_file = dep.name.tr("-", "/") Kernel.require namespaced_file rescue LoadError => e REQUIRE_ERRORS.find {|r| r =~ e.message } raise if $1 != namespaced_file end end end end end
まとめ
- rails起動時は、bundler/setupと、Bundler.requireを実行している。
- setup => 対象のgemのパスを$LOAD_PATHに通す。ただし、Gemfileで呼んでいるgemの、さらに依存しているgem(spec)もパスを通す。
- require => Gemfileで呼んでいるgemを読み込む。その先は読まない。引数で指定したgroupのgemのみが読み込まれる。