task.rb 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. require 'rake/invocation_exception_mixin'
  2. module Rake
  3. # #########################################################################
  4. # A Task is the basic unit of work in a Rakefile. Tasks have associated
  5. # actions (possibly more than one) and a list of prerequisites. When
  6. # invoked, a task will first ensure that all of its prerequisites have an
  7. # opportunity to run and then it will execute its own actions.
  8. #
  9. # Tasks are not usually created directly using the new method, but rather
  10. # use the +file+ and +task+ convenience methods.
  11. #
  12. class Task
  13. # List of prerequisites for a task.
  14. attr_reader :prerequisites
  15. # List of actions attached to a task.
  16. attr_reader :actions
  17. # Application owning this task.
  18. attr_accessor :application
  19. # Comment for this task. Restricted to a single line of no more than 50
  20. # characters.
  21. attr_reader :comment
  22. # Full text of the (possibly multi-line) comment.
  23. attr_reader :full_comment
  24. # Array of nested namespaces names used for task lookup by this task.
  25. attr_reader :scope
  26. # File/Line locations of each of the task definitions for this
  27. # task (only valid if the task was defined with the detect
  28. # location option set).
  29. attr_reader :locations
  30. # Return task name
  31. def to_s
  32. name
  33. end
  34. def inspect
  35. "<#{self.class} #{name} => [#{prerequisites.join(', ')}]>"
  36. end
  37. # List of sources for task.
  38. attr_writer :sources
  39. def sources
  40. @sources ||= []
  41. end
  42. # List of prerequisite tasks
  43. def prerequisite_tasks
  44. prerequisites.collect { |pre| lookup_prerequisite(pre) }
  45. end
  46. def lookup_prerequisite(prerequisite_name)
  47. application[prerequisite_name, @scope]
  48. end
  49. private :lookup_prerequisite
  50. # First source from a rule (nil if no sources)
  51. def source
  52. @sources.first if defined?(@sources)
  53. end
  54. # Create a task named +task_name+ with no actions or prerequisites. Use
  55. # +enhance+ to add actions and prerequisites.
  56. def initialize(task_name, app)
  57. @name = task_name.to_s
  58. @prerequisites = []
  59. @actions = []
  60. @already_invoked = false
  61. @full_comment = nil
  62. @comment = nil
  63. @lock = Monitor.new
  64. @application = app
  65. @scope = app.current_scope
  66. @arg_names = nil
  67. @locations = []
  68. end
  69. # Enhance a task with prerequisites or actions. Returns self.
  70. def enhance(deps=nil, &block)
  71. @prerequisites |= deps if deps
  72. @actions << block if block_given?
  73. self
  74. end
  75. # Name of the task, including any namespace qualifiers.
  76. def name
  77. @name.to_s
  78. end
  79. # Name of task with argument list description.
  80. def name_with_args # :nodoc:
  81. if arg_description
  82. "#{name}#{arg_description}"
  83. else
  84. name
  85. end
  86. end
  87. # Argument description (nil if none).
  88. def arg_description # :nodoc:
  89. @arg_names ? "[#{(arg_names || []).join(',')}]" : nil
  90. end
  91. # Name of arguments for this task.
  92. def arg_names
  93. @arg_names || []
  94. end
  95. # Reenable the task, allowing its tasks to be executed if the task
  96. # is invoked again.
  97. def reenable
  98. @already_invoked = false
  99. end
  100. # Clear the existing prerequisites and actions of a rake task.
  101. def clear
  102. clear_prerequisites
  103. clear_actions
  104. self
  105. end
  106. # Clear the existing prerequisites of a rake task.
  107. def clear_prerequisites
  108. prerequisites.clear
  109. self
  110. end
  111. # Clear the existing actions on a rake task.
  112. def clear_actions
  113. actions.clear
  114. self
  115. end
  116. # Invoke the task if it is needed. Prerequisites are invoked first.
  117. def invoke(*args)
  118. task_args = TaskArguments.new(arg_names, args)
  119. invoke_with_call_chain(task_args, InvocationChain::EMPTY)
  120. end
  121. # Same as invoke, but explicitly pass a call chain to detect
  122. # circular dependencies.
  123. def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
  124. new_chain = InvocationChain.append(self, invocation_chain)
  125. @lock.synchronize do
  126. if application.options.trace
  127. $stderr.puts "** Invoke #{name} #{format_trace_flags}"
  128. end
  129. return if @already_invoked
  130. @already_invoked = true
  131. invoke_prerequisites(task_args, new_chain)
  132. execute(task_args) if needed?
  133. end
  134. rescue Exception => ex
  135. add_chain_to(ex, new_chain)
  136. raise ex
  137. end
  138. protected :invoke_with_call_chain
  139. def add_chain_to(exception, new_chain)
  140. exception.extend(InvocationExceptionMixin) unless exception.respond_to?(:chain)
  141. exception.chain = new_chain if exception.chain.nil?
  142. end
  143. private :add_chain_to
  144. # Invoke all the prerequisites of a task.
  145. def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
  146. prerequisite_tasks.each { |prereq|
  147. prereq_args = task_args.new_scope(prereq.arg_names)
  148. prereq.invoke_with_call_chain(prereq_args, invocation_chain)
  149. }
  150. end
  151. # Format the trace flags for display.
  152. def format_trace_flags
  153. flags = []
  154. flags << "first_time" unless @already_invoked
  155. flags << "not_needed" unless needed?
  156. flags.empty? ? "" : "(" + flags.join(", ") + ")"
  157. end
  158. private :format_trace_flags
  159. # Execute the actions associated with this task.
  160. def execute(args=nil)
  161. args ||= EMPTY_TASK_ARGS
  162. if application.options.dryrun
  163. $stderr.puts "** Execute (dry run) #{name}"
  164. return
  165. end
  166. if application.options.trace
  167. $stderr.puts "** Execute #{name}"
  168. end
  169. application.enhance_with_matching_rule(name) if @actions.empty?
  170. @actions.each do |act|
  171. case act.arity
  172. when 1
  173. act.call(self)
  174. else
  175. act.call(self, args)
  176. end
  177. end
  178. end
  179. # Is this task needed?
  180. def needed?
  181. true
  182. end
  183. # Timestamp for this task. Basic tasks return the current time for their
  184. # time stamp. Other tasks can be more sophisticated.
  185. def timestamp
  186. prerequisite_tasks.collect { |pre| pre.timestamp }.max || Time.now
  187. end
  188. # Add a description to the task. The description can consist of an option
  189. # argument list (enclosed brackets) and an optional comment.
  190. def add_description(description)
  191. return if ! description
  192. comment = description.strip
  193. add_comment(comment) if comment && ! comment.empty?
  194. end
  195. # Writing to the comment attribute is the same as adding a description.
  196. def comment=(description)
  197. add_description(description)
  198. end
  199. # Add a comment to the task. If a comment already exists, separate
  200. # the new comment with " / ".
  201. def add_comment(comment)
  202. if @full_comment
  203. @full_comment << " / "
  204. else
  205. @full_comment = ''
  206. end
  207. @full_comment << comment
  208. if @full_comment =~ /\A([^.]+?\.)( |$)/
  209. @comment = $1
  210. else
  211. @comment = @full_comment
  212. end
  213. end
  214. private :add_comment
  215. # Set the names of the arguments for this task. +args+ should be
  216. # an array of symbols, one for each argument name.
  217. def set_arg_names(args)
  218. @arg_names = args.map { |a| a.to_sym }
  219. end
  220. # Return a string describing the internal state of a task. Useful for
  221. # debugging.
  222. def investigation
  223. result = "------------------------------\n"
  224. result << "Investigating #{name}\n"
  225. result << "class: #{self.class}\n"
  226. result << "task needed: #{needed?}\n"
  227. result << "timestamp: #{timestamp}\n"
  228. result << "pre-requisites: \n"
  229. prereqs = prerequisite_tasks
  230. prereqs.sort! {|a,b| a.timestamp <=> b.timestamp}
  231. prereqs.each do |p|
  232. result << "--#{p.name} (#{p.timestamp})\n"
  233. end
  234. latest_prereq = prerequisite_tasks.collect { |pre| pre.timestamp }.max
  235. result << "latest-prerequisite time: #{latest_prereq}\n"
  236. result << "................................\n\n"
  237. return result
  238. end
  239. # ----------------------------------------------------------------
  240. # Rake Module Methods
  241. #
  242. class << self
  243. # Clear the task list. This cause rake to immediately forget all the
  244. # tasks that have been assigned. (Normally used in the unit tests.)
  245. def clear
  246. Rake.application.clear
  247. end
  248. # List of all defined tasks.
  249. def tasks
  250. Rake.application.tasks
  251. end
  252. # Return a task with the given name. If the task is not currently
  253. # known, try to synthesize one from the defined rules. If no rules are
  254. # found, but an existing file matches the task name, assume it is a file
  255. # task with no dependencies or actions.
  256. def [](task_name)
  257. Rake.application[task_name]
  258. end
  259. # TRUE if the task name is already defined.
  260. def task_defined?(task_name)
  261. Rake.application.lookup(task_name) != nil
  262. end
  263. # Define a task given +args+ and an option block. If a rule with the
  264. # given name already exists, the prerequisites and actions are added to
  265. # the existing task. Returns the defined task.
  266. def define_task(*args, &block)
  267. Rake.application.define_task(self, *args, &block)
  268. end
  269. # Define a rule for synthesizing tasks.
  270. def create_rule(*args, &block)
  271. Rake.application.create_rule(*args, &block)
  272. end
  273. # Apply the scope to the task name according to the rules for
  274. # this kind of task. Generic tasks will accept the scope as
  275. # part of the name.
  276. def scope_name(scope, task_name)
  277. (scope + [task_name]).join(':')
  278. end
  279. end # class << Rake::Task
  280. end # class Rake::Task
  281. end