RubyGems/パッケージインストール処理を読む
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
#contents
*はじめに [#g24116ef]
Ruby on Railsをインストールする場合、以下のコマンドを入力...
gem install rails --include-dependencies
この一行によって何が起こるかを見ていきましょう。ちなみに...
Bulk updating Gem source index for: http://gems.rubyforg...
Successfully installed rails-1.2.3
Successfully installed rake-0.7.3
Successfully installed activesupport-1.4.2
Successfully installed activerecord-1.15.3
Successfully installed actionpack-1.13.3
Successfully installed actionmailer-1.3.3
Successfully installed actionwebservice-1.2.3
今回対象としたバージョンは0.9.4です。
*Gem::Version::Requirement(rubygems/version.rb) [#c992ddaa]
gemコマンドを見ると初めにRubyのバージョンチェックが行われ...
required_version = Gem::Version::Requirement.new(">= 1.8...
unless required_version.satisfied_by?(Gem::Version.new(...
puts "Expected Ruby Version #{required_version}, was #...
exit(1)
end
**オブジェクトの構築 [#lcd9e0ea]
Gem::Version::Requirementを見てみましょう。早速おもしろい...
class Requirement
OPS = {
"=" => lambda { |v, r| v == r },
"!=" => lambda { |v, r| v != r },
">" => lambda { |v, r| v > r },
"<" => lambda { |v, r| v < r },
">=" => lambda { |v, r| v >= r },
"<=" => lambda { |v, r| v <= r },
"~>" => lambda { |v, r| v >= r && v < r.bump }
}
OP_RE = Regexp.new(OPS.keys.collect{|k| Regexp.quote(k...
これらの定数はメソッドの外に書かれているため、Rubyインタ...
次に、Requirement#initializeメソッドとinitializeメソッド...
def initialize(reqs)
@requirements = reqs.collect do |rq|
op, version_string = parse(rq)
[op, Version.new(version_string)]
end
@version = nil # Avoid warnings.
end
def parse(str)
if md = /^\s*(#{OP_RE})\s*([0-9.]+)\s*$/.match(str)
[md[1], md[2]]
elsif md = /^\s*([0-9.]+)\s*$/.match(str)
["=", md[1]]
elsif md = /^\s*(#{OP_RE})\s*$/.match(str)
[md[1], "0"]
else
fail ArgumentError, "Illformed requirement [#{str}]"
end
end
渡された引数から演算子とバージョンを取り出しています。数...
**バージョンチェック処理 [#p6eda074]
次にバージョンチェック部分に移ります。以下のようになって...
def satisfied_by?(version)
normalize
@requirements.all? { |op, rv| satisfy?(op, version, rv...
end
all?メソッドって知らないなと思ったら1.8で追加されたらしい...
それはともかくsatisfy?メソッドです。
def satisfy?(op, version, required_version
OPS[op].call(version, required_version)
end
初めに見たOPSハッシュを参照してProcオブジェクトを呼び出し...
ここまで呼んできてわかったこととして、RubyGemsではpublic...
*Gem::GemRunner(rubygems/gem_runner.rb) [#g5fa53ac]
gemコマンドに戻って、最後の一行でGem::GemRunnerオブジェク...
他のメソッドで出てくるので、各インスタンス変数に何が入っ...
def initialize(options={})
@command_manager_class = options[:command_manager] || ...
@config_file_class = options[:config_file] || Gem::Con...
@doc_manager_class = options[:doc_manager] || Gem::Doc...
end
次にrunメソッドです。
def run(args)
do_configuration(args)
cmd = @command_manager_class.instance
cmd.command_names.each do |c|
Command.add_specific_extra_args c, Array(Gem.configu...
end
cmd.run(Gem.configuration.args)
end
do_configurationは[[YAML形式>http://jp.rubyist.net/magazi...
*Gem::CommandManager(rubygems/command_manager.rb) [#t6112...
**オブジェクトの構築 [#qd5d601a]
次にGem::CommandManagerをインスタンス化してます。単純にne...
def self.instance
@command_manager ||= CommandManager.new
end
何故これがシングルトンパターンを実装することになるかは||=...
:一回目|@command_managerがnullなのでCommandManager.newが...
:二回目|@command_managerはnullではないのでCommandManager....
さて、CommandManager#initializeメソッドに移るとRubyGemsの...
def register_command(command_obj)
@commands[command_obj] = load_and_instantiate(command_...
end
def load_and_instantiate(command_name)
command_name = command_name.to_s
begin
Gem::Commands.const_get("#{command_name.capitalize}C...
rescue
require "rubygems/commands/#{command_name}_command"
retry
end
end
load_and_instantiateメソッドでは各コマンドを実装するオブ...
+例えば、installコマンドを処理するGem::Commands::InstallC...
+定義されていないので下のrescueブロックが実行される
+rubygems/commands/install_command.rbが読み込まれる
+Gem::Commands::InstallCommandが定義される
+もう一度Gem::Commands::InstallCommandを作ろうとすると今...
拡張可能なように設計するという場合のイディオムな感じです...
そういえば何でクラスを取得するのにGem::Commandsの定数を取...
**コマンドの検索 [#rc52d39d]
GemRunner#runメソッドに戻ると、次の行は設定ファイルに書か...
次にGem::CommandManager#runメソッドに移ります。runメソッ...
cmd_name = args.shift.downcase
cmd = find_command(cmd_name)
cmd.invoke(*args)
find_commandメソッドの中身がおもしろいので見てみましょう。
def find_command(cmd_name)
possibilities = find_command_possibilities(cmd_name)
if possibilities.size > 1
raise "Ambiguous command #{cmd_name} matches [#{poss...
end
if possibilities.size < 1
raise "Unknown command #{cmd_name}"
end
self[possibilities.first]
end
def find_command_possibilities(cmd_name)
len = cmd_name.length
self.command_names.select { |n| cmd_name == n[0,len] }
end
入力された文字列からどのコマンドっぽいかを検索しています...
gem l
と入力した場合、listだけがArray#selectメソッドのブロック...
gem s
だとsource, search, specificationの3つが該当します。この...
*Gem::Command(rubygems/command.rb) [#dac53b6f]
というわけで実行するコマンドが判別できたら各コマンド実行...
**オブジェクトの構築 [#ib69c3bb]
invokeメソッドの前にinitializeメソッド周りを見てみましょ...
class InstallCommand < Command
include CommandAids
include VersionOption
include LocalRemoteOptions
include InstallUpdateOptions
def initialize
super(
'install',
'Install a gem into the local repository',
{
:domain => :both,
:generate_rdoc => true,
:generate_ri => true,
:force => false,
:test => false,
:wrappers => true,
:version => "> 0",
:install_dir => Gem.dir,
:security_policy => nil,
})
add_version_option('install')
add_local_remote_options
add_install_update_options
end
親クラスのコンストラクタを呼んでいます。第1引数はコマンド...
def initialize(command, summary=nil, defaults={})
@command = command
@summary = summary
@program_name = "gem #{command}"
@defaults = defaults
@options = defaults.dup
@option_list = []
@parser = nil
@when_invoked = nil
end
となっているので第3引数はオプションで変更可能な値のデフォ...
次にadd_version_optionメソッドですがこれは上でincludeして...
def add_version_option(taskname, *wrap)
add_option('-v', '--version VERSION',
"Specify version of gem to #{taskname}", *w...
|value, options|
options[:version] = value
end
end
add_local_remote_options, add_install_update_optionsにつ...
def add_option(*args, &handler)
@option_list << [args, handler]
end
**コマンドの実行 [#bc1f9749]
それでは次にCommandManagerから呼ばれるinvokeメソッドを見...
def invoke(*args)
handle_options(args)
if options[:help]
show_help
elsif @when_invoked
@when_invoked.call(options)
else
execute
end
end
handle_optionsメソッドは次のようになっています。
def handle_options(args)
args = add_extra_args(args)
@options = @defaults.clone
parser.parse!(args)
@options[:args] = args
end
extra_argsは設定ファイルに書かれていた場合に追加の引数を...
def parser
create_option_parser if @parser.nil?
@parser
end
パーサが作られていなかったら作成、作られていたら単純に返...
*Gem::Commands::InstallCommand(rubygems/commands/install_...
invokeメソッドに戻るとexecuteメソッドが呼ばれます。Comman...
executeメソッドの前半はローカルにgemファイルがある場合の...
installer = Gem::RemoteInstaller.new(options)
installed_gems = installer.install(
gem_name,
options[:version],
options[:force],
options[:install_dir])
if installed_gems
installed_gems.compact!
installed_gems.each do |spec|
say "Successfully installed #{spec.full_name}"
end
end
*Gem::RemoteInstaller(rubygems/remote_installer.rb) [#y2f...
ではGem::RemoteInstaller#installメソッドです。まず初めにR...
unless version_requirement.respond_to?(:satisfied_by?)
version_requirement = Version::Requirement.new [versio...
end
version_requirementはStringオブジェクトを渡すことも可能な...
さて本題の部分です。
spec, source = find_gem_to_install(gem_name, version_req...
dependencies = find_dependencies_not_installed(spec.depe...
installed_gems << install_dependencies(dependencies, for...
cache_dir = @options[:cache_dir] || File.join(install_di...
destination_file = File.join(cache_dir, spec.full_name +...
download_gem(destination_file, source, spec)
installer = new_installer(destination_file)
installed_gems.unshift installer.install(force, install_...
依存関係を見て依存パッケージをインストールしてから指定さ...
**パッケージ情報の取得 [#a114a648]
まずfind_gem_to_installメソッドを見てみましょう。
def find_gem_to_install(gem_name, version_requirement)
specs_n_sources = specs_n_sources_matching gem_name, v...
top_3_versions = specs_n_sources.map{|gs| gs.first.ver...
specs_n_sources.reject!{|gs| !top_3_versions.include?(...
binary_gems = specs_n_sources.reject { |item|
item[0].platform.nil? || item[0].platform==Platform:...
}
# only non-binary gems...return latest
return specs_n_sources.first if binary_gems.empty?
find_gem_to_installメソッドの残りではバイナリgemの処理が...
def specs_n_sources_matching(gem_name, version_requireme...
specs_n_sources = []
source_index_hash.each do |source_uri, source_index|
specs = source_index.search(/^#{Regexp.escape gem_na...
version_requirement)
# TODO move to SourceIndex#search?
ruby_version = Gem::Version.new RUBY_VERSION
specs = specs.select do |spec|
spec.required_ruby_version.nil? or
spec.required_ruby_version.satisfied_by? ruby_ve...
end
specs.each { |spec| specs_n_sources << [spec, source...
end
specs_n_sources = specs_n_sources.sort_by { |gs,| gs.v...
specs_n_sources
end
def source_index_hash
return @source_index_hash if @source_index_hash
@source_index_hash = {}
Gem::SourceInfoCache.cache_data.each do |source_uri, s...
@source_index_hash[source_uri] = sic_entry.source_in...
end
@source_index_hash
end
どうやらリモートのパッケージ一覧をキャッシュしてそこから...
*Gem::SourceInfoCache(rubygems/source_info_cache.rb) [#l3...
Gem::SourceInfoCache.cache_dataから始まる呼び出しは以下の...
def self.cache_data
cache.cache_data
end
def self.cache
return @cache if @cache
@cache = new
@cache.refresh
@cache
end
def refresh
Gem.sources.each do |source_uri|
cache_entry = cache_data[source_uri]
if cache_entry.nil? then
cache_entry = Gem::SourceInfoCacheEntry.new nil, 0
cache_data[source_uri] = cache_entry
end
cache_entry.refresh source_uri
end
update
flush
end
今回は初めてgem installしたのでSourceInfoCache#cache_data...
Gem.sourcesってどこに定義されているんだ?と探したところ、...
*Gem::SourceIndex(rubygems/source_index.rb) [#nb885523]
Gem::SourceInfoCacheEntry#refreshメソッドです。content-le...
def refresh(source_uri)
remote_size = Gem::RemoteFetcher.fetcher.fetch_size so...
return if @size == remote_size # HACK bad check, local...
@source_index.update source_uri
@size = remote_size
end
Gem::SourceIndexクラスに移りましょう。
def update(source_uri)
use_incremental = false
begin
gem_names = fetch_quick_index source_uri
remove_extra gem_names
missing_gems = find_missing gem_names
use_incremental = missing_gems.size <= INCREMENTAL_T...
rescue Gem::OperationNotSupportedError => ex
use_incremental = false
end
if use_incremental then
update_with_missing source_uri, missing_gems
else
new_index = fetch_bulk_index source_uri
@gems.replace new_index.gems
end
self
end
以前パッケージ情報を取得したときから増えているパッケージ...
def fetch_bulk_index(source_uri)
say "Bulk updating Gem source index for: #{source_uri}"
begin
yaml_spec = fetcher.fetch_path source_uri + '/yaml.Z'
yaml_spec = unzip yaml_spec
rescue
begin
yaml_spec = fetcher.fetch_path source_uri + '/yaml'
end
end
convert_specs yaml_spec
end
どうやらパッケージ情報はYAMLで書かれているようです。長い...
え〜っと・・・、ここまででパッケージ情報が取得できたので...
def search(gem_pattern, version_requirement=Version::Req...
gem_pattern = /#{ gem_pattern }/i if String === gem_pa...
version_requirement = Gem::Version::Requirement.create...
result = []
@gems.each do |full_spec_name, spec|
next unless spec.name =~ gem_pattern
result << spec if version_requirement.satisfied_by?(...
end
result = result.sort
result
end
*再びGem::RemoteInstaller [#rc03388a]
**インストールされていない依存パッケージの検索 [#n167fb09]
Gem::RemoteInstaller#installメソッドに戻ります。次はfind_...
def find_dependencies_not_installed(dependencies)
to_install = []
dependencies.each do |dependency|
srcindex = Gem::SourceIndex.from_installed_gems
matches = srcindex.find_name(dependency.name, depend...
to_install.push dependency if matches.empty?
end
to_install
end
そんな感じかなというところです。もちろん、必要がないのに...
Gem::SourceIndex#from_installed_gemsメソッドおよび呼ばれ...
def from_installed_gems(*deprecated)
if deprecated.empty?
from_gems_in(*installed_spec_directories)
else
from_gems_in(*deprecated)
end
end
def from_gems_in(*spec_dirs)
self.new.load_gems_in(*spec_dirs)
end
def load_gems_in(*spec_dirs)
@gems.clear
specs = Dir.glob File.join("{#{spec_dirs.join(',')}}",...
specs.each do |file_name|
gemspec = self.class.load_specification(file_name.un...
add_spec(gemspec) if gemspec
end
self
end
def load_specification(file_name)
spec_code = File.read(file_name).untaint
gemspec = eval(spec_code)
gemspec.loaded_from = file_name
return gemspec
end
gemspecファイルを見るとわかりますがgemspecファイルはRuby...
**依存パッケージのインストール [#d129ec81]
Gem::RemoteInstaller#installメソッドに戻って、次のinstall...
def install_dependencies(dependencies, force, install_dir)
return if @options[:ignore_dependencies]
installed_gems = []
dependencies.each do |dep|
if @options[:include_dependencies] ||
ask_yes_no("Install required dependency #{dep.nam...
remote_installer = RemoteInstaller.new @options
installed_gems << remote_installer.install(dep.name,
dep.ver...
force, ...
elsif force then
# ignore
else
raise DependencyError, "Required dependency #{dep....
end
end
installed_gems
end
つまりこういうことです。
+aパッケージをインストール。b, cに依存(RemoteInstaller.i...
++bパッケージインストール。d, eに依存(RemoteInstaller.in...
+++dパッケージをインストール(RemoteInstaller.install('d'...
+++eパッケージをインストール(RemoteInstaller.install('e'...
++cパッケージをインストール(RemoteInstaller.install('c'))
*Gem::Installer(rubygems/installer.rb) [#z88a792c]
次にリモートからファイルをダウンロードしてくると、後はロ...
def install(force=false, install_dir=Gem.dir, ignore_thi...
format = Gem::Format.from_file_by_path @gem, security_...
# Build spec dir.
@directory = File.join(install_dir, "gems", format.spe...
FileUtils.mkdir_p @directory
extract_files(@directory, format)
generate_bin(format.spec, install_dir)
build_extensions(@directory, format.spec)
# Build spec/cache/doc dir.
build_support_directories(install_dir)
# Write the spec and cache files.
write_spec(format.spec, File.join(install_dir, "specif...
unless File.exist? File.join(install_dir, "cache", @ge...
FileUtils.cp @gem, File.join(install_dir, "cache")
end
puts format.spec.post_install_message unless format.sp...
format.spec.loaded_from = File.join(install_dir, 'spec...
return format.spec
end
*Gem::Package(rubygems/package.rb) [#w849937a]
まず一行目のGem::Format.from_file_by_pathメソッドから。例...
def self.from_io(io, gem_path="(io)", security_policy = ...
format = self.new(gem_path)
Package.open_from_io(io, 'r', security_policy) do |pkg|
format.spec = pkg.metadata
format.file_entries = []
pkg.each do |entry|
format.file_entries << [{"size", entry.size, "mode...
"path", entry.full_name}, entry.read]
end
end
format
end
Gem::Packageに処理が移っています。package.rbを開いて眺め...
Gem::Package.open_from_ioメソッドは第2引数modeが"r"の場合...
TarInput#initializeメソッドでは与えられたストリームからTa...
次にPackage#eachメソッドを眺めてみます。
def each(&block)
@tarreader.each do |entry|
next unless entry.full_name == "data.tar.gz"
is = zipped_stream(entry)
begin
TarReader.new(is) do |inner|
inner.each(&block)
end
ensure
is.close if is
end
end
@tarreader.rewind
end
というわけでPackage#eachメソッドのブロックに渡されてくる...
*追補Gem::Installer [#q9bc9ae7]
Installer#installメソッドに戻って、extract_filesメソッド...
以上でRubyGemsのパッケージインストール処理は終了となりま...
*おわりに [#k91b8adb]
今回はRubyGemsのインストール処理を読みました。これだ!と...
それではみなさんもよいコードリーディングを。
終了行:
#contents
*はじめに [#g24116ef]
Ruby on Railsをインストールする場合、以下のコマンドを入力...
gem install rails --include-dependencies
この一行によって何が起こるかを見ていきましょう。ちなみに...
Bulk updating Gem source index for: http://gems.rubyforg...
Successfully installed rails-1.2.3
Successfully installed rake-0.7.3
Successfully installed activesupport-1.4.2
Successfully installed activerecord-1.15.3
Successfully installed actionpack-1.13.3
Successfully installed actionmailer-1.3.3
Successfully installed actionwebservice-1.2.3
今回対象としたバージョンは0.9.4です。
*Gem::Version::Requirement(rubygems/version.rb) [#c992ddaa]
gemコマンドを見ると初めにRubyのバージョンチェックが行われ...
required_version = Gem::Version::Requirement.new(">= 1.8...
unless required_version.satisfied_by?(Gem::Version.new(...
puts "Expected Ruby Version #{required_version}, was #...
exit(1)
end
**オブジェクトの構築 [#lcd9e0ea]
Gem::Version::Requirementを見てみましょう。早速おもしろい...
class Requirement
OPS = {
"=" => lambda { |v, r| v == r },
"!=" => lambda { |v, r| v != r },
">" => lambda { |v, r| v > r },
"<" => lambda { |v, r| v < r },
">=" => lambda { |v, r| v >= r },
"<=" => lambda { |v, r| v <= r },
"~>" => lambda { |v, r| v >= r && v < r.bump }
}
OP_RE = Regexp.new(OPS.keys.collect{|k| Regexp.quote(k...
これらの定数はメソッドの外に書かれているため、Rubyインタ...
次に、Requirement#initializeメソッドとinitializeメソッド...
def initialize(reqs)
@requirements = reqs.collect do |rq|
op, version_string = parse(rq)
[op, Version.new(version_string)]
end
@version = nil # Avoid warnings.
end
def parse(str)
if md = /^\s*(#{OP_RE})\s*([0-9.]+)\s*$/.match(str)
[md[1], md[2]]
elsif md = /^\s*([0-9.]+)\s*$/.match(str)
["=", md[1]]
elsif md = /^\s*(#{OP_RE})\s*$/.match(str)
[md[1], "0"]
else
fail ArgumentError, "Illformed requirement [#{str}]"
end
end
渡された引数から演算子とバージョンを取り出しています。数...
**バージョンチェック処理 [#p6eda074]
次にバージョンチェック部分に移ります。以下のようになって...
def satisfied_by?(version)
normalize
@requirements.all? { |op, rv| satisfy?(op, version, rv...
end
all?メソッドって知らないなと思ったら1.8で追加されたらしい...
それはともかくsatisfy?メソッドです。
def satisfy?(op, version, required_version
OPS[op].call(version, required_version)
end
初めに見たOPSハッシュを参照してProcオブジェクトを呼び出し...
ここまで呼んできてわかったこととして、RubyGemsではpublic...
*Gem::GemRunner(rubygems/gem_runner.rb) [#g5fa53ac]
gemコマンドに戻って、最後の一行でGem::GemRunnerオブジェク...
他のメソッドで出てくるので、各インスタンス変数に何が入っ...
def initialize(options={})
@command_manager_class = options[:command_manager] || ...
@config_file_class = options[:config_file] || Gem::Con...
@doc_manager_class = options[:doc_manager] || Gem::Doc...
end
次にrunメソッドです。
def run(args)
do_configuration(args)
cmd = @command_manager_class.instance
cmd.command_names.each do |c|
Command.add_specific_extra_args c, Array(Gem.configu...
end
cmd.run(Gem.configuration.args)
end
do_configurationは[[YAML形式>http://jp.rubyist.net/magazi...
*Gem::CommandManager(rubygems/command_manager.rb) [#t6112...
**オブジェクトの構築 [#qd5d601a]
次にGem::CommandManagerをインスタンス化してます。単純にne...
def self.instance
@command_manager ||= CommandManager.new
end
何故これがシングルトンパターンを実装することになるかは||=...
:一回目|@command_managerがnullなのでCommandManager.newが...
:二回目|@command_managerはnullではないのでCommandManager....
さて、CommandManager#initializeメソッドに移るとRubyGemsの...
def register_command(command_obj)
@commands[command_obj] = load_and_instantiate(command_...
end
def load_and_instantiate(command_name)
command_name = command_name.to_s
begin
Gem::Commands.const_get("#{command_name.capitalize}C...
rescue
require "rubygems/commands/#{command_name}_command"
retry
end
end
load_and_instantiateメソッドでは各コマンドを実装するオブ...
+例えば、installコマンドを処理するGem::Commands::InstallC...
+定義されていないので下のrescueブロックが実行される
+rubygems/commands/install_command.rbが読み込まれる
+Gem::Commands::InstallCommandが定義される
+もう一度Gem::Commands::InstallCommandを作ろうとすると今...
拡張可能なように設計するという場合のイディオムな感じです...
そういえば何でクラスを取得するのにGem::Commandsの定数を取...
**コマンドの検索 [#rc52d39d]
GemRunner#runメソッドに戻ると、次の行は設定ファイルに書か...
次にGem::CommandManager#runメソッドに移ります。runメソッ...
cmd_name = args.shift.downcase
cmd = find_command(cmd_name)
cmd.invoke(*args)
find_commandメソッドの中身がおもしろいので見てみましょう。
def find_command(cmd_name)
possibilities = find_command_possibilities(cmd_name)
if possibilities.size > 1
raise "Ambiguous command #{cmd_name} matches [#{poss...
end
if possibilities.size < 1
raise "Unknown command #{cmd_name}"
end
self[possibilities.first]
end
def find_command_possibilities(cmd_name)
len = cmd_name.length
self.command_names.select { |n| cmd_name == n[0,len] }
end
入力された文字列からどのコマンドっぽいかを検索しています...
gem l
と入力した場合、listだけがArray#selectメソッドのブロック...
gem s
だとsource, search, specificationの3つが該当します。この...
*Gem::Command(rubygems/command.rb) [#dac53b6f]
というわけで実行するコマンドが判別できたら各コマンド実行...
**オブジェクトの構築 [#ib69c3bb]
invokeメソッドの前にinitializeメソッド周りを見てみましょ...
class InstallCommand < Command
include CommandAids
include VersionOption
include LocalRemoteOptions
include InstallUpdateOptions
def initialize
super(
'install',
'Install a gem into the local repository',
{
:domain => :both,
:generate_rdoc => true,
:generate_ri => true,
:force => false,
:test => false,
:wrappers => true,
:version => "> 0",
:install_dir => Gem.dir,
:security_policy => nil,
})
add_version_option('install')
add_local_remote_options
add_install_update_options
end
親クラスのコンストラクタを呼んでいます。第1引数はコマンド...
def initialize(command, summary=nil, defaults={})
@command = command
@summary = summary
@program_name = "gem #{command}"
@defaults = defaults
@options = defaults.dup
@option_list = []
@parser = nil
@when_invoked = nil
end
となっているので第3引数はオプションで変更可能な値のデフォ...
次にadd_version_optionメソッドですがこれは上でincludeして...
def add_version_option(taskname, *wrap)
add_option('-v', '--version VERSION',
"Specify version of gem to #{taskname}", *w...
|value, options|
options[:version] = value
end
end
add_local_remote_options, add_install_update_optionsにつ...
def add_option(*args, &handler)
@option_list << [args, handler]
end
**コマンドの実行 [#bc1f9749]
それでは次にCommandManagerから呼ばれるinvokeメソッドを見...
def invoke(*args)
handle_options(args)
if options[:help]
show_help
elsif @when_invoked
@when_invoked.call(options)
else
execute
end
end
handle_optionsメソッドは次のようになっています。
def handle_options(args)
args = add_extra_args(args)
@options = @defaults.clone
parser.parse!(args)
@options[:args] = args
end
extra_argsは設定ファイルに書かれていた場合に追加の引数を...
def parser
create_option_parser if @parser.nil?
@parser
end
パーサが作られていなかったら作成、作られていたら単純に返...
*Gem::Commands::InstallCommand(rubygems/commands/install_...
invokeメソッドに戻るとexecuteメソッドが呼ばれます。Comman...
executeメソッドの前半はローカルにgemファイルがある場合の...
installer = Gem::RemoteInstaller.new(options)
installed_gems = installer.install(
gem_name,
options[:version],
options[:force],
options[:install_dir])
if installed_gems
installed_gems.compact!
installed_gems.each do |spec|
say "Successfully installed #{spec.full_name}"
end
end
*Gem::RemoteInstaller(rubygems/remote_installer.rb) [#y2f...
ではGem::RemoteInstaller#installメソッドです。まず初めにR...
unless version_requirement.respond_to?(:satisfied_by?)
version_requirement = Version::Requirement.new [versio...
end
version_requirementはStringオブジェクトを渡すことも可能な...
さて本題の部分です。
spec, source = find_gem_to_install(gem_name, version_req...
dependencies = find_dependencies_not_installed(spec.depe...
installed_gems << install_dependencies(dependencies, for...
cache_dir = @options[:cache_dir] || File.join(install_di...
destination_file = File.join(cache_dir, spec.full_name +...
download_gem(destination_file, source, spec)
installer = new_installer(destination_file)
installed_gems.unshift installer.install(force, install_...
依存関係を見て依存パッケージをインストールしてから指定さ...
**パッケージ情報の取得 [#a114a648]
まずfind_gem_to_installメソッドを見てみましょう。
def find_gem_to_install(gem_name, version_requirement)
specs_n_sources = specs_n_sources_matching gem_name, v...
top_3_versions = specs_n_sources.map{|gs| gs.first.ver...
specs_n_sources.reject!{|gs| !top_3_versions.include?(...
binary_gems = specs_n_sources.reject { |item|
item[0].platform.nil? || item[0].platform==Platform:...
}
# only non-binary gems...return latest
return specs_n_sources.first if binary_gems.empty?
find_gem_to_installメソッドの残りではバイナリgemの処理が...
def specs_n_sources_matching(gem_name, version_requireme...
specs_n_sources = []
source_index_hash.each do |source_uri, source_index|
specs = source_index.search(/^#{Regexp.escape gem_na...
version_requirement)
# TODO move to SourceIndex#search?
ruby_version = Gem::Version.new RUBY_VERSION
specs = specs.select do |spec|
spec.required_ruby_version.nil? or
spec.required_ruby_version.satisfied_by? ruby_ve...
end
specs.each { |spec| specs_n_sources << [spec, source...
end
specs_n_sources = specs_n_sources.sort_by { |gs,| gs.v...
specs_n_sources
end
def source_index_hash
return @source_index_hash if @source_index_hash
@source_index_hash = {}
Gem::SourceInfoCache.cache_data.each do |source_uri, s...
@source_index_hash[source_uri] = sic_entry.source_in...
end
@source_index_hash
end
どうやらリモートのパッケージ一覧をキャッシュしてそこから...
*Gem::SourceInfoCache(rubygems/source_info_cache.rb) [#l3...
Gem::SourceInfoCache.cache_dataから始まる呼び出しは以下の...
def self.cache_data
cache.cache_data
end
def self.cache
return @cache if @cache
@cache = new
@cache.refresh
@cache
end
def refresh
Gem.sources.each do |source_uri|
cache_entry = cache_data[source_uri]
if cache_entry.nil? then
cache_entry = Gem::SourceInfoCacheEntry.new nil, 0
cache_data[source_uri] = cache_entry
end
cache_entry.refresh source_uri
end
update
flush
end
今回は初めてgem installしたのでSourceInfoCache#cache_data...
Gem.sourcesってどこに定義されているんだ?と探したところ、...
*Gem::SourceIndex(rubygems/source_index.rb) [#nb885523]
Gem::SourceInfoCacheEntry#refreshメソッドです。content-le...
def refresh(source_uri)
remote_size = Gem::RemoteFetcher.fetcher.fetch_size so...
return if @size == remote_size # HACK bad check, local...
@source_index.update source_uri
@size = remote_size
end
Gem::SourceIndexクラスに移りましょう。
def update(source_uri)
use_incremental = false
begin
gem_names = fetch_quick_index source_uri
remove_extra gem_names
missing_gems = find_missing gem_names
use_incremental = missing_gems.size <= INCREMENTAL_T...
rescue Gem::OperationNotSupportedError => ex
use_incremental = false
end
if use_incremental then
update_with_missing source_uri, missing_gems
else
new_index = fetch_bulk_index source_uri
@gems.replace new_index.gems
end
self
end
以前パッケージ情報を取得したときから増えているパッケージ...
def fetch_bulk_index(source_uri)
say "Bulk updating Gem source index for: #{source_uri}"
begin
yaml_spec = fetcher.fetch_path source_uri + '/yaml.Z'
yaml_spec = unzip yaml_spec
rescue
begin
yaml_spec = fetcher.fetch_path source_uri + '/yaml'
end
end
convert_specs yaml_spec
end
どうやらパッケージ情報はYAMLで書かれているようです。長い...
え〜っと・・・、ここまででパッケージ情報が取得できたので...
def search(gem_pattern, version_requirement=Version::Req...
gem_pattern = /#{ gem_pattern }/i if String === gem_pa...
version_requirement = Gem::Version::Requirement.create...
result = []
@gems.each do |full_spec_name, spec|
next unless spec.name =~ gem_pattern
result << spec if version_requirement.satisfied_by?(...
end
result = result.sort
result
end
*再びGem::RemoteInstaller [#rc03388a]
**インストールされていない依存パッケージの検索 [#n167fb09]
Gem::RemoteInstaller#installメソッドに戻ります。次はfind_...
def find_dependencies_not_installed(dependencies)
to_install = []
dependencies.each do |dependency|
srcindex = Gem::SourceIndex.from_installed_gems
matches = srcindex.find_name(dependency.name, depend...
to_install.push dependency if matches.empty?
end
to_install
end
そんな感じかなというところです。もちろん、必要がないのに...
Gem::SourceIndex#from_installed_gemsメソッドおよび呼ばれ...
def from_installed_gems(*deprecated)
if deprecated.empty?
from_gems_in(*installed_spec_directories)
else
from_gems_in(*deprecated)
end
end
def from_gems_in(*spec_dirs)
self.new.load_gems_in(*spec_dirs)
end
def load_gems_in(*spec_dirs)
@gems.clear
specs = Dir.glob File.join("{#{spec_dirs.join(',')}}",...
specs.each do |file_name|
gemspec = self.class.load_specification(file_name.un...
add_spec(gemspec) if gemspec
end
self
end
def load_specification(file_name)
spec_code = File.read(file_name).untaint
gemspec = eval(spec_code)
gemspec.loaded_from = file_name
return gemspec
end
gemspecファイルを見るとわかりますがgemspecファイルはRuby...
**依存パッケージのインストール [#d129ec81]
Gem::RemoteInstaller#installメソッドに戻って、次のinstall...
def install_dependencies(dependencies, force, install_dir)
return if @options[:ignore_dependencies]
installed_gems = []
dependencies.each do |dep|
if @options[:include_dependencies] ||
ask_yes_no("Install required dependency #{dep.nam...
remote_installer = RemoteInstaller.new @options
installed_gems << remote_installer.install(dep.name,
dep.ver...
force, ...
elsif force then
# ignore
else
raise DependencyError, "Required dependency #{dep....
end
end
installed_gems
end
つまりこういうことです。
+aパッケージをインストール。b, cに依存(RemoteInstaller.i...
++bパッケージインストール。d, eに依存(RemoteInstaller.in...
+++dパッケージをインストール(RemoteInstaller.install('d'...
+++eパッケージをインストール(RemoteInstaller.install('e'...
++cパッケージをインストール(RemoteInstaller.install('c'))
*Gem::Installer(rubygems/installer.rb) [#z88a792c]
次にリモートからファイルをダウンロードしてくると、後はロ...
def install(force=false, install_dir=Gem.dir, ignore_thi...
format = Gem::Format.from_file_by_path @gem, security_...
# Build spec dir.
@directory = File.join(install_dir, "gems", format.spe...
FileUtils.mkdir_p @directory
extract_files(@directory, format)
generate_bin(format.spec, install_dir)
build_extensions(@directory, format.spec)
# Build spec/cache/doc dir.
build_support_directories(install_dir)
# Write the spec and cache files.
write_spec(format.spec, File.join(install_dir, "specif...
unless File.exist? File.join(install_dir, "cache", @ge...
FileUtils.cp @gem, File.join(install_dir, "cache")
end
puts format.spec.post_install_message unless format.sp...
format.spec.loaded_from = File.join(install_dir, 'spec...
return format.spec
end
*Gem::Package(rubygems/package.rb) [#w849937a]
まず一行目のGem::Format.from_file_by_pathメソッドから。例...
def self.from_io(io, gem_path="(io)", security_policy = ...
format = self.new(gem_path)
Package.open_from_io(io, 'r', security_policy) do |pkg|
format.spec = pkg.metadata
format.file_entries = []
pkg.each do |entry|
format.file_entries << [{"size", entry.size, "mode...
"path", entry.full_name}, entry.read]
end
end
format
end
Gem::Packageに処理が移っています。package.rbを開いて眺め...
Gem::Package.open_from_ioメソッドは第2引数modeが"r"の場合...
TarInput#initializeメソッドでは与えられたストリームからTa...
次にPackage#eachメソッドを眺めてみます。
def each(&block)
@tarreader.each do |entry|
next unless entry.full_name == "data.tar.gz"
is = zipped_stream(entry)
begin
TarReader.new(is) do |inner|
inner.each(&block)
end
ensure
is.close if is
end
end
@tarreader.rewind
end
というわけでPackage#eachメソッドのブロックに渡されてくる...
*追補Gem::Installer [#q9bc9ae7]
Installer#installメソッドに戻って、extract_filesメソッド...
以上でRubyGemsのパッケージインストール処理は終了となりま...
*おわりに [#k91b8adb]
今回はRubyGemsのインストール処理を読みました。これだ!と...
それではみなさんもよいコードリーディングを。
ページ名: