一个简单的rake的执行步骤
跟踪一个简单的rake,如下
require 'trace_tree'
binding.trace_tree(htmp: 'rake/simple') do
task default: [:print]
task :print do
puts 'hello'
end
end
调用栈如下
首先是生成一个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
调用栈如下
源码如下
依赖的检查是在运行时才查找
# 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任务执行的部分,粗略来看,基本上是这样
