123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- require 'rake/invocation_exception_mixin'
- module Rake
- # #########################################################################
- # A Task is the basic unit of work in a Rakefile. Tasks have associated
- # actions (possibly more than one) and a list of prerequisites. When
- # invoked, a task will first ensure that all of its prerequisites have an
- # opportunity to run and then it will execute its own actions.
- #
- # Tasks are not usually created directly using the new method, but rather
- # use the +file+ and +task+ convenience methods.
- #
- class Task
- # List of prerequisites for a task.
- attr_reader :prerequisites
- # List of actions attached to a task.
- attr_reader :actions
- # Application owning this task.
- attr_accessor :application
- # Comment for this task. Restricted to a single line of no more than 50
- # characters.
- attr_reader :comment
- # Full text of the (possibly multi-line) comment.
- attr_reader :full_comment
- # Array of nested namespaces names used for task lookup by this task.
- attr_reader :scope
- # File/Line locations of each of the task definitions for this
- # task (only valid if the task was defined with the detect
- # location option set).
- attr_reader :locations
- # Return task name
- def to_s
- name
- end
- def inspect
- "<#{self.class} #{name} => [#{prerequisites.join(', ')}]>"
- end
- # List of sources for task.
- attr_writer :sources
- def sources
- @sources ||= []
- end
- # List of prerequisite tasks
- def prerequisite_tasks
- prerequisites.collect { |pre| lookup_prerequisite(pre) }
- end
- def lookup_prerequisite(prerequisite_name)
- application[prerequisite_name, @scope]
- end
- private :lookup_prerequisite
- # First source from a rule (nil if no sources)
- def source
- @sources.first if defined?(@sources)
- end
- # Create a task named +task_name+ with no actions or prerequisites. Use
- # +enhance+ to add actions and prerequisites.
- def initialize(task_name, app)
- @name = task_name.to_s
- @prerequisites = []
- @actions = []
- @already_invoked = false
- @full_comment = nil
- @comment = nil
- @lock = Monitor.new
- @application = app
- @scope = app.current_scope
- @arg_names = nil
- @locations = []
- end
- # Enhance a task with prerequisites or actions. Returns self.
- def enhance(deps=nil, &block)
- @prerequisites |= deps if deps
- @actions << block if block_given?
- self
- end
- # Name of the task, including any namespace qualifiers.
- def name
- @name.to_s
- end
- # Name of task with argument list description.
- def name_with_args # :nodoc:
- if arg_description
- "#{name}#{arg_description}"
- else
- name
- end
- end
- # Argument description (nil if none).
- def arg_description # :nodoc:
- @arg_names ? "[#{(arg_names || []).join(',')}]" : nil
- end
- # Name of arguments for this task.
- def arg_names
- @arg_names || []
- end
- # Reenable the task, allowing its tasks to be executed if the task
- # is invoked again.
- def reenable
- @already_invoked = false
- end
- # Clear the existing prerequisites and actions of a rake task.
- def clear
- clear_prerequisites
- clear_actions
- self
- end
- # Clear the existing prerequisites of a rake task.
- def clear_prerequisites
- prerequisites.clear
- self
- end
- # Clear the existing actions on a rake task.
- def clear_actions
- actions.clear
- self
- end
- # Invoke the task if it is needed. Prerequisites are invoked first.
- 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
- $stderr.puts "** 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
- protected :invoke_with_call_chain
- def add_chain_to(exception, new_chain)
- exception.extend(InvocationExceptionMixin) unless exception.respond_to?(:chain)
- exception.chain = new_chain if exception.chain.nil?
- end
- private :add_chain_to
- # Invoke all the prerequisites of a task.
- def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
- prerequisite_tasks.each { |prereq|
- prereq_args = task_args.new_scope(prereq.arg_names)
- prereq.invoke_with_call_chain(prereq_args, invocation_chain)
- }
- end
- # Format the trace flags for display.
- def format_trace_flags
- flags = []
- flags << "first_time" unless @already_invoked
- flags << "not_needed" unless needed?
- flags.empty? ? "" : "(" + flags.join(", ") + ")"
- end
- private :format_trace_flags
- # Execute the actions associated with this task.
- def execute(args=nil)
- args ||= EMPTY_TASK_ARGS
- if application.options.dryrun
- $stderr.puts "** Execute (dry run) #{name}"
- return
- end
- if application.options.trace
- $stderr.puts "** Execute #{name}"
- end
- application.enhance_with_matching_rule(name) if @actions.empty?
- @actions.each do |act|
- case act.arity
- when 1
- act.call(self)
- else
- act.call(self, args)
- end
- end
- end
- # Is this task needed?
- def needed?
- true
- end
- # Timestamp for this task. Basic tasks return the current time for their
- # time stamp. Other tasks can be more sophisticated.
- def timestamp
- prerequisite_tasks.collect { |pre| pre.timestamp }.max || Time.now
- end
- # Add a description to the task. The description can consist of an option
- # argument list (enclosed brackets) and an optional comment.
- def add_description(description)
- return if ! description
- comment = description.strip
- add_comment(comment) if comment && ! comment.empty?
- end
- # Writing to the comment attribute is the same as adding a description.
- def comment=(description)
- add_description(description)
- end
- # Add a comment to the task. If a comment already exists, separate
- # the new comment with " / ".
- def add_comment(comment)
- if @full_comment
- @full_comment << " / "
- else
- @full_comment = ''
- end
- @full_comment << comment
- if @full_comment =~ /\A([^.]+?\.)( |$)/
- @comment = $1
- else
- @comment = @full_comment
- end
- end
- private :add_comment
- # Set the names of the arguments for this task. +args+ should be
- # an array of symbols, one for each argument name.
- def set_arg_names(args)
- @arg_names = args.map { |a| a.to_sym }
- end
- # Return a string describing the internal state of a task. Useful for
- # debugging.
- def investigation
- result = "------------------------------\n"
- result << "Investigating #{name}\n"
- result << "class: #{self.class}\n"
- result << "task needed: #{needed?}\n"
- result << "timestamp: #{timestamp}\n"
- result << "pre-requisites: \n"
- prereqs = prerequisite_tasks
- prereqs.sort! {|a,b| a.timestamp <=> b.timestamp}
- prereqs.each do |p|
- result << "--#{p.name} (#{p.timestamp})\n"
- end
- latest_prereq = prerequisite_tasks.collect { |pre| pre.timestamp }.max
- result << "latest-prerequisite time: #{latest_prereq}\n"
- result << "................................\n\n"
- return result
- end
- # ----------------------------------------------------------------
- # Rake Module Methods
- #
- class << self
- # Clear the task list. This cause rake to immediately forget all the
- # tasks that have been assigned. (Normally used in the unit tests.)
- def clear
- Rake.application.clear
- end
- # List of all defined tasks.
- def tasks
- Rake.application.tasks
- end
- # Return a task with the given name. If the task is not currently
- # known, try to synthesize one from the defined rules. If no rules are
- # found, but an existing file matches the task name, assume it is a file
- # task with no dependencies or actions.
- def [](task_name)
- Rake.application[task_name]
- end
- # TRUE if the task name is already defined.
- def task_defined?(task_name)
- Rake.application.lookup(task_name) != nil
- end
- # Define a task given +args+ and an option block. If a rule with the
- # given name already exists, the prerequisites and actions are added to
- # the existing task. Returns the defined task.
- def define_task(*args, &block)
- Rake.application.define_task(self, *args, &block)
- end
- # Define a rule for synthesizing tasks.
- def create_rule(*args, &block)
- Rake.application.create_rule(*args, &block)
- end
- # Apply the scope to the task name according to the rules for
- # this kind of task. Generic tasks will accept the scope as
- # part of the name.
- def scope_name(scope, task_name)
- (scope + [task_name]).join(':')
- end
- end # class << Rake::Task
- end # class Rake::Task
- end
|