跟踪一个简单的rake,如下

require 'trace_tree'

binding.trace_tree(htmp: 'rake/simple') do
  task default: [:print]

  task :print do
    puts 'hello'
  end
end


调用栈如下

20170805_212932_055_simple.html

首先是生成一个rake application,穿过dsl那一层后到达的define_task,所做的主要是resolve_args(分离出任务名、参数名、依赖名)、intern(生成task,并暂存起来到本application中)、enhance(给task增加依赖、方法体)

define_task源码如下

# rake-12.0.0/lib/rake/task_manager.rb
module Rake
  module TaskManager

    def define_task(task_class, *args, &block)
      task_name, arg_names, deps = resolve_args(args)

      original_scope = @scope
      if String === task_name and
         not task_class.ancestors.include? Rake::FileTask
        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

    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 intern(task_class, task_name)
      @tasks[task_name.to_s] ||= task_class.new(task_name, self)
    end


enhance如下

# rake-12.0.0/lib/rake/task.rb
module Rake
  class Task

    def enhance(deps=nil, &block)
      @prerequisites |= deps if deps
      @actions << block if block_given?
      self
    end


然后在rakefile顶部加入puts caller,再rake一次,看rakefile是在哪一点被读取,以及读取完rakefile之后又会怎样编排task来

$ rk rake
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/rake_module.rb:28:in `load'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/rake_module.rb:28:in `load_rakefile'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/application.rb:687:in `raw_load_rakefile'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/application.rb:96:in `block in load_rakefile'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/application.rb:178:in `standard_exception_handling'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/application.rb:95:in `load_rakefile'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/application.rb:79:in `block in run'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/application.rb:178:in `standard_exception_handling'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/lib/rake/application.rb:77:in `run'
/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rake-12.0.0/exe/rake:27:in `<top (required)="">'
/home/z/.rbenv/versions/2.4.0/bin/rake:22:in `load'
/home/z/.rbenv/versions/2.4.0/bin/rake:22:in `</top>
' hello


可见是在application#run里的top_level,于是跟踪一下它

# Run the Rake application.  The run method performs the following
# three steps:
#
# * Initialize the command line options (+init+).
# * Define the tasks (+load_rakefile+).
# * Run the top level tasks (+top_level+).
def run
  standard_exception_handling do
    init
    load_rakefile
    top_level
  end
end

def top_level
  run_with_threads do
    if options.show_tasks
      display_tasks_and_comments
    elsif options.show_prereqs
      display_prerequisites
    else
      require 'trace_tree'
      binding.trace_tree(htmp: 'rake/invoke') do
      top_level_tasks.each { |task_name| invoke_task(task_name) }
      end
    end
  end
end


调用栈如下

20170806_105540_373_invoke.html

源码如下

依赖的检查是在运行时才查找

# rake-12.0.0/lib/rake/task.rb
def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
  if application.options.always_multitask
    invoke_prerequisites_concurrently(task_args, invocation_chain)
  else
    prerequisite_tasks.each { |p|
      prereq_args = task_args.new_scope(p.arg_names)
      p.invoke_with_call_chain(prereq_args, invocation_chain)
    }
  end
end

def prerequisite_tasks
  prerequisites.map { |pre| lookup_prerequisite(pre) }
end

def lookup_prerequisite(prerequisite_name) # :nodoc:
  scoped_prerequisite_task = application[prerequisite_name, @scope]
  if scoped_prerequisite_task == self
    unscoped_prerequisite_task = application[prerequisite_name]
  end
  unscoped_prerequisite_task || scoped_prerequisite_task
end


而“依赖是否在更早时执行过”,以及环形调用的检查,则分别通过@already_invoked和InvocationChain来做

# rake-12.0.0/lib/rake/task.rb
def invoke(*args)
  task_args = TaskArguments.new(arg_names, args)
  invoke_with_call_chain(task_args, InvocationChain::EMPTY)
end

# Same as invoke, but explicitly pass a call chain to detect
# circular dependencies.
def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
  new_chain = InvocationChain.append(self, invocation_chain)
  @lock.synchronize do
    if application.options.trace
      application.trace "** Invoke #{name} #{format_trace_flags}"
    end
    return if @already_invoked
    @already_invoked = true
    invoke_prerequisites(task_args, new_chain)
    execute(task_args) if needed?
  end
rescue Exception => ex
  add_chain_to(ex, new_chain)
  raise ex
end


于是,这个简单的rake任务执行的部分,粗略来看,基本上是这样