Jonny Zheng

I'm an idealist, not perfectionist

Rails 源码学习 1 (Rails 的初始化过程)

rails bin 文件

在rails 项目的目录里运行命令就会看到rails命令的路径/usr/bin/rails,其实是一个ruby文件,可以用编辑器打开看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env ruby
#
# This file was generated by RubyGems.
#
# The application 'railties' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
  p str
  if str =~ /\A_(.*)_\z/
    version = $1
    ARGV.shift
  end
end

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)

该文件做的事情主要是 require rubygems, 然后最后一行 load railties下面的rails,我们可以打印一下到底load了什么东西:

1
2
p Gem.bin_path('railties', 'rails', version)
# will out put /*/*/ruby-1.9.3-p194/gems/railties-3.2.7/bin/rails

然后就去看看 railties-3.2.7/bin/rails 里面都有什么吧,路径中的版本号只是我自己机器上使用的版本,可能和其他人的不一样,所以下面都会把路径中的版本号去掉。

railties/bin/rails

这里面也很简单,基本上只是设置环境,

1
2
3
4
5
6
7
#!/usr/bin/env ruby

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

最后require了 railties/lib/rails/cli.rb 这个文件:

railties/lib/rails/cli.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'rbconfig'
require 'rails/script_rails_loader'

# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
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

第一行的rbconfig是ruby standlib, 可以用来知道当前环境ruby的path,第二行引用的就是rails的装载脚本了,下面的 Rails::ScriptRailsLoader.exec_script_rails! 方法就是在里面定义的

railties/lib/rails/script_rails_loader.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module Rails
  module ScriptRailsLoader
    RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
    SCRIPT_RAILS = File.join('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?
	  ...	
    rescue SystemCallError
      # could not chdir, no problem just return
    end

    ...

  end
end

self.exec_script_rails 方法里就是真正的调用rails项目里 script/rails 这个文件啦, 这个文件在每个项目里都有的, 让我们再回到项目里看看这个文件

rails 项目中的 script/rails

1
2
3
4
5
6
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.

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

这里require了 项目中的config/boot.rb 文件,它的主要作用是通过Bundle 把Gemfile 里的Gems都引用进来。最后一步 require 'rails/commands' 是真正更具我们输入的命令做执行的地方,比如 rails s,rails c, rails g 都是在这里做的判断。

railties/lib/rails/commands.rb

这里看看 启动server的片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
when 'server'
  # Change to the application's path if there is no config.ru file in current dir.
  # This allows us to run script/rails server from other directories, but still get
  # the main config.ru and properly set the tmp directory.
  Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exists?(File.expand_path("config.ru"))

  require 'rails/commands/server'
  Rails::Server.new.tap { |server|
    # We need to require application after the server sets environment,
    # otherwise the --environment option given to the server won't propagate.
    require APP_PATH
    Dir.chdir(Rails.application.root)
    server.start
  }

这里最主要的就是调用了 rails/commnads/server 该文件里定义了 server.start 方法。

railties/lib/rails/commands/server.rb

这个文件里主要是在 initialize的时候设置sever 的一些参数,再就是start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 def start
      url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
      puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
      puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
      puts "=> Call with -d to detach" unless options[:daemonize]
      trap(:INT) { exit }
      puts "=> Ctrl-C to shutdown server" unless options[:daemonize]

      #Create required tmp directories if not found
      %w(cache pids sessions sockets).each do |dir_to_make|
        FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
      end

      super
    ensure
      # The '-h' option calls exit before @options is set.
      # If we call 'options' with it unset, we get double help banners.
      puts 'Exiting' unless @options && options[:daemonize]
  end

这时你就知道在 rails s 的时候显示的几行文字是从这里打印出来的了,最后他会调用 Rack::Server.start 方法,也就是父类方法.

actionpack/lib/action_dispatch.rb

server.rb 里还require了 action_dispatch,这个文件在 Gem action_pack 目录下, 文件里引入了大部分rails需要的模块,包括:

1
2
3
4
5
6
require 'active_support'
require 'active_support/dependencies/autoload'

require 'action_pack'
require 'active_model'
require 'rack'

接下来就是rack server了

rack/lib/server.rb

rack 提供了统一的最简单的web server和应用程序之间的接口,所有的应用程序和web server都可以基于这个开发,大大节省了工作量和兼容问题,比如 rails 可以在自带的webserver webrick 跑,也可以在unicorn, thin,passanger 等webserver上跑,因为大家都是基于rack做的实现。

Comments