# frozen_string_literal: truerequire"rake/invocation_exception_mixin"moduleRake### 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.#classTask# List of prerequisites for a task.attr_reader:prerequisitesaliasprereqsprerequisites# List of order only prerequisites for a task.attr_reader:order_only_prerequisites# List of actions attached to a task.attr_reader:actions# Application owning this task.attr_accessor:application# 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# Has this task already been invoked? Already invoked tasks# will be skipped unless you reenable them.attr_reader:already_invoked# Return task namedefto_snameenddefinspect# :nodoc:"<#{self.class}#{name} => [#{prerequisites.join(', ')}]>"end# List of sources for task.attr_writer:sourcesdefsourcesifdefined?(@sources)@sourceselseprerequisitesendend# List of prerequisite tasksdefprerequisite_tasks(prerequisites+order_only_prerequisites).map{|pre|lookup_prerequisite(pre)}enddeflookup_prerequisite(prerequisite_name)# :nodoc:scoped_prerequisite_task=application[prerequisite_name,@scope]ifscoped_prerequisite_task==selfunscoped_prerequisite_task=application[prerequisite_name]endunscoped_prerequisite_task||scoped_prerequisite_taskendprivate:lookup_prerequisite# List of all unique prerequisite tasks including prerequisite tasks'# prerequisites.# Includes self when cyclic dependencies are found.defall_prerequisite_tasksseen={}collect_prerequisites(seen)seen.valuesenddefcollect_prerequisites(seen)# :nodoc:prerequisite_tasks.eachdo|pre|nextifseen[pre.name]seen[pre.name]=prepre.collect_prerequisites(seen)endendprotected:collect_prerequisites# First source from a rule (nil if no sources)defsourcesources.firstend# Create a task named +task_name+ with no actions or prerequisites. Use# +enhance+ to add actions and prerequisites.definitialize(task_name,app)@name=task_name.to_s@prerequisites=[]@actions=[]@already_invoked=false@comments=[]@lock=Monitor.new@application=app@scope=app.current_scope@arg_names=nil@locations=[]@invocation_exception=nil@order_only_prerequisites=[]end# Enhance a task with prerequisites or actions. Returns self.defenhance(deps=nil,&block)@prerequisites|=depsifdeps@actions<<blockifblock_given?selfend# Name of the task, including any namespace qualifiers.defname@name.to_send# Name of task with argument list description.defname_with_args# :nodoc:ifarg_description"#{name}#{arg_description}"elsenameendend# Argument description (nil if none).defarg_description# :nodoc:@arg_names?"[#{arg_names.join(',')}]":nilend# Name of arguments for this task.defarg_names@arg_names||[]end# Reenable the task, allowing its tasks to be executed if the task# is invoked again.defreenable@already_invoked=false@invocation_exception=nilend# Clear the existing prerequisites, actions, comments, and arguments of a rake task.defclearclear_prerequisitesclear_actionsclear_commentsclear_argsselfend# Clear the existing prerequisites of a rake task.defclear_prerequisitesprerequisites.clearselfend# Clear the existing actions on a rake task.defclear_actionsactions.clearselfend# Clear the existing comments on a rake task.defclear_comments@comments=[]selfend# Clear the existing arguments on a rake task.defclear_args@arg_names=nilselfend# Invoke the task if it is needed. Prerequisites are invoked first.definvoke(*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.## If multiple tasks depend on this# one in parallel, they will all fail if the first execution of# this task fails.definvoke_with_call_chain(task_args,invocation_chain)new_chain=Rake::InvocationChain.append(self,invocation_chain)@lock.synchronizedobeginifapplication.options.traceapplication.trace"** Invoke #{name}#{format_trace_flags}"endif@already_invokedif@invocation_exceptionifapplication.options.traceapplication.trace"** Previous invocation of #{name} failed #{format_trace_flags}"endraise@invocation_exceptionelsereturnendend@already_invoked=trueinvoke_prerequisites(task_args,new_chain)execute(task_args)ifneeded?rescueException=>exadd_chain_to(ex,new_chain)@invocation_exception=exraiseexendendendprotected:invoke_with_call_chaindefadd_chain_to(exception,new_chain)# :nodoc:exception.extend(InvocationExceptionMixin)unlessexception.respond_to?(:chain)exception.chain=new_chainifexception.chain.nil?endprivate:add_chain_to# Invoke all the prerequisites of a task.definvoke_prerequisites(task_args,invocation_chain)# :nodoc:ifapplication.options.always_multitaskinvoke_prerequisites_concurrently(task_args,invocation_chain)elseprerequisite_tasks.each{|p|prereq_args=task_args.new_scope(p.arg_names)p.invoke_with_call_chain(prereq_args,invocation_chain)}endend# Invoke all the prerequisites of a task in parallel.definvoke_prerequisites_concurrently(task_args,invocation_chain)# :nodoc:futures=prerequisite_tasks.mapdo|p|prereq_args=task_args.new_scope(p.arg_names)application.thread_pool.future(p)do|r|r.invoke_with_call_chain(prereq_args,invocation_chain)endend# Iterate in reverse to improve performance related to thread waiting and switchingfutures.reverse_each(&:value)end# Format the trace flags for display.defformat_trace_flagsflags=[]flags<<"first_time"unless@already_invokedflags<<"not_needed"unlessneeded?flags.empty??"":"("+flags.join(", ")+")"endprivate:format_trace_flags# Execute the actions associated with this task.defexecute(args=nil)args||=EMPTY_TASK_ARGSifapplication.options.dryrunapplication.trace"** Execute (dry run) #{name}"returnendapplication.trace"** Execute #{name}"ifapplication.options.traceapplication.enhance_with_matching_rule(name)if@actions.empty?ifopts=Hash.try_convert(args)and!opts.empty?@actions.each{|act|act.call(self,args,**opts)}else@actions.each{|act|act.call(self,args)}endend# Is this task needed?defneeded?trueend# Timestamp for this task. Basic tasks return the current time for their# time stamp. Other tasks can be more sophisticated.deftimestampTime.nowend# Add a description to the task. The description can consist of an option# argument list (enclosed brackets) and an optional comment.defadd_description(description)returnunlessdescriptioncomment=description.stripadd_comment(comment)ifcomment&&!comment.empty?enddefcomment=(comment)# :nodoc:add_comment(comment)enddefadd_comment(comment)# :nodoc:returnifcomment.nil?@comments<<commentunless@comments.include?(comment)endprivate:add_comment# Full collection of comments. Multiple comments are separated by# newlines.deffull_commenttransform_comments("\n")end# First line (or sentence) of all comments. Multiple comments are# separated by a "/".defcommenttransform_comments(" / "){|c|first_sentence(c)}end# Transform the list of comments as specified by the block and# join with the separator.deftransform_comments(separator,&block)if@comments.empty?nilelseblock||=lambda{|c|c}@comments.map(&block).join(separator)endendprivate:transform_comments# Get the first sentence in a string. The sentence is terminated# by the first period, exclamation mark, or the end of the line.# Decimal points do not count as periods.deffirst_sentence(string)string.split(/(?<=\w)(\.|!)[ \t]|(\.$|!)|\n/).firstendprivate:first_sentence# Set the names of the arguments for this task. +args+ should be# an array of symbols, one for each argument name.defset_arg_names(args)@arg_names=args.map(&:to_sym)end# Return a string describing the internal state of a task. Useful for# debugging.definvestigationresult="------------------------------\n".dupresult<<"Investigating #{name}\n"result<<"class: #{self.class}\n"result<<"task needed: #{needed?}\n"result<<"timestamp: #{timestamp}\n"result<<"pre-requisites: \n"prereqs=prerequisite_tasksprereqs.sort!{|a,b|a.timestamp<=>b.timestamp}prereqs.eachdo|p|result<<"--#{p.name} (#{p.timestamp})\n"endlatest_prereq=prerequisite_tasks.map(&:timestamp).maxresult<<"latest-prerequisite time: #{latest_prereq}\n"result<<"................................\n\n"returnresultend# Format dependencies parameter to pass to task.defself.format_deps(deps)deps=[deps]unlessdeps.respond_to?(:to_ary)deps.map{|d|Rake.from_pathname(d).to_s}end# Add order only dependencies.def|(deps)@order_only_prerequisites|=Task.format_deps(deps)-@prerequisitesselfend# ----------------------------------------------------------------# 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.)defclearRake.application.clearend# List of all defined tasks.deftasksRake.application.tasksend# 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.deftask_defined?(task_name)Rake.application.lookup(task_name)!=nilend# 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.defdefine_task(*args,&block)Rake.application.define_task(self,*args,&block)end# Define a rule for synthesizing tasks.defcreate_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.defscope_name(scope,task_name)scope.path_with_task_name(task_name)endend# class << Rake::Taskend# class Rake::Taskend