kei-p3’s blog

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

Rakeを徹底解剖 - その3 "タスクの定義"

前回はタスクの読み込みについて調べたが、今回は読み込んだrakefileをどう解釈し、タスクとして定義しているのかについて調べていく。

前回よりタスクの読み込みは、Rake.load_rakefile によって行われていることがわかりました。

## rake/rake_module.rb

def load_rakefile(path)
  load(path)
end

ここで、読み込む際にタスクとして定義する dsl は全てRake::DSLに定義されたものになっています。

今回はその中でも、頻繁に使う task の宣言のみに焦点を当てて読み解いていきます。

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

kei-p3.hatenablog.com

kei-p3.hatenablog.com

タスクの定義

タスクの定義は以下のように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の定義自体に焦点をあてればそこまで難しくはなさそうであった。

気になっていた豊富なタスクの指定方法とその、解釈の仕方だがどうやら、かなり力技で対応しているようだった。

次回は、ついにタスクの実行時の動作について。dependenciesargsの処理など、正直ここがキモだ! じっくり、解剖していこう。