yukata

日々出会ったIT技術関連の所感やら紹介やら

rails3.2.8の起動を追いかける

プログラムコードを、スーパーpre記法できれいに書けたものの、行数をつける方法がわからない・・・とりあえず一旦諦めます。。orz


さて、本題。

railsアプリケーションの起動中、どんな流れでプログラムが実行されているのかを追ってみました。Railsのバージョンは3.2.8です。

下記のように実行したあと、サーバの起動が完了するまでの流れをざっくりと追いかけて行きたいと思います。

$ rails new sample
$ cd sample
$ rails s


rubyrails等のインストールディレクトリは環境によって異なりますので、適宜読み替えて下さい。私の環境は、Mac OS X 10.7.4で以下のようになっています。

rubyコマンド /usr/bin/ruby
railsコマンド /usr/bin/rails
gemコマンド /usr/bin/gem
gemライブラリ /Library/Ruby/Gems/1.8/gems


まずは、最初に実行するrailsコマンドの中身

/usr/bin/rails

require 'rubygems'

version = ">= 0"

if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
  version = $1
  ARGV.shift
end

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)
1行目:

"rubygems"を読み込み。読み込まれるファイルは、「/Library/Ruby/Site/1.8/rubygems.rb」
この「rubygems.rb」の中で色々な定義やファイルが読み込まれますが、とりあえず読み飛ばします。

3行目:

使用するrailsのデフォルトバージョン。

5~8行目:

・引数の最初が"_xxx_"のような形式(文字列の両端がアンダースコア)かどうかを判断
・上記形式にマッチした場合、"$1"にマッチした文字列(アンダースコアの間の部分のみ)が格納される
・上記且つ、バージョンの記述形式にマッチしていた場合、if文の中身が実行される
 (バージョン記述形式はこのファイルを参照:「/Library/Ruby/Site/1.8/rubygems/version.rb 」)
・ifの中身は、"$1"に格納されたバージョン情報を、"version"へ代入し、引数のリスト"ARGV"をshiftしてるだけ。

10行目:

指定されたバージョンのrailtiesを読み込み。
railtiesは、Railsのブートプロセス、アプリケーションひな形作成等、Railsに必要な周辺ライブラリ関係が詰め込まれている。
railtiesも話が広がりすぎるので、一旦読み飛ばします。

11行目:

指定したバージョンのrailsコマンドファイルが読み込まれます。
また、このrailsコマンドファイルの読み込みに、引数"s"を引き継ぎます。
ちなみに「Gem.bin_path('railties', 'rails', version)」の実行結果は、以下のようになります。

$ irb
>> Gem.bin_path('railties', 'rails', "3.2.8")
=> "/Library/Ruby/Gems/1.8/gems/railties-3.2.8/bin/rails"


上記ソースコードの末尾でloadしたrailsコマンドファイルの中身

/Library/Ruby/Gems/1.8/gems/railties-3.2.8/bin/rails

if File.exists?(File.join(File.expand_path('../../..', __FILE__), '.git'))
  railties_path = File.expand_path('../../lib', __FILE__)
  $:.unshift(railties_path)
end
require "rails/cli"
1~4行目:

railtiesがGit管理であれば、railtiesのlibをロードパスに追加(すみません、なぜかよくわかりません・・・)
"$:"は、rubyの組み込み変数で、ロードパスが格納されています。("$LOAD_PATH"と同じ)
これは"load"や"require"がファイルをロードする時に検索するディレクトリのリストです。

5行目:

「/Library/Ruby/Gems/1.8/gems/railties-3.2.8/lib/rails/cli.rb」を読み込み。


cli.rbの中身

require 'rbconfig'
require 'rails/script_rails_loader'

Rails::ScriptRailsLoader.exec_script_rails!

require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }

if ARGV.first == 'plugin'
  ARGV.shift
  require 'rails/commands/plugin_new'
else
  require 'rails/commands/application'
end
1〜2行目:

必要なライブラリの読み込み。"rbconfig"は、「Rubyインタプリタ作成時に設定された情報を格納したライブラリ」らしい・・・

4行目:

script/railsスクリプトを、外部コマンドとして実行します。
あとで"exec_script_rails!"メソッドでなにをしているのか確認してみます。

7行目:

「Ctrl+C」でアプリケーションが終了するようにシグナルを登録しています。

9〜14行目:

引数は"plugin"ではなく"s"なので、"rails/commands/application"ライブラリを読み込みます。

以下、"exec_script_rails!"メソッドの中身です。

def self.exec_script_rails!
  cwd = Dir.pwd
  return unless in_rails_application? || in_rails_application_subdirectory?
  exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
  Dir.chdir("..") do
    exec_script_rails! unless cwd == Dir.pwd
  end
rescue SystemCallError
    # could not chdir, no problem just return
end

3行目、及び4行目のif、5~7行目の再帰文では、カレントディレクトリがrailsアプリケーションディレクトリ内であるかどうかを確認しているだけです。
カレントディレクトリがrailsアプリケーション内であった場合、4行目で外部コマンド「ruby script/rails s」が実行されます。


sample/script/railsの中身

次に実行されるのは、sampleアプリケーション内にある、「script/rails」ファイルです。

APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rails/commands'
1行目:

定数APP_PATHに、「config/application.rb」のパスを格納しています。

2行目:

「config/boot.rb」を読み込んでいます。あとで中身を確認します。

3行目:

"rails/commands"の読み込み。詳細は飛ばしたいとことですが、ここでスクリプトの流れが止まってしまっています。"rails/commands"の中で色々やっているのでしょう。

以下、「config/boot.rb」の中身。

require 'rubygems'

# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])

bundlerでgemファイルを管理している場合、ここでセットアップしてくれるようです。



ちょっと長くなりすぎてしまうので、次回「rails/commands」以降の流れを追っていきたいと思います。