Rakeを徹底解剖 - その3 "タスクの定義"
前回はタスクの読み込みについて調べたが、今回は読み込んだrakefileをどう解釈し、タスクとして定義しているのかについて調べていく。
前回よりタスクの読み込みは、Rake.load_rakefile
によって行われていることがわかりました。
## rake/rake_module.rb def load_rakefile(path) load(path) end
ここで、読み込む際にタスクとして定義する dsl は全てRake::DSL
に定義されたものになっています。
今回はその中でも、頻繁に使う task
の宣言のみに焦点を当てて読み解いていきます。
なお、まだその1, その2を読んでない方はこちらから。
タスクの定義
タスクの定義は以下のようにDSLとして用意されています。
## rake/dsl_definition.rb def task(*args, &block) # :doc: Rake::Task.define_task(*args, &block) end
## rake/task.rb def define_task(*args, &block) Rake.application.define_task(self, *args, &block) end
Rake::Task.define_task
をコールしてはいるが実態は、Rake.application.define_task
のようだ。
## rake/task_manager.rb def define_task(task_class, *args, &block) # :nodoc: task_name, arg_names, deps = resolve_args(args) original_scope = @scope if String === task_name and not task_class.ancestors.include? Rake::FileTask then task_name, *definition_scope = *(task_name.split(":").reverse) @scope = Scope.make(*(definition_scope + @scope.to_a)) end task_name = task_class.scope_name(@scope, task_name) deps = [deps] unless deps.respond_to?(:to_ary) deps = deps.map { |d| Rake.from_pathname(d).to_s } task = intern(task_class, task_name) task.set_arg_names(arg_names) unless arg_names.empty? if Rake::TaskManager.record_task_metadata add_location(task) task.add_description(get_description(task)) end task.enhance(deps, &block) ensure @scope = original_scope end
まず、一行目を見てみる。
task_name, arg_names, deps = resolve_args(args)
これはどうやら、taskに与えられた引数を処理しているらしい。 rakeのタスクは、たくさんの定義の仕方が存在していて、以下のような書き方がある。
task task_name task task_name: dependencies task task_name, arguments => dependencies
これらをargs
として取得して、それぞれ task_name
, arg_names
, deps
としてパースするのがここの役目。
与えられる引数の構造が結構違って大変だが、それを全てこのresolve_args
で対応してます。
ちなみに処理の内容は以下の感じ。
## rake/task_manager.rb def resolve_args(args) if args.last.is_a?(Hash) deps = args.pop resolve_args_with_dependencies(args, deps) else resolve_args_without_dependencies(args) end end def resolve_args_without_dependencies(args) task_name = args.shift if args.size == 1 && args.first.respond_to?(:to_ary) arg_names = args.first.to_ary else arg_names = args end [task_name, arg_names, []] end def resolve_args_with_dependencies(args, hash) # :nodoc: fail "Task Argument Error" if hash.size != 1 key, value = hash.map { |k, v| [k, v] }.first if args.empty? task_name = key arg_names = [] deps = value || [] else task_name = args.shift arg_names = key deps = value end deps = [deps] unless deps.respond_to?(:to_ary) [task_name, arg_names, deps] end
わぉ、かなりの力技。。。
これ、抜け漏れないか確認するのかなり大変だな。。。
さて、次はこちら。
original_scope = @scope if String === task_name and not task_class.ancestors.include? Rake::FileTask then task_name, *definition_scope = *(task_name.split(":").reverse) @scope = Scope.make(*(definition_scope + @scope.to_a)) end
これは、とりあえず今回は、Rake::Task
を中心にみたいので、task_class.ancestors.include? Rake::FileTask
については割愛。
if文の中の処理としては、task 'namespace_name:task_name'
という形式で書かれたタスクを、以下のように書いたもののと同様に扱うためのものと見られる。
namespace namespace_name do task :task_name do end end
@scope
については、詳しくは調べてはいないが、おそらくこのタスクを定義する際のネストを意味しているのであろう。
それでは、あとは残りの部分を読み解いていく。
# task名をscopeを考慮した形へ変換 task_name = task_class.scope_name(@scope, task_name) # depsの整形 deps = [deps] unless deps.respond_to?(:to_ary) deps = deps.map { |d| Rake.from_pathname(d).to_s } # Pathname を string へ変換してるらしい # タスククラスの登録および取得 task = intern(task_class, task_name) # タスクへ各種変数を代入 task.set_arg_names(arg_names) unless arg_names.empty? if Rake::TaskManager.record_task_metadata # "rake -T"のようにタスク情報を表示する必要があるときのみ add_location(task) task.add_description(get_description(task)) end task.enhance(deps, &block)
基本的にはタスクインスタンスを生成し、各種タスクの仕様を設定している感じのようだ。
まとめ
今回は、dslによるタスクの読み込みについて調べた。
見てみた感想としては、task
の定義自体に焦点をあてればそこまで難しくはなさそうであった。
気になっていた豊富なタスクの指定方法とその、解釈の仕方だがどうやら、かなり力技で対応しているようだった。
次回は、ついにタスクの実行時の動作について。dependencies
やargs
の処理など、正直ここがキモだ!
じっくり、解剖していこう。