mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	 e6c1752137
			
		
	
	
		e6c1752137
		
	
	
	
	
		
			
			<evanfarrar at gmail.com> in [ruby-doc:1382] applied. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@16810 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			2240 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			2240 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable file
		
	
	
	
	
| #!/usr/bin/env ruby
 | |
| 
 | |
| #--
 | |
| 
 | |
| # Copyright (c) 2003, 2004, 2005, 2006, 2007  Jim Weirich
 | |
| #
 | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
| # of this software and associated documentation files (the "Software"), to
 | |
| # deal in the Software without restriction, including without limitation the
 | |
| # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 | |
| # sell copies of the Software, and to permit persons to whom the Software is
 | |
| # furnished to do so, subject to the following conditions:
 | |
| #
 | |
| # The above copyright notice and this permission notice shall be included in
 | |
| # all copies or substantial portions of the Software.
 | |
| #
 | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | |
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 | |
| # IN THE SOFTWARE.
 | |
| #++
 | |
| #
 | |
| # = Rake -- Ruby Make
 | |
| #
 | |
| # This is the main file for the Rake application.  Normally it is referenced
 | |
| # as a library via a require statement, but it can be distributed
 | |
| # independently as an application.
 | |
| 
 | |
| RAKEVERSION = '0.8.0'
 | |
| 
 | |
| require 'rbconfig'
 | |
| require 'getoptlong'
 | |
| require 'fileutils'
 | |
| require 'singleton'
 | |
| require 'thread'
 | |
| require 'ostruct'
 | |
| 
 | |
| ######################################################################
 | |
| # Rake extensions to Module.
 | |
| #
 | |
| class Module
 | |
|   # Check for an existing method in the current class before extending.  IF
 | |
|   # the method already exists, then a warning is printed and the extension is
 | |
|   # not added.  Otherwise the block is yielded and any definitions in the
 | |
|   # block will take effect.
 | |
|   #
 | |
|   # Usage:
 | |
|   #
 | |
|   #   class String
 | |
|   #     rake_extension("xyz") do
 | |
|   #       def xyz
 | |
|   #         ...
 | |
|   #       end
 | |
|   #     end
 | |
|   #   end
 | |
|   #
 | |
|   def rake_extension(method)
 | |
|     if instance_methods.include?(method.to_s) || instance_methods.include?(method.to_sym)
 | |
|       $stderr.puts "WARNING: Possible conflict with Rake extension: #{self}##{method} already exists"
 | |
|     else
 | |
|       yield
 | |
|     end
 | |
|   end
 | |
| end # module Module
 | |
| 
 | |
| 
 | |
| ######################################################################
 | |
| # User defined methods to be added to String.
 | |
| #
 | |
| class String
 | |
|   rake_extension("ext") do
 | |
|     # Replace the file extension with +newext+.  If there is no extension on
 | |
|     # the string, append the new extension to the end.  If the new extension
 | |
|     # is not given, or is the empty string, remove any existing extension.
 | |
|     #
 | |
|     # +ext+ is a user added method for the String class.
 | |
|     def ext(newext='')
 | |
|       return self.dup if ['.', '..'].include? self
 | |
|       if newext != ''
 | |
|         newext = (newext =~ /^\./) ? newext : ("." + newext)
 | |
|       end
 | |
|       dup.sub!(%r(([^/\\])\.[^./\\]*$)) { $1 + newext } || self + newext
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   rake_extension("pathmap") do
 | |
|     # Explode a path into individual components.  Used by +pathmap+.
 | |
|     def pathmap_explode
 | |
|       head, tail = File.split(self)
 | |
|       return [self] if head == self
 | |
|       return [tail] if head == '.' || tail == '/'
 | |
|       return [head, tail] if head == '/'
 | |
|       return head.pathmap_explode + [tail]
 | |
|     end
 | |
|     protected :pathmap_explode
 | |
| 
 | |
|     # Extract a partial path from the path.  Include +n+ directories from the
 | |
|     # front end (left hand side) if +n+ is positive.  Include |+n+|
 | |
|     # directories from the back end (right hand side) if +n+ is negative.
 | |
|     def pathmap_partial(n)
 | |
|       dirs = File.dirname(self).pathmap_explode
 | |
|       partial_dirs =
 | |
|         if n > 0
 | |
|           dirs[0...n]
 | |
|         elsif n < 0
 | |
|           dirs.reverse[0...-n].reverse
 | |
|         else
 | |
|           "."
 | |
|         end
 | |
|       File.join(partial_dirs)
 | |
|     end
 | |
|     protected :pathmap_partial
 | |
|       
 | |
|     # Preform the pathmap replacement operations on the given path. The
 | |
|     # patterns take the form 'pat1,rep1;pat2,rep2...'.
 | |
|     def pathmap_replace(patterns, &block)
 | |
|       result = self
 | |
|       patterns.split(';').each do |pair|
 | |
|         pattern, replacement = pair.split(',')
 | |
|         pattern = Regexp.new(pattern)
 | |
|         if replacement == '*' && block_given?
 | |
|           result = result.sub(pattern, &block)
 | |
|         elsif replacement
 | |
|           result = result.sub(pattern, replacement)
 | |
|         else
 | |
|           result = result.sub(pattern, '')
 | |
|         end
 | |
|       end
 | |
|       result
 | |
|     end
 | |
|     protected :pathmap_replace
 | |
| 
 | |
|     # Map the path according to the given specification.  The specification
 | |
|     # controls the details of the mapping.  The following special patterns are
 | |
|     # recognized:
 | |
|     #
 | |
|     # * <b>%p</b> -- The complete path.
 | |
|     # * <b>%f</b> -- The base file name of the path, with its file extension,
 | |
|     #   but without any directories.
 | |
|     # * <b>%n</b> -- The file name of the path without its file extension.
 | |
|     # * <b>%d</b> -- The directory list of the path.
 | |
|     # * <b>%x</b> -- The file extension of the path.  An empty string if there
 | |
|     #   is no extension.
 | |
|     # * <b>%X</b> -- Everything *but* the file extension.
 | |
|     # * <b>%s</b> -- The alternate file separator if defined, otherwise use
 | |
|     #   the standard file separator.
 | |
|     # * <b>%%</b> -- A percent sign.
 | |
|     #
 | |
|     # The %d specifier can also have a numeric prefix (e.g. '%2d'). If the
 | |
|     # number is positive, only return (up to) +n+ directories in the path,
 | |
|     # starting from the left hand side.  If +n+ is negative, return (up to)
 | |
|     # |+n+| directories from the right hand side of the path.
 | |
|     #
 | |
|     # Examples:
 | |
|     #
 | |
|     #   'a/b/c/d/file.txt'.pathmap("%2d")   => 'a/b'
 | |
|     #   'a/b/c/d/file.txt'.pathmap("%-2d")  => 'c/d'
 | |
|     #
 | |
|     # Also the %d, %p, $f, $n, %x, and %X operators can take a
 | |
|     # pattern/replacement argument to perform simple string substitutions on a
 | |
|     # particular part of the path.  The pattern and replacement are separated
 | |
|     # by a comma and are enclosed by curly braces.  The replacement spec comes
 | |
|     # after the % character but before the operator letter.  (e.g.
 | |
|     # "%{old,new}d").  Muliple replacement specs should be separated by
 | |
|     # semi-colons (e.g. "%{old,new;src,bin}d").
 | |
|     #
 | |
|     # Regular expressions may be used for the pattern, and back refs may be
 | |
|     # used in the replacement text.  Curly braces, commas and semi-colons are
 | |
|     # excluded from both the pattern and replacement text (let's keep parsing
 | |
|     # reasonable).
 | |
|     #
 | |
|     # For example:
 | |
|     #
 | |
|     #    "src/org/onestepback/proj/A.java".pathmap("%{^src,bin}X.class")
 | |
|     #
 | |
|     # returns:
 | |
|     #
 | |
|     #    "bin/org/onestepback/proj/A.class"
 | |
|     #
 | |
|     # If the replacement text is '*', then a block may be provided to perform
 | |
|     # some arbitrary calculation for the replacement.
 | |
|     #
 | |
|     # For example:
 | |
|     #
 | |
|     #   "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext|
 | |
|     #      ext.downcase
 | |
|     #   }
 | |
|     #
 | |
|     # Returns:
 | |
|     #
 | |
|     #  "/path/to/file.txt"
 | |
|     #
 | |
|     def pathmap(spec=nil, &block)
 | |
|       return self if spec.nil?
 | |
|       result = ''
 | |
|       spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag|
 | |
|         case frag
 | |
|         when '%f'
 | |
|           result << File.basename(self)
 | |
|         when '%n'
 | |
|           result << File.basename(self).ext
 | |
|         when '%d'
 | |
|           result << File.dirname(self)
 | |
|         when '%x'
 | |
|           result << $1 if self =~ /[^\/](\.[^.]+)$/
 | |
|         when '%X'
 | |
|           if self =~ /^(.*[^\/])(\.[^.]+)$/
 | |
|             result << $1
 | |
|           else
 | |
|             result << self
 | |
|           end
 | |
|         when '%p'
 | |
|           result << self
 | |
|         when '%s'
 | |
|           result << (File::ALT_SEPARATOR || File::SEPARATOR)
 | |
|         when '%-'
 | |
|           # do nothing
 | |
|         when '%%'
 | |
|           result << "%"
 | |
|         when /%(-?\d+)d/
 | |
|           result << pathmap_partial($1.to_i)
 | |
|         when /^%\{([^}]*)\}(\d*[dpfnxX])/
 | |
|           patterns, operator = $1, $2
 | |
|           result << pathmap('%' + operator).pathmap_replace(patterns, &block)
 | |
|         when /^%/
 | |
|           fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'"
 | |
|         else
 | |
|           result << frag
 | |
|         end
 | |
|       end
 | |
|       result
 | |
|     end
 | |
|   end
 | |
| end # class String
 | |
| 
 | |
| ##############################################################################
 | |
| module Rake
 | |
| 
 | |
|   # --------------------------------------------------------------------------
 | |
|   # Rake module singleton methods.
 | |
|   #
 | |
|   class << self
 | |
|     # Current Rake Application
 | |
|     def application
 | |
|       @application ||= Rake::Application.new
 | |
|     end
 | |
| 
 | |
|     # Set the current Rake application object.
 | |
|     def application=(app)
 | |
|       @application = app
 | |
|     end
 | |
| 
 | |
|     # Return the original directory where the Rake application was started.
 | |
|     def original_dir
 | |
|       application.original_dir
 | |
|     end
 | |
| 
 | |
|   end
 | |
| 
 | |
|   # ##########################################################################
 | |
|   # Mixin for creating easily cloned objects.
 | |
|   #
 | |
|   module Cloneable
 | |
|     # Clone an object by making a new object and setting all the instance
 | |
|     # variables to the same values.
 | |
|     def clone
 | |
|       sibling = self.class.new
 | |
|       instance_variables.each do |ivar|
 | |
|         value = self.instance_variable_get(ivar)
 | |
|         new_value = value.clone rescue value
 | |
|         sibling.instance_variable_set(ivar, new_value)
 | |
|       end
 | |
|       sibling
 | |
|     end
 | |
|     alias dup clone
 | |
|   end
 | |
| 
 | |
|   ####################################################################
 | |
|   # TaskAguments manage the arguments passed to a task.
 | |
|   #
 | |
|   class TaskArguments
 | |
|     include Enumerable
 | |
| 
 | |
|     attr_reader :names
 | |
| 
 | |
|     def initialize(names, values, parent=nil)
 | |
|       @names = names
 | |
|       @parent = parent
 | |
|       @hash = {}
 | |
|       names.each_with_index { |name, i|
 | |
|         @hash[name.to_sym] = values[i]
 | |
|       }
 | |
|     end
 | |
| 
 | |
|     # Create a new argument scope using the prerequisite argument
 | |
|     # names.
 | |
|     def new_scope(names)
 | |
|       values = names.collect { |n| self[n] }
 | |
|       self.class.new(names, values, self)
 | |
|     end
 | |
| 
 | |
|     # Find an argument value by name or index.
 | |
|     def [](index)
 | |
|       lookup(index.to_sym)
 | |
|     end
 | |
| 
 | |
|     def each(&block)
 | |
|       @hash.each(&block)
 | |
|     end
 | |
| 
 | |
|     def method_missing(sym, *args, &block)
 | |
|       lookup(sym.to_sym)
 | |
|     end
 | |
| 
 | |
|     def to_hash
 | |
|       @hash
 | |
|     end
 | |
| 
 | |
|     def to_s
 | |
|       @hash.inspect
 | |
|     end
 | |
| 
 | |
|     def inspect
 | |
|       to_s
 | |
|     end
 | |
|     
 | |
|     protected
 | |
|     
 | |
|     def lookup(name)
 | |
|       if @hash.has_key?(name)
 | |
|         @hash[name]
 | |
|       elsif ENV.has_key?(name.to_s)
 | |
|         ENV[name.to_s]
 | |
|       elsif ENV.has_key?(name.to_s.upcase)
 | |
|         ENV[name.to_s.upcase]
 | |
|       elsif @parent
 | |
|         @parent.lookup(name)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   ####################################################################
 | |
|   # InvocationChain tracks the chain of task invocations to detect
 | |
|   # circular dependencies.
 | |
|   class InvocationChain
 | |
|     def initialize(value, tail)
 | |
|       @value = value
 | |
|       @tail = tail
 | |
|     end
 | |
| 
 | |
|     def member?(obj)
 | |
|       @value == obj || @tail.member?(obj)
 | |
|     end
 | |
| 
 | |
|     def append(value)
 | |
|       if member?(value)
 | |
|         fail RuntimeError, "Circular dependency detected: #{to_s} => #{value}"
 | |
|       end
 | |
|       self.class.new(value, self)
 | |
|     end
 | |
| 
 | |
|     def to_s
 | |
|       "#{prefix}#{@value}"
 | |
|     end
 | |
| 
 | |
|     def self.append(value, chain)
 | |
|       chain.append(value)
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     def prefix
 | |
|       "#{@tail.to_s} => "
 | |
|     end
 | |
| 
 | |
|     class EmptyInvocationChain
 | |
|       def member?(obj)
 | |
|         false
 | |
|       end
 | |
|       def append(value)
 | |
|         InvocationChain.new(value, self)
 | |
|       end
 | |
|       def to_s
 | |
|         "TOP"
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     EMPTY = EmptyInvocationChain.new
 | |
| 
 | |
|   end # class InvocationChain
 | |
| 
 | |
| end # module Rake
 | |
| 
 | |
| 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
 | |
| 
 | |
|     # 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
 | |
| 
 | |
|     # 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
 | |
| 
 | |
|     # 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 = FileList[]
 | |
|       @actions = []
 | |
|       @already_invoked = false
 | |
|       @full_comment = nil
 | |
|       @comment = nil
 | |
|       @lock = Mutex.new
 | |
|       @application = app
 | |
|       @scope = app.current_scope
 | |
|       @arg_names = nil
 | |
|     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
 | |
| 
 | |
|     # Invoke the task if it is needed.  Prerequites 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)
 | |
|       new_chain = InvocationChain.append(self, invocation_chain)
 | |
|       @lock.synchronize do
 | |
|         if application.options.trace
 | |
|           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
 | |
|     end
 | |
|     protected :invoke_with_call_chain
 | |
| 
 | |
|     # Invoke all the prerequisites of a task.
 | |
|     def invoke_prerequisites(task_args, invocation_chain)
 | |
|       @prerequisites.each { |n|
 | |
|         prereq = application[n, @scope]
 | |
|         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)
 | |
|       if application.options.dryrun
 | |
|         puts "** Execute (dry run) #{name}"
 | |
|         return
 | |
|       end
 | |
|       if application.options.trace
 | |
|         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
 | |
|       @prerequisites.collect { |p| application[p].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 alread 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 = @prerequisites.collect {|name| application[name]}
 | |
|       prereqs.sort! {|a,b| a.timestamp <=> b.timestamp}
 | |
|       prereqs.each do |p|
 | |
|         result << "--#{p.name} (#{p.timestamp})\n"
 | |
|       end
 | |
|       latest_prereq = @prerequisites.collect{|n| application[n].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
 | |
| 
 | |
| 
 | |
|   # #########################################################################
 | |
|   # A FileTask is a task that includes time based dependencies.  If any of a
 | |
|   # FileTask's prerequisites have a timestamp that is later than the file
 | |
|   # represented by this task, then the file must be rebuilt (using the
 | |
|   # supplied actions).
 | |
|   #
 | |
|   class FileTask < Task
 | |
| 
 | |
|     # Is this file task needed?  Yes if it doesn't exist, or if its time stamp
 | |
|     # is out of date.
 | |
|     def needed?
 | |
|       return true unless File.exist?(name)
 | |
|       return true if out_of_date?(timestamp)
 | |
|       false
 | |
|     end
 | |
| 
 | |
|     # Time stamp for file task.
 | |
|     def timestamp
 | |
|       if File.exist?(name)
 | |
|         File.mtime(name.to_s)
 | |
|       else
 | |
|         Rake::EARLY
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     # Are there any prerequisites with a later time than the given time stamp?
 | |
|     def out_of_date?(stamp)
 | |
|       @prerequisites.any? { |n| application[n].timestamp > stamp}
 | |
|     end
 | |
| 
 | |
|     # ----------------------------------------------------------------
 | |
|     # Task class methods.
 | |
|     #
 | |
|     class << self
 | |
|       # Apply the scope to the task name according to the rules for this kind
 | |
|       # of task.  File based tasks ignore the scope when creating the name.
 | |
|       def scope_name(scope, task_name)
 | |
|         task_name
 | |
|       end
 | |
|     end
 | |
|   end # class Rake::FileTask
 | |
| 
 | |
|   # #########################################################################
 | |
|   # A FileCreationTask is a file task that when used as a dependency will be
 | |
|   # needed if and only if the file has not been created.  Once created, it is
 | |
|   # not re-triggered if any of its dependencies are newer, nor does trigger
 | |
|   # any rebuilds of tasks that depend on it whenever it is updated.
 | |
|   #
 | |
|   class FileCreationTask < FileTask
 | |
|     # Is this file task needed?  Yes if it doesn't exist.
 | |
|     def needed?
 | |
|       ! File.exist?(name)
 | |
|     end
 | |
| 
 | |
|     # Time stamp for file creation task.  This time stamp is earlier
 | |
|     # than any other time stamp.
 | |
|     def timestamp
 | |
|       Rake::EARLY
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # #########################################################################
 | |
|   # Same as a regular task, but the immediate prerequisites are done in
 | |
|   # parallel using Ruby threads.
 | |
|   #
 | |
|   class MultiTask < Task
 | |
|     def invoke_prerequisites(args, invocation_chain)
 | |
|       threads = @prerequisites.collect { |p|
 | |
|         Thread.new(p) { |r| application[r].invoke_with_call_chain(args, invocation_chain) }
 | |
|       }
 | |
|       threads.each { |t| t.join }
 | |
|     end
 | |
|   end
 | |
| end # module Rake
 | |
| 
 | |
| # ###########################################################################
 | |
| # Task Definition Functions ...
 | |
| 
 | |
| # Declare a basic task.
 | |
| #
 | |
| # Example:
 | |
| #   task :clobber => [:clean] do
 | |
| #     rm_rf "html"
 | |
| #   end
 | |
| #
 | |
| def task(*args, &block)
 | |
|   Rake::Task.define_task(*args, &block)
 | |
| end
 | |
| 
 | |
| 
 | |
| # Declare a file task.
 | |
| #
 | |
| # Example:
 | |
| #   file "config.cfg" => ["config.template"] do
 | |
| #     open("config.cfg", "w") do |outfile|
 | |
| #       open("config.template") do |infile|
 | |
| #         while line = infile.gets
 | |
| #           outfile.puts line
 | |
| #         end
 | |
| #       end
 | |
| #     end
 | |
| #  end
 | |
| #
 | |
| def file(args, &block)
 | |
|   Rake::FileTask.define_task(args, &block)
 | |
| end
 | |
| 
 | |
| # Declare a file creation task.
 | |
| # (Mainly used for the directory command).
 | |
| def file_create(args, &block)
 | |
|   Rake::FileCreationTask.define_task(args, &block)
 | |
| end
 | |
| 
 | |
| # Declare a set of files tasks to create the given directories on demand.
 | |
| #
 | |
| # Example:
 | |
| #   directory "testdata/doc"
 | |
| #
 | |
| def directory(dir)
 | |
|   Rake.each_dir_parent(dir) do |d|
 | |
|     file_create d do |t|
 | |
|       mkdir_p t.name if ! File.exist?(t.name)
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| # Declare a task that performs its prerequisites in parallel. Multitasks does
 | |
| # *not* guarantee that its prerequisites will execute in any given order
 | |
| # (which is obvious when you think about it)
 | |
| #
 | |
| # Example:
 | |
| #   multitask :deploy => [:deploy_gem, :deploy_rdoc]
 | |
| #
 | |
| def multitask(args, &block)
 | |
|   Rake::MultiTask.define_task(args, &block)
 | |
| end
 | |
| 
 | |
| # Create a new rake namespace and use it for evaluating the given block.
 | |
| # Returns a NameSpace object that can be used to lookup tasks defined in the
 | |
| # namespace.
 | |
| #
 | |
| # E.g.
 | |
| #
 | |
| #   ns = namespace "nested" do
 | |
| #     task :run
 | |
| #   end
 | |
| #   task_run = ns[:run] # find :run in the given namespace.
 | |
| #
 | |
| def namespace(name=nil, &block)
 | |
|   Rake.application.in_namespace(name, &block)
 | |
| end
 | |
| 
 | |
| # Declare a rule for auto-tasks.
 | |
| #
 | |
| # Example:
 | |
| #  rule '.o' => '.c' do |t|
 | |
| #    sh %{cc -o #{t.name} #{t.source}}
 | |
| #  end
 | |
| #
 | |
| def rule(*args, &block)
 | |
|   Rake::Task.create_rule(*args, &block)
 | |
| end
 | |
| 
 | |
| # Describe the next rake task.
 | |
| #
 | |
| # Example:
 | |
| #   desc "Run the Unit Tests"
 | |
| #   task :test => [:build]
 | |
| #     runtests
 | |
| #   end
 | |
| #
 | |
| def desc(description)
 | |
|   Rake.application.last_description = description
 | |
| end
 | |
| 
 | |
| # Import the partial Rakefiles +fn+.  Imported files are loaded _after_ the
 | |
| # current file is completely loaded.  This allows the import statement to
 | |
| # appear anywhere in the importing file, and yet allowing the imported files
 | |
| # to depend on objects defined in the importing file.
 | |
| #
 | |
| # A common use of the import statement is to include files containing
 | |
| # dependency declarations.
 | |
| #
 | |
| # See also the --rakelibdir command line option.
 | |
| #
 | |
| # Example:
 | |
| #   import ".depend", "my_rules"
 | |
| #
 | |
| def import(*fns)
 | |
|   fns.each do |fn|
 | |
|     Rake.application.add_import(fn)
 | |
|   end
 | |
| end
 | |
| 
 | |
| # ###########################################################################
 | |
| # This a FileUtils extension that defines several additional commands to be
 | |
| # added to the FileUtils utility functions.
 | |
| #
 | |
| module FileUtils
 | |
|   RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
 | |
| 
 | |
|   OPT_TABLE['sh']  = %w(noop verbose)
 | |
|   OPT_TABLE['ruby'] = %w(noop verbose)
 | |
| 
 | |
|   # Run the system command +cmd+. If multiple arguments are given the command
 | |
|   # is not run with the shell (same semantics as Kernel::exec and
 | |
|   # Kernel::system).
 | |
|   #
 | |
|   # Example:
 | |
|   #   sh %{ls -ltr}
 | |
|   #
 | |
|   #   sh 'ls', 'file with spaces'
 | |
|   #
 | |
|   #   # check exit status after command runs
 | |
|   #   sh %{grep pattern file} do |ok, res|
 | |
|   #     if ! ok
 | |
|   #       puts "pattern not found (status = #{res.exitstatus})"
 | |
|   #     end
 | |
|   #   end
 | |
|   #
 | |
|   def sh(*cmd, &block)
 | |
|     options = (Hash === cmd.last) ? cmd.pop : {}
 | |
|     unless block_given?
 | |
|       show_command = cmd.join(" ")
 | |
|       show_command = show_command[0,42] + "..."
 | |
|       # TODO code application logic heref show_command.length > 45
 | |
|       block = lambda { |ok, status|
 | |
|         ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]"
 | |
|       }
 | |
|     end
 | |
|     rake_check_options options, :noop, :verbose
 | |
|     rake_output_message cmd.join(" ") if options[:verbose]
 | |
|     unless options[:noop]
 | |
|       res = system(*cmd)
 | |
|       block.call(res, $?)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # Run a Ruby interpreter with the given arguments.
 | |
|   #
 | |
|   # Example:
 | |
|   #   ruby %{-pe '$_.upcase!' <README}
 | |
|   #
 | |
|   def ruby(*args,&block)
 | |
|     options = (Hash === args.last) ? args.pop : {}
 | |
|     if args.length > 1 then
 | |
|       sh(*([RUBY] + args + [options]), &block)
 | |
|     else
 | |
|       sh("#{RUBY} #{args.first}", options, &block)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   LN_SUPPORTED = [true]
 | |
| 
 | |
|   #  Attempt to do a normal file link, but fall back to a copy if the link
 | |
|   #  fails.
 | |
|   def safe_ln(*args)
 | |
|     unless LN_SUPPORTED[0]
 | |
|       cp(*args)
 | |
|     else
 | |
|       begin
 | |
|         ln(*args)
 | |
|       rescue StandardError, NotImplementedError => ex
 | |
|         LN_SUPPORTED[0] = false
 | |
|         cp(*args)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # Split a file path into individual directory names.
 | |
|   #
 | |
|   # Example:
 | |
|   #   split_all("a/b/c") =>  ['a', 'b', 'c']
 | |
|   #
 | |
|   def split_all(path)
 | |
|     head, tail = File.split(path)
 | |
|     return [tail] if head == '.' || tail == '/'
 | |
|     return [head, tail] if head == '/'
 | |
|     return split_all(head) + [tail]
 | |
|   end
 | |
| end
 | |
| 
 | |
| # ###########################################################################
 | |
| # RakeFileUtils provides a custom version of the FileUtils methods that
 | |
| # respond to the <tt>verbose</tt> and <tt>nowrite</tt> commands.
 | |
| #
 | |
| module RakeFileUtils
 | |
|   include FileUtils
 | |
| 
 | |
|   class << self
 | |
|     attr_accessor :verbose_flag, :nowrite_flag
 | |
|   end
 | |
|   RakeFileUtils.verbose_flag = true
 | |
|   RakeFileUtils.nowrite_flag = false
 | |
| 
 | |
|   $fileutils_verbose = true
 | |
|   $fileutils_nowrite = false
 | |
| 
 | |
|   FileUtils::OPT_TABLE.each do |name, opts|
 | |
|     default_options = []
 | |
|     if opts.include?('verbose')
 | |
|       default_options << ':verbose => RakeFileUtils.verbose_flag'
 | |
|     end
 | |
|     if opts.include?('noop')
 | |
|       default_options << ':noop => RakeFileUtils.nowrite_flag'
 | |
|     end
 | |
| 
 | |
|     next if default_options.empty?
 | |
|     module_eval(<<-EOS, __FILE__, __LINE__ + 1)
 | |
|     def #{name}( *args, &block )
 | |
|       super(
 | |
|         *rake_merge_option(args,
 | |
|           #{default_options.join(', ')}
 | |
|           ), &block)
 | |
|     end
 | |
|     EOS
 | |
|   end
 | |
| 
 | |
|   # Get/set the verbose flag controlling output from the FileUtils utilities.
 | |
|   # If verbose is true, then the utility method is echoed to standard output.
 | |
|   #
 | |
|   # Examples:
 | |
|   #    verbose              # return the current value of the verbose flag
 | |
|   #    verbose(v)           # set the verbose flag to _v_.
 | |
|   #    verbose(v) { code }  # Execute code with the verbose flag set temporarily to _v_.
 | |
|   #                         # Return to the original value when code is done.
 | |
|   def verbose(value=nil)
 | |
|     oldvalue = RakeFileUtils.verbose_flag
 | |
|     RakeFileUtils.verbose_flag = value unless value.nil?
 | |
|     if block_given?
 | |
|       begin
 | |
|         yield
 | |
|       ensure
 | |
|         RakeFileUtils.verbose_flag = oldvalue
 | |
|       end
 | |
|     end
 | |
|     RakeFileUtils.verbose_flag
 | |
|   end
 | |
| 
 | |
|   # Get/set the nowrite flag controlling output from the FileUtils utilities.
 | |
|   # If verbose is true, then the utility method is echoed to standard output.
 | |
|   #
 | |
|   # Examples:
 | |
|   #    nowrite              # return the current value of the nowrite flag
 | |
|   #    nowrite(v)           # set the nowrite flag to _v_.
 | |
|   #    nowrite(v) { code }  # Execute code with the nowrite flag set temporarily to _v_.
 | |
|   #                         # Return to the original value when code is done.
 | |
|   def nowrite(value=nil)
 | |
|     oldvalue = RakeFileUtils.nowrite_flag
 | |
|     RakeFileUtils.nowrite_flag = value unless value.nil?
 | |
|     if block_given?
 | |
|       begin
 | |
|         yield
 | |
|       ensure
 | |
|         RakeFileUtils.nowrite_flag = oldvalue
 | |
|       end
 | |
|     end
 | |
|     oldvalue
 | |
|   end
 | |
| 
 | |
|   # Use this function to prevent potentially destructive ruby code from
 | |
|   # running when the :nowrite flag is set.
 | |
|   #
 | |
|   # Example:
 | |
|   #
 | |
|   #   when_writing("Building Project") do
 | |
|   #     project.build
 | |
|   #   end
 | |
|   #
 | |
|   # The following code will build the project under normal conditions. If the
 | |
|   # nowrite(true) flag is set, then the example will print:
 | |
|   #      DRYRUN: Building Project
 | |
|   # instead of actually building the project.
 | |
|   #
 | |
|   def when_writing(msg=nil)
 | |
|     if RakeFileUtils.nowrite_flag
 | |
|       puts "DRYRUN: #{msg}" if msg
 | |
|     else
 | |
|       yield
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # Merge the given options with the default values.
 | |
|   def rake_merge_option(args, defaults)
 | |
|     if Hash === args.last
 | |
|       defaults.update(args.last)
 | |
|       args.pop
 | |
|     end
 | |
|     args.push defaults
 | |
|     args
 | |
|   end
 | |
|   private :rake_merge_option
 | |
| 
 | |
|   # Send the message to the default rake output (which is $stderr).
 | |
|   def rake_output_message(message)
 | |
|     $stderr.puts(message)
 | |
|   end
 | |
|   private :rake_output_message
 | |
| 
 | |
|   # Check that the options do not contain options not listed in +optdecl+.  An
 | |
|   # ArgumentError exception is thrown if non-declared options are found.
 | |
|   def rake_check_options(options, *optdecl)
 | |
|     h = options.dup
 | |
|     optdecl.each do |name|
 | |
|       h.delete name
 | |
|     end
 | |
|     raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
 | |
|   end
 | |
|   private :rake_check_options
 | |
| 
 | |
|   extend self
 | |
| end
 | |
| 
 | |
| # ###########################################################################
 | |
| # Include the FileUtils file manipulation functions in the top level module,
 | |
| # but mark them private so that they don't unintentionally define methods on
 | |
| # other objects.
 | |
| 
 | |
| include RakeFileUtils
 | |
| private(*FileUtils.instance_methods(false))
 | |
| private(*RakeFileUtils.instance_methods(false))
 | |
| 
 | |
| ######################################################################
 | |
| module Rake
 | |
| 
 | |
|   class RuleRecursionOverflowError < StandardError
 | |
|     def initialize(*args)
 | |
|       super
 | |
|       @targets = []
 | |
|     end
 | |
| 
 | |
|     def add_target(target)
 | |
|       @targets << target
 | |
|     end
 | |
| 
 | |
|     def message
 | |
|       super + ": [" + @targets.reverse.join(' => ') + "]"
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # #########################################################################
 | |
|   # A FileList is essentially an array with a few helper methods defined to
 | |
|   # make file manipulation a bit easier.
 | |
|   #
 | |
|   # FileLists are lazy.  When given a list of glob patterns for possible files
 | |
|   # to be included in the file list, instead of searching the file structures
 | |
|   # to find the files, a FileList holds the pattern for latter use.
 | |
|   #
 | |
|   # This allows us to define a number of FileList to match any number of
 | |
|   # files, but only search out the actual files when then FileList itself is
 | |
|   # actually used.  The key is that the first time an element of the
 | |
|   # FileList/Array is requested, the pending patterns are resolved into a real
 | |
|   # list of file names.
 | |
|   #
 | |
|   class FileList
 | |
| 
 | |
|     include Cloneable
 | |
| 
 | |
|     # == Method Delegation
 | |
|     #
 | |
|     # The lazy evaluation magic of FileLists happens by implementing all the
 | |
|     # array specific methods to call +resolve+ before delegating the heavy
 | |
|     # lifting to an embedded array object (@items).
 | |
|     #
 | |
|     # In addition, there are two kinds of delegation calls.  The regular kind
 | |
|     # delegates to the @items array and returns the result directly.  Well,
 | |
|     # almost directly.  It checks if the returned value is the @items object
 | |
|     # itself, and if so will return the FileList object instead.
 | |
|     #
 | |
|     # The second kind of delegation call is used in methods that normally
 | |
|     # return a new Array object.  We want to capture the return value of these
 | |
|     # methods and wrap them in a new FileList object.  We enumerate these
 | |
|     # methods in the +SPECIAL_RETURN+ list below.
 | |
| 
 | |
|     # List of array methods (that are not in +Object+) that need to be
 | |
|     # delegated.
 | |
|     ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
 | |
| 
 | |
|     # List of additional methods that must be delegated.
 | |
|     MUST_DEFINE = %w[to_a inspect]
 | |
| 
 | |
|     # List of methods that should not be delegated here (we define special
 | |
|     # versions of them explicitly below).
 | |
|     MUST_NOT_DEFINE = %w[to_a to_ary partition *]
 | |
| 
 | |
|     # List of delegated methods that return new array values which need
 | |
|     # wrapping.
 | |
|     SPECIAL_RETURN = %w[
 | |
|       map collect sort sort_by select find_all reject grep
 | |
|       compact flatten uniq values_at
 | |
|       + - & |
 | |
|     ]
 | |
| 
 | |
|     DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).collect{ |s| s.to_s }.sort.uniq
 | |
| 
 | |
|     # Now do the delegation.
 | |
|     DELEGATING_METHODS.each_with_index do |sym, i|
 | |
|       if SPECIAL_RETURN.include?(sym)
 | |
|         ln = __LINE__+1
 | |
|         class_eval %{
 | |
|           def #{sym}(*args, &block)
 | |
|             resolve
 | |
|             result = @items.send(:#{sym}, *args, &block)
 | |
|             FileList.new.import(result)
 | |
|           end
 | |
|         }, __FILE__, ln
 | |
|       else
 | |
|         ln = __LINE__+1
 | |
|         class_eval %{
 | |
|           def #{sym}(*args, &block)
 | |
|             resolve
 | |
|             result = @items.send(:#{sym}, *args, &block)
 | |
|             result.object_id == @items.object_id ? self : result
 | |
|           end
 | |
|         }, __FILE__, ln
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Create a file list from the globbable patterns given.  If you wish to
 | |
|     # perform multiple includes or excludes at object build time, use the
 | |
|     # "yield self" pattern.
 | |
|     #
 | |
|     # Example:
 | |
|     #   file_list = FileList.new('lib/**/*.rb', 'test/test*.rb')
 | |
|     #
 | |
|     #   pkg_files = FileList.new('lib/**/*') do |fl|
 | |
|     #     fl.exclude(/\bCVS\b/)
 | |
|     #   end
 | |
|     #
 | |
|     def initialize(*patterns)
 | |
|       @pending_add = []
 | |
|       @pending = false
 | |
|       @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
 | |
|       @exclude_procs = DEFAULT_IGNORE_PROCS.dup
 | |
|       @exclude_re = nil
 | |
|       @items = []
 | |
|       patterns.each { |pattern| include(pattern) }
 | |
|       yield self if block_given?
 | |
|     end
 | |
| 
 | |
|     # Add file names defined by glob patterns to the file list.  If an array
 | |
|     # is given, add each element of the array.
 | |
|     #
 | |
|     # Example:
 | |
|     #   file_list.include("*.java", "*.cfg")
 | |
|     #   file_list.include %w( math.c lib.h *.o )
 | |
|     #
 | |
|     def include(*filenames)
 | |
|       # TODO: check for pending
 | |
|       filenames.each do |fn|
 | |
|         if fn.respond_to? :to_ary
 | |
|           include(*fn.to_ary)
 | |
|         else
 | |
|           @pending_add << fn
 | |
|         end
 | |
|       end
 | |
|       @pending = true
 | |
|       self
 | |
|     end
 | |
|     alias :add :include
 | |
| 
 | |
|     # Register a list of file name patterns that should be excluded from the
 | |
|     # list.  Patterns may be regular expressions, glob patterns or regular
 | |
|     # strings.  In addition, a block given to exclude will remove entries that
 | |
|     # return true when given to the block.
 | |
|     #
 | |
|     # Note that glob patterns are expanded against the file system. If a file
 | |
|     # is explicitly added to a file list, but does not exist in the file
 | |
|     # system, then an glob pattern in the exclude list will not exclude the
 | |
|     # file.
 | |
|     #
 | |
|     # Examples:
 | |
|     #   FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
 | |
|     #   FileList['a.c', 'b.c'].exclude(/^a/)  => ['b.c']
 | |
|     #
 | |
|     # If "a.c" is a file, then ...
 | |
|     #   FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
 | |
|     #
 | |
|     # If "a.c" is not a file, then ...
 | |
|     #   FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
 | |
|     #
 | |
|     def exclude(*patterns, &block)
 | |
|       patterns.each do |pat|
 | |
|         @exclude_patterns << pat
 | |
|       end
 | |
|       if block_given?
 | |
|         @exclude_procs << block
 | |
|       end
 | |
|       resolve_exclude if ! @pending
 | |
|       self
 | |
|     end
 | |
| 
 | |
| 
 | |
|     # Clear all the exclude patterns so that we exclude nothing.
 | |
|     def clear_exclude
 | |
|       @exclude_patterns = []
 | |
|       @exclude_procs = []
 | |
|       calculate_exclude_regexp if ! @pending
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     # Define equality.
 | |
|     def ==(array)
 | |
|       to_ary == array
 | |
|     end
 | |
| 
 | |
|     # Return the internal array object.
 | |
|     def to_a
 | |
|       resolve
 | |
|       @items
 | |
|     end
 | |
| 
 | |
|     # Return the internal array object.
 | |
|     def to_ary
 | |
|       to_a
 | |
|     end
 | |
| 
 | |
|     # Lie about our class.
 | |
|     def is_a?(klass)
 | |
|       klass == Array || super(klass)
 | |
|     end
 | |
|     alias kind_of? is_a?
 | |
| 
 | |
|     # Redefine * to return either a string or a new file list.
 | |
|     def *(other)
 | |
|       result = @items * other
 | |
|       case result
 | |
|       when Array
 | |
|         FileList.new.import(result)
 | |
|       else
 | |
|         result
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Resolve all the pending adds now.
 | |
|     def resolve
 | |
|       if @pending
 | |
|         @pending = false
 | |
|         @pending_add.each do |fn| resolve_add(fn) end
 | |
|         @pending_add = []
 | |
|         resolve_exclude
 | |
|       end
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     def calculate_exclude_regexp
 | |
|       ignores = []
 | |
|       @exclude_patterns.each do |pat|
 | |
|         case pat
 | |
|         when Regexp
 | |
|           ignores << pat
 | |
|         when /[*?]/
 | |
|           Dir[pat].each do |p| ignores << p end
 | |
|         else
 | |
|           ignores << Regexp.quote(pat)
 | |
|         end
 | |
|       end
 | |
|       if ignores.empty?
 | |
|         @exclude_re = /^$/
 | |
|       else
 | |
|         re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
 | |
|         @exclude_re = Regexp.new(re_str)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def resolve_add(fn)
 | |
|       case fn
 | |
|       when %r{[*?\[\{]}
 | |
|         add_matching(fn)
 | |
|       else
 | |
|         self << fn
 | |
|       end
 | |
|     end
 | |
|     private :resolve_add
 | |
| 
 | |
|     def resolve_exclude
 | |
|       calculate_exclude_regexp
 | |
|       reject! { |fn| exclude?(fn) }
 | |
|       self
 | |
|     end
 | |
|     private :resolve_exclude
 | |
| 
 | |
|     # Return a new FileList with the results of running +sub+ against each
 | |
|     # element of the original list.
 | |
|     #
 | |
|     # Example:
 | |
|     #   FileList['a.c', 'b.c'].sub(/\.c$/, '.o')  => ['a.o', 'b.o']
 | |
|     #
 | |
|     def sub(pat, rep)
 | |
|       inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
 | |
|     end
 | |
| 
 | |
|     # Return a new FileList with the results of running +gsub+ against each
 | |
|     # element of the original list.
 | |
|     #
 | |
|     # Example:
 | |
|     #   FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
 | |
|     #      => ['lib\\test\\file', 'x\\y']
 | |
|     #
 | |
|     def gsub(pat, rep)
 | |
|       inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) }
 | |
|     end
 | |
| 
 | |
|     # Same as +sub+ except that the oringal file list is modified.
 | |
|     def sub!(pat, rep)
 | |
|       each_with_index { |fn, i| self[i] = fn.sub(pat,rep) }
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     # Same as +gsub+ except that the original file list is modified.
 | |
|     def gsub!(pat, rep)
 | |
|       each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) }
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     # Apply the pathmap spec to each of the included file names, returning a
 | |
|     # new file list with the modified paths.  (See String#pathmap for
 | |
|     # details.)
 | |
|     def pathmap(spec=nil)
 | |
|       collect { |fn| fn.pathmap(spec) }
 | |
|     end
 | |
| 
 | |
|     # Return a new array with <tt>String#ext</tt> method applied to each
 | |
|     # member of the array.
 | |
|     #
 | |
|     # This method is a shortcut for:
 | |
|     #
 | |
|     #    array.collect { |item| item.ext(newext) }
 | |
|     #
 | |
|     # +ext+ is a user added method for the Array class.
 | |
|     def ext(newext='')
 | |
|       collect { |fn| fn.ext(newext) }
 | |
|     end
 | |
| 
 | |
| 
 | |
|     # Grep each of the files in the filelist using the given pattern. If a
 | |
|     # block is given, call the block on each matching line, passing the file
 | |
|     # name, line number, and the matching line of text.  If no block is given,
 | |
|     # a standard emac style file:linenumber:line message will be printed to
 | |
|     # standard out.
 | |
|     def egrep(pattern)
 | |
|       each do |fn|
 | |
|         open(fn) do |inf|
 | |
|           count = 0
 | |
|           inf.each do |line|
 | |
|             count += 1
 | |
|             if pattern.match(line)
 | |
|               if block_given?
 | |
|                 yield fn, count, line
 | |
|               else
 | |
|                 puts "#{fn}:#{count}:#{line}"
 | |
|               end
 | |
|             end
 | |
|           end
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Return a new file list that only contains file names from the current
 | |
|     # file list that exist on the file system.
 | |
|     def existing
 | |
|       select { |fn| File.exist?(fn) }
 | |
|     end
 | |
| 
 | |
|     # Modify the current file list so that it contains only file name that
 | |
|     # exist on the file system.
 | |
|     def existing!
 | |
|       resolve
 | |
|       @items = @items.select { |fn| File.exist?(fn) }
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     # FileList version of partition.  Needed because the nested arrays should
 | |
|     # be FileLists in this version.
 | |
|     def partition(&block)       # :nodoc:
 | |
|       resolve
 | |
|       result = @items.partition(&block)
 | |
|       [
 | |
|         FileList.new.import(result[0]),
 | |
|         FileList.new.import(result[1]),
 | |
|       ]
 | |
|     end
 | |
| 
 | |
|     # Convert a FileList to a string by joining all elements with a space.
 | |
|     def to_s
 | |
|       resolve
 | |
|       self.join(' ')
 | |
|     end
 | |
| 
 | |
|     # Add matching glob patterns.
 | |
|     def add_matching(pattern)
 | |
|       Dir[pattern].each do |fn|
 | |
|         self << fn unless exclude?(fn)
 | |
|       end
 | |
|     end
 | |
|     private :add_matching
 | |
| 
 | |
|     # Should the given file name be excluded?
 | |
|     def exclude?(fn)
 | |
|       calculate_exclude_regexp unless @exclude_re
 | |
|       fn =~ @exclude_re || @exclude_procs.any? { |p| p.call(fn) }
 | |
|     end
 | |
| 
 | |
|     DEFAULT_IGNORE_PATTERNS = [
 | |
|       /(^|[\/\\])CVS([\/\\]|$)/,
 | |
|       /(^|[\/\\])\.svn([\/\\]|$)/,
 | |
|       /\.bak$/,
 | |
|       /~$/
 | |
|     ]
 | |
|     DEFAULT_IGNORE_PROCS = [
 | |
|       proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) }
 | |
|     ]
 | |
| #    @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
 | |
| 
 | |
|     def import(array)
 | |
|       @items = array
 | |
|       self
 | |
|     end
 | |
| 
 | |
|     class << self
 | |
|       # Create a new file list including the files listed. Similar to:
 | |
|       #
 | |
|       #   FileList.new(*args)
 | |
|       def [](*args)
 | |
|         new(*args)
 | |
|       end
 | |
|     end
 | |
|   end # FileList
 | |
| end
 | |
| 
 | |
| module Rake
 | |
|   class << self
 | |
| 
 | |
|     # Yield each file or directory component.
 | |
|     def each_dir_parent(dir)
 | |
|       old_length = nil
 | |
|       while dir != '.' && dir.length != old_length
 | |
|         yield(dir)
 | |
|         old_length = dir.length
 | |
|         dir = File.dirname(dir)
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end # module Rake
 | |
| 
 | |
| # Alias FileList to be available at the top level.
 | |
| FileList = Rake::FileList
 | |
| 
 | |
| # ###########################################################################
 | |
| module Rake
 | |
| 
 | |
|   # Default Rakefile loader used by +import+.
 | |
|   class DefaultLoader
 | |
|     def load(fn)
 | |
|       Kernel.load(File.expand_path(fn))
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   # EarlyTime is a fake timestamp that occurs _before_ any other time value.
 | |
|   class EarlyTime
 | |
|     include Comparable
 | |
|     include Singleton
 | |
| 
 | |
|     def <=>(other)
 | |
|       -1
 | |
|     end
 | |
| 
 | |
|     def to_s
 | |
|       "<EARLY TIME>"
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   EARLY = EarlyTime.instance
 | |
| end # module Rake
 | |
| 
 | |
| # ###########################################################################
 | |
| # Extensions to time to allow comparisons with an early time class.
 | |
| #
 | |
| class Time
 | |
|   alias rake_original_time_compare :<=>
 | |
|   def <=>(other)
 | |
|     if Rake::EarlyTime === other
 | |
|       - other.<=>(self)
 | |
|     else
 | |
|       rake_original_time_compare(other)
 | |
|     end
 | |
|   end
 | |
| end # class Time
 | |
| 
 | |
| module Rake
 | |
| 
 | |
|   ####################################################################
 | |
|   # The NameSpace class will lookup task names in the the scope
 | |
|   # defined by a +namespace+ command.
 | |
|   #
 | |
|   class NameSpace
 | |
| 
 | |
|     # Create a namespace lookup object using the given task manager
 | |
|     # and the list of scopes.
 | |
|     def initialize(task_manager, scope_list)
 | |
|       @task_manager = task_manager
 | |
|       @scope = scope_list.dup
 | |
|     end
 | |
| 
 | |
|     # Lookup a task named +name+ in the namespace.
 | |
|     def [](name)
 | |
|       @task_manager.lookup(name, @scope)
 | |
|     end
 | |
| 
 | |
|     # Return the list of tasks defined in this namespace.
 | |
|     def tasks
 | |
|       @task_manager.tasks
 | |
|     end
 | |
|   end # NameSpace
 | |
| 
 | |
| 
 | |
|   ####################################################################
 | |
|   # The TaskManager module is a mixin for managing tasks.
 | |
|   module TaskManager
 | |
|     # Track the last comment made in the Rakefile.
 | |
|     attr_accessor :last_description
 | |
|     alias :last_comment :last_description    # Backwards compatibility
 | |
| 
 | |
|     def initialize
 | |
|       super
 | |
|       @tasks = Hash.new
 | |
|       @rules = Array.new
 | |
|       @scope = Array.new
 | |
|       @last_description = nil
 | |
|     end
 | |
| 
 | |
|     def create_rule(*args, &block)
 | |
|       pattern, arg_names, deps = resolve_args(args)
 | |
|       pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern
 | |
|       @rules << [pattern, deps, block]
 | |
|     end
 | |
| 
 | |
|     def define_task(task_class, *args, &block)
 | |
|       task_name, arg_names, deps = resolve_args(args)
 | |
|       task_name = task_class.scope_name(@scope, task_name)
 | |
|       deps = [deps] unless deps.respond_to?(:to_ary)
 | |
|       deps = deps.collect {|d| d.to_s }
 | |
|       task = intern(task_class, task_name)
 | |
|       task.set_arg_names(arg_names) unless arg_names.empty?
 | |
|       task.add_description(@last_description)
 | |
|       @last_description = nil
 | |
|       task.enhance(deps, &block)
 | |
|       task
 | |
|     end
 | |
| 
 | |
|     # Lookup a task.  Return an existing task if found, otherwise
 | |
|     # create a task of the current type.
 | |
|     def intern(task_class, task_name)
 | |
|       @tasks[task_name.to_s] ||= task_class.new(task_name, self)
 | |
|     end
 | |
| 
 | |
|     # Find a matching task for +task_name+.
 | |
|     def [](task_name, scopes=nil)
 | |
|       task_name = task_name.to_s
 | |
|       self.lookup(task_name, scopes) or
 | |
|         enhance_with_matching_rule(task_name) or
 | |
|         synthesize_file_task(task_name) or
 | |
|         fail "Don't know how to build task '#{task_name}'"
 | |
|     end
 | |
| 
 | |
|     def synthesize_file_task(task_name)
 | |
|       return nil unless File.exist?(task_name)
 | |
|       define_task(Rake::FileTask, task_name)
 | |
|     end
 | |
| 
 | |
|     # Resolve the arguments for a task/rule.  Returns a triplet of
 | |
|     # [task_name, arg_name_list, prerequisites].
 | |
|     def resolve_args(args)
 | |
|       task_name = args.shift
 | |
|       arg_names = args #.map { |a| a.to_sym }
 | |
|       needs = []
 | |
|       if task_name.is_a?(Hash)
 | |
|         hash = task_name
 | |
|         task_name = hash.keys[0]
 | |
|         needs = hash[task_name]
 | |
|       end
 | |
|       if arg_names.last.is_a?(Hash)
 | |
|         hash = arg_names.pop
 | |
|         needs = hash[:needs]
 | |
|         fail "Unrecognized keys in task hash: #{hash.keys.inspect}" if hash.size > 1
 | |
|       end
 | |
|       needs = [needs] unless needs.respond_to?(:to_ary)
 | |
|       [task_name, arg_names, needs]
 | |
|     end
 | |
| 
 | |
|     # If a rule can be found that matches the task name, enhance the
 | |
|     # task with the prerequisites and actions from the rule.  Set the
 | |
|     # source attribute of the task appropriately for the rule.  Return
 | |
|     # the enhanced task or nil of no rule was found.
 | |
|     def enhance_with_matching_rule(task_name, level=0)
 | |
|       fail Rake::RuleRecursionOverflowError,
 | |
|         "Rule Recursion Too Deep" if level >= 16
 | |
|       @rules.each do |pattern, extensions, block|
 | |
|         if md = pattern.match(task_name)
 | |
|           task = attempt_rule(task_name, extensions, block, level)
 | |
|           return task if task
 | |
|         end
 | |
|       end
 | |
|       nil
 | |
|     rescue Rake::RuleRecursionOverflowError => ex
 | |
|       ex.add_target(task_name)
 | |
|       fail ex
 | |
|     end
 | |
| 
 | |
|     # List of all defined tasks in this application.
 | |
|     def tasks
 | |
|       @tasks.values.sort_by { |t| t.name }
 | |
|     end
 | |
| 
 | |
|     # Clear all tasks in this application.
 | |
|     def clear
 | |
|       @tasks.clear
 | |
|       @rules.clear
 | |
|     end
 | |
| 
 | |
|     # Lookup a task, using scope and the scope hints in the task name.
 | |
|     # This method performs straight lookups without trying to
 | |
|     # synthesize file tasks or rules.  Special scope names (e.g. '^')
 | |
|     # are recognized.  If no scope argument is supplied, use the
 | |
|     # current scope.  Return nil if the task cannot be found.
 | |
|     def lookup(task_name, initial_scope=nil)
 | |
|       initial_scope ||= @scope
 | |
|       task_name = task_name.to_s
 | |
|       if task_name =~ /^rake:/
 | |
|         scopes = []
 | |
|         task_name = task_name.sub(/^rake:/, '')
 | |
|       elsif task_name =~ /^(\^+)/
 | |
|         scopes = initial_scope[0, initial_scope.size - $1.size]
 | |
|         task_name = task_name.sub(/^(\^+)/, '')
 | |
|       else
 | |
|         scopes = initial_scope
 | |
|       end
 | |
|       lookup_in_scope(task_name, scopes)
 | |
|     end
 | |
| 
 | |
|     # Lookup the task name
 | |
|     def lookup_in_scope(name, scope)
 | |
|       n = scope.size
 | |
|       while n >= 0
 | |
|         tn = (scope[0,n] + [name]).join(':')
 | |
|         task = @tasks[tn]
 | |
|         return task if task
 | |
|         n -= 1
 | |
|       end
 | |
|       nil
 | |
|     end
 | |
|     private :lookup_in_scope
 | |
| 
 | |
|     # Return the list of scope names currently active in the task
 | |
|     # manager.
 | |
|     def current_scope
 | |
|       @scope.dup
 | |
|     end
 | |
| 
 | |
|     # Evaluate the block in a nested namespace named +name+.  Create
 | |
|     # an anonymous namespace if +name+ is nil.
 | |
|     def in_namespace(name)
 | |
|       name ||= generate_name
 | |
|       @scope.push(name)
 | |
|       ns = NameSpace.new(self, @scope)
 | |
|       yield(ns)
 | |
|       ns
 | |
|     ensure
 | |
|       @scope.pop
 | |
|     end
 | |
| 
 | |
|     private
 | |
| 
 | |
|     # Generate an anonymous namespace name.
 | |
|     def generate_name
 | |
|       @seed ||= 0
 | |
|       @seed += 1
 | |
|       "_anon_#{@seed}"
 | |
|     end
 | |
| 
 | |
|     # Attempt to create a rule given the list of prerequisites.
 | |
|     def attempt_rule(task_name, extensions, block, level)
 | |
|       sources = make_sources(task_name, extensions)
 | |
|       prereqs = sources.collect { |source|
 | |
|         if File.exist?(source) || Rake::Task.task_defined?(source)
 | |
|           source
 | |
|         elsif parent = enhance_with_matching_rule(sources.first, level+1)
 | |
|           parent.name
 | |
|         else
 | |
|           return nil
 | |
|         end
 | |
|       }
 | |
|       task = FileTask.define_task({task_name => prereqs}, &block)
 | |
|       task.sources = prereqs
 | |
|       task
 | |
|     end
 | |
| 
 | |
|     # Make a list of sources from the list of file name extensions /
 | |
|     # translation procs.
 | |
|     def make_sources(task_name, extensions)
 | |
|       extensions.collect { |ext|
 | |
|         case ext
 | |
|         when /%/
 | |
|           task_name.pathmap(ext)
 | |
|         when %r{/}
 | |
|           ext
 | |
|         when /^\./
 | |
|           task_name.ext(ext)
 | |
|         when String
 | |
|           ext
 | |
|         when Proc
 | |
|           if ext.arity == 1
 | |
|             ext.call(task_name)
 | |
|           else
 | |
|             ext.call
 | |
|           end
 | |
|         else
 | |
|           fail "Don't know how to handle rule dependent: #{ext.inspect}"
 | |
|         end
 | |
|       }.flatten
 | |
|     end
 | |
| 
 | |
|   end # TaskManager
 | |
| 
 | |
|   ######################################################################
 | |
|   # Rake main application object.  When invoking +rake+ from the
 | |
|   # command line, a Rake::Application object is created and run.
 | |
|   #
 | |
|   class Application
 | |
|     include TaskManager
 | |
| 
 | |
|     # The name of the application (typically 'rake')
 | |
|     attr_reader :name
 | |
| 
 | |
|     # The original directory where rake was invoked.
 | |
|     attr_reader :original_dir
 | |
| 
 | |
|     # Name of the actual rakefile used.
 | |
|     attr_reader :rakefile
 | |
| 
 | |
|     # List of the top level task names (task names from the command line).
 | |
|     attr_reader :top_level_tasks
 | |
| 
 | |
|     DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze
 | |
| 
 | |
|     OPTIONS = [     # :nodoc:
 | |
|       ['--classic-namespace', '-C', GetoptLong::NO_ARGUMENT,
 | |
|         "Put Task and FileTask in the top level namespace"],
 | |
|       ['--describe',  '-D', GetoptLong::OPTIONAL_ARGUMENT,
 | |
|         "Describe the tasks (matching optional PATTERN), then exit."],
 | |
|       ['--rakefile', '-f', GetoptLong::OPTIONAL_ARGUMENT,
 | |
|         "Use FILE as the rakefile."],
 | |
|       ['--help',     '-h', '-H', GetoptLong::NO_ARGUMENT,
 | |
|         "Display this help message."],
 | |
|       ['--libdir',   '-I', GetoptLong::REQUIRED_ARGUMENT,
 | |
|         "Include LIBDIR in the search path for required modules."],
 | |
|       ['--dry-run',  '-n', GetoptLong::NO_ARGUMENT,
 | |
|         "Do a dry run without executing actions."],
 | |
|       ['--nosearch', '-N', GetoptLong::NO_ARGUMENT,
 | |
|         "Do not search parent directories for the Rakefile."],
 | |
|       ['--prereqs',  '-P', GetoptLong::NO_ARGUMENT,
 | |
|         "Display the tasks and dependencies, then exit."],
 | |
|       ['--quiet',    '-q', GetoptLong::NO_ARGUMENT,
 | |
|         "Do not log messages to standard output."],
 | |
|       ['--require',  '-r', GetoptLong::REQUIRED_ARGUMENT,
 | |
|         "Require MODULE before executing rakefile."],
 | |
|       ['--rakelibdir', '-R', GetoptLong::REQUIRED_ARGUMENT,
 | |
|         "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')"],
 | |
|       ['--silent',   '-s', GetoptLong::NO_ARGUMENT,
 | |
|         "Like --quiet, but also suppresses the 'in directory' announcement."],
 | |
|       ['--tasks',    '-T', GetoptLong::OPTIONAL_ARGUMENT,
 | |
|         "Display the tasks (matching optional PATTERN) with descriptions, then exit."],
 | |
|       ['--trace',    '-t', GetoptLong::NO_ARGUMENT,
 | |
|         "Turn on invoke/execute tracing, enable full backtrace."],
 | |
|       ['--verbose',  '-v', GetoptLong::NO_ARGUMENT,
 | |
|         "Log message to standard output (default)."],
 | |
|       ['--version',  '-V', GetoptLong::NO_ARGUMENT,
 | |
|         "Display the program version."],
 | |
|     ]
 | |
| 
 | |
|     # Initialize a Rake::Application object.
 | |
|     def initialize
 | |
|       super
 | |
|       @name = 'rake'
 | |
|       @rakefiles = DEFAULT_RAKEFILES.dup
 | |
|       @rakefile = nil
 | |
|       @pending_imports = []
 | |
|       @imported = []
 | |
|       @loaders = {}
 | |
|       @default_loader = Rake::DefaultLoader.new
 | |
|       @original_dir = Dir.pwd
 | |
|       @top_level_tasks = []
 | |
|       add_loader('rf', DefaultLoader.new)
 | |
|       add_loader('rake', DefaultLoader.new)
 | |
|     end
 | |
| 
 | |
|     # 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 (+run_tasks+).
 | |
|     #
 | |
|     # If you wish to build a custom rake command, you should call +init+ on your
 | |
|     # application.  The define any tasks.  Finally, call +top_level+ to run your top
 | |
|     # level tasks.
 | |
|     def run
 | |
|       standard_exception_handling do
 | |
|         init
 | |
|         load_rakefile
 | |
|         top_level
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Initialize the command line parameters and app name.
 | |
|     def init(app_name='rake')
 | |
|       standard_exception_handling do
 | |
|         @name = app_name
 | |
|         handle_options
 | |
|         collect_tasks
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Find the rakefile and then load it and any pending imports.
 | |
|     def load_rakefile
 | |
|       standard_exception_handling do
 | |
|         raw_load_rakefile
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Run the top level tasks of a Rake application.
 | |
|     def top_level
 | |
|       standard_exception_handling do
 | |
|         if options.show_tasks
 | |
|           display_tasks_and_comments
 | |
|         elsif options.show_prereqs
 | |
|           display_prerequisites
 | |
|         else
 | |
|           top_level_tasks.each { |task_name| invoke_task(task_name) }
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Add a loader to handle imported files ending in the extension
 | |
|     # +ext+.
 | |
|     def add_loader(ext, loader)
 | |
|       ext = ".#{ext}" unless ext =~ /^\./
 | |
|       @loaders[ext] = loader
 | |
|     end
 | |
| 
 | |
|     # Application options from the command line
 | |
|     def options
 | |
|       @options ||= OpenStruct.new
 | |
|     end
 | |
| 
 | |
|     # private ----------------------------------------------------------------
 | |
| 
 | |
|     def invoke_task(task_string)
 | |
|       name, args = parse_task_string(task_string)
 | |
|       t = self[name]
 | |
|       t.invoke(*args)
 | |
|     end
 | |
| 
 | |
|     def parse_task_string(string)
 | |
|       if string =~ /^([^\[]+)(\[(.*)\])$/
 | |
|         name = $1
 | |
|         args = $3.split(/\s*,\s*/)
 | |
|       else
 | |
|         name = string
 | |
|         args = []
 | |
|       end
 | |
|       [name, args]
 | |
|     end
 | |
| 
 | |
|     # Provide standard exception handling for the given block.
 | |
|     def standard_exception_handling
 | |
|       begin
 | |
|         yield
 | |
|       rescue SystemExit => ex
 | |
|         # Exit silently with current status
 | |
|         exit(ex.status)
 | |
|       rescue SystemExit, GetoptLong::InvalidOption => ex
 | |
|         # Exit silently
 | |
|         exit(1)
 | |
|       rescue Exception => ex
 | |
|         # Exit with error message
 | |
|         $stderr.puts "rake aborted!"
 | |
|         $stderr.puts ex.message
 | |
|         if options.trace
 | |
|           $stderr.puts ex.backtrace.join("\n")
 | |
|         else
 | |
|           $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
 | |
|           $stderr.puts "(See full trace by running task with --trace)"
 | |
|         end
 | |
|         exit(1)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # True if one of the files in RAKEFILES is in the current directory.
 | |
|     # If a match is found, it is copied into @rakefile.
 | |
|     def have_rakefile
 | |
|       @rakefiles.each do |fn|
 | |
|         if File.exist?(fn) || fn == ''
 | |
|           @rakefile = fn
 | |
|           return true
 | |
|         end
 | |
|       end
 | |
|       return false
 | |
|     end
 | |
| 
 | |
|     # Display the rake command line help.
 | |
|     def help
 | |
|       puts "rake [-f rakefile] {options} targets..."
 | |
|       puts
 | |
|       puts "Options are ..."
 | |
|       puts
 | |
|       OPTIONS.sort.each do |long, short, mode, desc|
 | |
|         if mode == GetoptLong::REQUIRED_ARGUMENT
 | |
|           if desc =~ /\b([A-Z]{2,})\b/
 | |
|             long = long + "=#{$1}"
 | |
|           end
 | |
|         end
 | |
|         printf "  %-20s (%s)\n", long, short
 | |
|         printf "      %s\n", desc
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Display the tasks and dependencies.
 | |
|     def display_tasks_and_comments
 | |
|       displayable_tasks = tasks.select { |t|
 | |
|         t.comment && t.name =~ options.show_task_pattern
 | |
|       }
 | |
|       if options.full_description
 | |
|         displayable_tasks.each do |t|
 | |
|           puts "rake #{t.name_with_args}"
 | |
|           t.full_comment.split("\n").each do |line|
 | |
|             puts "    #{line}"
 | |
|           end
 | |
|           puts
 | |
|         end
 | |
|       else
 | |
|         width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10
 | |
|         max_column = 80 - name.size - width - 7
 | |
|         displayable_tasks.each do |t|
 | |
|           printf "#{name} %-#{width}s  # %s\n",
 | |
|             t.name_with_args, truncate(t.comment, max_column)
 | |
|         end
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     def truncate(string, width)
 | |
|       if string.length <= width
 | |
|         string
 | |
|       else
 | |
|         string[0, width-3] + "..."
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Display the tasks and prerequisites
 | |
|     def display_prerequisites
 | |
|       tasks.each do |t|
 | |
|         puts "rake #{t.name}"
 | |
|         t.prerequisites.each { |pre| puts "    #{pre}" }
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Return a list of the command line options supported by the
 | |
|     # program.
 | |
|     def command_line_options
 | |
|       OPTIONS.collect { |lst| lst[0..-2] }
 | |
|     end
 | |
| 
 | |
|     # Do the option defined by +opt+ and +value+.
 | |
|     def do_option(opt, value)
 | |
|       case opt
 | |
|       when '--describe'
 | |
|         options.show_tasks = true
 | |
|         options.show_task_pattern = Regexp.new(value || '.')
 | |
|         options.full_description = true
 | |
|       when '--dry-run'
 | |
|         verbose(true)
 | |
|         nowrite(true)
 | |
|         options.dryrun = true
 | |
|         options.trace = true
 | |
|       when '--help'
 | |
|         help
 | |
|         exit
 | |
|       when '--libdir'
 | |
|         $:.push(value)
 | |
|       when '--nosearch'
 | |
|         options.nosearch = true
 | |
|       when '--prereqs'
 | |
|         options.show_prereqs = true
 | |
|       when '--quiet'
 | |
|         verbose(false)
 | |
|       when '--rakefile'
 | |
|         @rakefiles.clear
 | |
|         @rakefiles << value
 | |
|       when '--rakelibdir'
 | |
|         options.rakelib = value.split(':')
 | |
|       when '--require'
 | |
|         begin
 | |
|           require value
 | |
|         rescue LoadError => ex
 | |
|           begin
 | |
|             rake_require value
 | |
|           rescue LoadError => ex2
 | |
|             raise ex
 | |
|           end
 | |
|         end
 | |
|       when '--silent'
 | |
|         verbose(false)
 | |
|         options.silent = true
 | |
|       when '--tasks'
 | |
|         options.show_tasks = true
 | |
|         options.show_task_pattern = Regexp.new(value || '.')
 | |
|         options.full_description = false
 | |
|       when '--trace'
 | |
|         options.trace = true
 | |
|         verbose(true)
 | |
|       when '--verbose'
 | |
|         verbose(true)
 | |
|       when '--version'
 | |
|         puts "rake, version #{RAKEVERSION}"
 | |
|         exit
 | |
|       when '--classic-namespace'
 | |
|         require 'rake/classic_namespace'
 | |
|         options.classic_namespace = true
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Read and handle the command line options.
 | |
|     def handle_options
 | |
|       options.rakelib = ['rakelib']
 | |
| 
 | |
|       opts = GetoptLong.new(*command_line_options)
 | |
|       opts.each { |opt, value| do_option(opt, value) }
 | |
| 
 | |
|       # If class namespaces are requested, set the global options
 | |
|       # according to the values in the options structure.
 | |
|       if options.classic_namespace
 | |
|         $show_tasks = options.show_tasks
 | |
|         $show_prereqs = options.show_prereqs
 | |
|         $trace = options.trace
 | |
|         $dryrun = options.dryrun
 | |
|         $silent = options.silent
 | |
|       end
 | |
|     rescue NoMethodError => ex
 | |
|       raise GetoptLong::InvalidOption, "While parsing options, error = #{ex.class}:#{ex.message}"
 | |
|     end
 | |
| 
 | |
|     # Similar to the regular Ruby +require+ command, but will check
 | |
|     # for .rake files in addition to .rb files.
 | |
|     def rake_require(file_name, paths=$LOAD_PATH, loaded=$")
 | |
|       return false if loaded.include?(file_name)
 | |
|       paths.each do |path|
 | |
|         fn = file_name + ".rake"
 | |
|         full_path = File.join(path, fn)
 | |
|         if File.exist?(full_path)
 | |
|           load full_path
 | |
|           loaded << fn
 | |
|           return true
 | |
|         end
 | |
|       end
 | |
|       fail LoadError, "Can't find #{file_name}"
 | |
|     end
 | |
| 
 | |
|     def raw_load_rakefile # :nodoc:
 | |
|       here = Dir.pwd
 | |
|       while ! have_rakefile
 | |
|         Dir.chdir("..")
 | |
|         if Dir.pwd == here || options.nosearch
 | |
|           fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})"
 | |
|         end
 | |
|         here = Dir.pwd
 | |
|       end
 | |
|       puts "(in #{Dir.pwd})" unless options.silent
 | |
|       $rakefile = @rakefile
 | |
|       load File.expand_path(@rakefile) if @rakefile != ''
 | |
|       options.rakelib.each do |rlib|
 | |
|         Dir["#{rlib}/*.rake"].each do |name| add_import name end
 | |
|       end
 | |
|       load_imports
 | |
|     end
 | |
| 
 | |
|     # Collect the list of tasks on the command line.  If no tasks are
 | |
|     # given, return a list containing only the default task.
 | |
|     # Environmental assignments are processed at this time as well.
 | |
|     def collect_tasks
 | |
|       @top_level_tasks = []
 | |
|       ARGV.each do |arg|
 | |
|         if arg =~ /^(\w+)=(.*)$/
 | |
|           ENV[$1] = $2
 | |
|         else
 | |
|           @top_level_tasks << arg
 | |
|         end
 | |
|       end
 | |
|       @top_level_tasks.push("default") if @top_level_tasks.size == 0
 | |
|     end
 | |
| 
 | |
|     # Add a file to the list of files to be imported.
 | |
|     def add_import(fn)
 | |
|       @pending_imports << fn
 | |
|     end
 | |
| 
 | |
|     # Load the pending list of imported files.
 | |
|     def load_imports
 | |
|       while fn = @pending_imports.shift
 | |
|         next if @imported.member?(fn)
 | |
|         if fn_task = lookup(fn)
 | |
|           fn_task.invoke
 | |
|         end
 | |
|         ext = File.extname(fn)
 | |
|         loader = @loaders[ext] || @default_loader
 | |
|         loader.load(fn)
 | |
|         @imported << fn
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Warn about deprecated use of top level constant names.
 | |
|     def const_warning(const_name)
 | |
|       @const_warning ||= false
 | |
|       if ! @const_warning
 | |
|         $stderr.puts %{WARNING: Deprecated reference to top-level constant '#{const_name}' } +
 | |
|           %{found at: #{rakefile_location}} # '
 | |
|         $stderr.puts %{    Use --classic-namespace on rake command}
 | |
|         $stderr.puts %{    or 'require "rake/classic_namespace"' in Rakefile}
 | |
|       end
 | |
|       @const_warning = true
 | |
|     end
 | |
| 
 | |
|     def rakefile_location
 | |
|       begin
 | |
|         fail
 | |
|       rescue RuntimeError => ex
 | |
|         ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| 
 | |
| class Module
 | |
|   # Rename the original handler to make it available.
 | |
|   alias :rake_original_const_missing :const_missing
 | |
| 
 | |
|   # Check for deprecated uses of top level (i.e. in Object) uses of
 | |
|   # Rake class names.  If someone tries to reference the constant
 | |
|   # name, display a warning and return the proper object.  Using the
 | |
|   # --classic-namespace command line option will define these
 | |
|   # constants in Object and avoid this handler.
 | |
|   def const_missing(const_name)
 | |
|     case const_name
 | |
|     when :Task
 | |
|       Rake.application.const_warning(const_name)
 | |
|       Rake::Task
 | |
|     when :FileTask
 | |
|       Rake.application.const_warning(const_name)
 | |
|       Rake::FileTask
 | |
|     when :FileCreationTask
 | |
|       Rake.application.const_warning(const_name)
 | |
|       Rake::FileCreationTask
 | |
|     when :RakeApp
 | |
|       Rake.application.const_warning(const_name)
 | |
|       Rake::Application
 | |
|     else
 | |
|       rake_original_const_missing(const_name)
 | |
|     end
 | |
|   end
 | |
| end
 |