Capistranoのコードリーディングをしてみました。今回はCapistranoが利用しているRakeの基本部分の紹介をします。

概要

コードの概要をまずはざっくり

  • Rake::Applicationを継承したのがCapistranoなので基本的にRakeタスクを走らせるのと同じ感じで処理が進む
    • Rakefile => Capfileだったり、環境設定のタスクを定義したりするのが違うところ
  • 以下の処理を実行している
    • パラメータ初期化
    • タスクファイルの読み込み
    • タスクの実行
  • 環境のセットアップ自体がタスクになっている。例えばconfig/deploy/staging.rbを読み込むとstagingというタスクが作成される。引数の順にタスクが実行される仕様なので、環境による変数設定のタスク => メインタスクの実行という流れで処理が行われる。
    • cap staging deploy コマンドを叩くのはstagingとdeployというタスクを順に実行していることになる

コードリーディング

まず、Capistrano::Application.new.runが走ります。

Capistrano::ApplicationはRake::Applicationを継承しています。

runメソッドでは継承元のRake::Applicationを呼び出します。

runメソッドはinit/load_rakefile/top_levelの3つに処理が分かれています。

initメソッド

standard_exception_handlingはエラーハンドリングをラップするメソッドになります。

initは@top_level_tasksにcapコマンドの引数の環境名、タスク名を格納する処理です。

handle_options、standard_rake_options経由で呼び出されるsort_optionsメソッドはオーバーライドされていて、capistrano独自のオプションを扱えるようになっています。

load_rakefileメソッド

load_rakefileはraw_load_rakefileを呼び出します。rakefile(=Capfile)を検索して、Rake.load_rakefileを呼び出して、rakefileをロードします。

find_rakefile_locationはCapfile(厳密には@rakefilesのファイル名)が見つかるまで上の階層に登っていき、Capfileがあるディレクトリとファイル名を返します。

load_rakefileの実体はKernel#loadの呼び出しです。

options.rakelib直下のrakeファイルを読み出していますが、実体はadd_importメソッドで@pending_importsに追加しています。importメソッドはCapfileでのlib/capistrano/tasks/*.rakeの読み出しに利用されていて、add_importメソッドを呼び出しています。

raw_load_rakefileの最後にload_importsメソッドを呼び出していますが、ここで@pending_importsで加えたtaskファイルを読み出しています。loader.loadはDefautLoader#loadで、実体はRake#load_rakefile、つまりKernel#loadを呼び出します。

loadによるRakeタスクの定義

CapistranoというよりはRakeタスクの定義の説明になります。

require “rake” => require “rake/dsl_definition”で呼び出し元(Rake::Application.new.runの実行コンテキスト)のselfに対してRake::DSLモジュールのメソッドが定義されます。

namespaceメソッドでは引数を適切な文字列に変換し、Rake.application.in_namespaceを呼び出します。

scopeを定義するとScopeクラスというLinkedListクラスのインスタンスが@scopeに設定されます。namespaceのブロックを抜けると@scope.tailが呼び出され、親のscopeに戻ります。

namespaceをネストするとScopeクラスのLinkedListが連結されていきます。直近でnamespace呼び出ししたものがLinkedListの先頭になるので、タスク名をnamespaceを考慮して書き出すときはLinkedListの逆順に出力すると、最初に定義した順にnamespaceが並びます。

taskはdefine_taskメソッドを呼び出します。

Rake::TaskManagerにdefine_taskが定義されており、Rake::TaskManagerはRake::Applicationにincludeされています。

internメソッドで@tasksのハッシュに、タスク名をキーにRake::Taskクラスのインスタンスを格納します。

enhanceメソッドで引数として与えられたブロックを@actionsに登録します。

これによってタスク名 => タスク => アクション(ブロック)の紐付けがされます。

環境(stage)のロード

capistrano/setupをrequireすると、以下のコードによって環境登録用のタスクが登録されます。

loadメソッドによってconfig/deploy.rbとconfig/deploy/xxx.rbが読み出されます。

top_level

最後にtop_levelメソッドを呼び出して、タスクを実行します。options.show_tasks、options.show_prereqsともにfalseyな値の場合、top_level_tasks.eachが呼び出されます。

tasks_without_stage_dependencyはstages + default_tasksになります。

cap staging helloと叩くと、@top_level_tasks.firstはstagingになるので、@top_level_tasksが返されます。

これらの引数文字列(cap stagin helloだとstagingとhelloが引数)を元にinvoke_taskが実行されます。parse_task_stringは引数とタスク名に分解します。

self[]はタスク名からRake::Taskクラスを取得します。self.lookupはRake::TaskManagerで格納した@tasksのハッシュからタスク名をキーにRake::Taskクラスを取得しています。

Rake::Task#invoke => Rake::Task#invoke_with_call_chain => Rake::Task#executeを呼び出します。

executeメソッドではタスクの@actions(ブロックの配列)が引数付きで呼び出されます。

全体の流れはこんな感じ。

次回はon、execute周りのDSLの解説をする予定です!