kei-p3’s blog

kei-pによる技術共有と思考整理

Rakeを徹底解剖 - その2 "タスクの読みこみ"

前回に引き続き、rakeの仕組みを知るべく、ソースリーディングをして処理を解読していく。

前回では、initの中身を調べて行ったが今回は、タスクの読み込みおよびタスクの定義に焦点を当てて、読み込んでいく。

## rake/application.rb

def run
  standard_exception_handling do
    init               # 初期化
    load_rakefile      # taskの読み込み <= ココ
    top_level          # コマンドの実行
  end
end

まだ、その1を読んでいない方はこちらから。

kei-p3.hatenablog.com

タスクの読み込み

まずは、load_rakefileを見てみる。

## rake/application.rb

def load_rakefile
  standard_exception_handling do
    raw_load_rakefile
  end
end

前回もでてきたExceptionをcatchするブロックが登場。
読み込み処理の肝心な部分は、raw_load_rakefileのようだ。

## rake/application.rb

def raw_load_rakefile # :nodoc:
  rakefile, location = find_rakefile_location
  if (! options.ignore_system) &&
      (options.load_system || rakefile.nil?) &&
      system_dir && File.directory?(system_dir)
    print_rakefile_directory(location)
    glob("#{system_dir}/*.rake") do |name|
      add_import name
    end
  else
    fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if
      rakefile.nil?
    @rakefile = rakefile
    Dir.chdir(location)
    print_rakefile_directory(location)
    Rake.load_rakefile(File.expand_path(@rakefile)) if
      @rakefile && @rakefile != ''
    options.rakelib.each do |rlib|
      glob("#{rlib}/*.rake") do |name|
        add_import name
      end
    end
  end
  load_imports
end

まずは、この中のfind_rakefile_locationから。

def find_rakefile_location # :nodoc:
  here = Dir.pwd
  until (fn = have_rakefile)
    Dir.chdir("..")
    return nil if Dir.pwd == here || options.nosearch
    here = Dir.pwd
  end
  [fn, here]
ensure
  Dir.chdir(Rake.original_dir)
end

この中では、Rakefileを探してる模様。
実行したときのカレントディレクトリから始まり、見つかるまでは親のディレクトリをひたすら登っていくようになっている。
そのため、rakeはそのプロジェクト内のディレクトリであれば、どこからでも呼べるようになっているようだ。

さて、再びraw_load_rakefileへ戻る。

if (! options.ignore_system) &&
    (options.load_system || rakefile.nil?) &&
    system_dir && File.directory?(system_dir)
  print_rakefile_directory(location)
  glob("#{system_dir}/*.rake") do |name|
    add_import name
  end
else

options.ignore_systemに関しては、--no-systemを設定したときの動作である。今回は通常のrakeの挙動を追っていきたいの、ここはパスする。
よって、elseの部分だけを切り出して深掘っていく。

rakefile, location = find_rakefile_location

fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if rakefile.nil?
@rakefile = rakefile

# カレントディレクトリの移動
Dir.chdir(location)

# 現在のディレクトリの位置を表示 (Rakefileが実行時のカレントディクトリにない場合のみ)
print_rakefile_directory(location)

# Rakefileをロード
Rake.load_rakefile(File.expand_path(@rakefile)) if @rakefile && @rakefile != ''

# rakelibに指定されたディレクトリがrake読み込み対象になる
# のちの load_imports で 読み込まれる
options.rakelib.each do |rlib|
  glob("#{rlib}/*.rake") do |name|
    add_import name
  end
end

load_imports

ここで気になったのは、load_rakefile
ただ、中を探してみるととくに特別なことはしておらず、ただ単にloadを呼び出している。

## rake/rake_module.rb

def load_rakefile(path)
  load(path)
end

タスクの追加はRake::Application自身が行っていると思ったが、そうではないようだ。
このときにRakefileを読み込んでいるのは、mainである。

気になっていたDSLの解釈のmoduleであるRake::DSLをなぜmainextendしているのだろうというところだが、
その理由はloadによってタスクの読み込みを行っているためであることが判明した。

さて、読み込みとしては最後となるload_importsrakelibディレクトリや、引数として指定されたディレクトリのrakeタスクを読み込むようだ。

ここで使っているlookupだが、まだ詳しく処理の内容はわからない。 返り値としては、nilになるため、ここの処理の内容が詳しくはわからない。

どうやら、すでにタスクが定義されているとタスクが返却されるようだ。
そしてそれを実行しているようだ。

現時点でこの役割はわからないが、あまり一般的な利用法でもないと思うので、とりあえずここも深堀しないことにする。(のちにわかるかもしれない)

def load_imports # :nodoc:
  while fn = @pending_imports.shift
    next if @imported.member?(fn)
    fn_task = lookup(fn) and fn_task.invoke
    ext = File.extname(fn)
    loader = @loaders[ext] || @default_loader
    loader.load(fn)
    if fn_task = lookup(fn) and fn_task.needed?
      fn_task.reenable
      fn_task.invoke
      loader.load(fn)
    end
    @imported << fn
  end
end

まとめ

今回は、タスクの読み込みについて調べてみた。
前回の初期化に比べると、なかなか複雑な感じがでてきた。 細かなオプションによって細かな挙動が変わるようで、いままで知らない使い方が発見できそうだ。