summaryrefslogtreecommitdiff
path: root/lib/rake
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rake')
-rw-r--r--lib/rake/alt_system.rb109
-rw-r--r--lib/rake/application.rb594
-rw-r--r--lib/rake/classic_namespace.rb1
-rw-r--r--lib/rake/cloneable.rb25
-rw-r--r--lib/rake/contrib/compositepublisher.rb1
-rw-r--r--lib/rake/contrib/ftptools.rb6
-rw-r--r--lib/rake/contrib/publisher.rb6
-rw-r--r--lib/rake/contrib/sshpublisher.rb5
-rw-r--r--lib/rake/contrib/sys.rb191
-rw-r--r--lib/rake/default_loader.rb10
-rw-r--r--lib/rake/dsl_definition.rb167
-rw-r--r--lib/rake/early_time.rb18
-rw-r--r--lib/rake/ext/core.rb27
-rw-r--r--lib/rake/ext/module.rb39
-rw-r--r--lib/rake/ext/string.rb167
-rw-r--r--lib/rake/ext/time.rb14
-rw-r--r--lib/rake/file_creation_task.rb24
-rw-r--r--lib/rake/file_list.rb403
-rw-r--r--lib/rake/file_task.rb47
-rw-r--r--lib/rake/file_utils.rb112
-rw-r--r--lib/rake/file_utils_ext.rb142
-rw-r--r--lib/rake/gempackagetask.rb96
-rw-r--r--lib/rake/invocation_chain.rb51
-rw-r--r--lib/rake/invocation_exception_mixin.rb16
-rw-r--r--lib/rake/lib/project.rake21
-rw-r--r--lib/rake/loaders/makefile.rb8
-rw-r--r--lib/rake/multi_task.rb16
-rw-r--r--lib/rake/name_space.rb25
-rw-r--r--lib/rake/packagetask.rb5
-rw-r--r--lib/rake/pathmap.rb1
-rw-r--r--lib/rake/pseudo_status.rb24
-rw-r--r--lib/rake/rake_module.rb29
-rw-r--r--lib/rake/rake_test_loader.rb12
-rw-r--r--lib/rake/rdoctask.rb232
-rwxr-xr-xlib/rake/ruby182_test_unit_fix.rb25
-rw-r--r--lib/rake/rule_recursion_overflow_error.rb20
-rw-r--r--lib/rake/runtest.rb8
-rw-r--r--lib/rake/task.rb327
-rw-r--r--lib/rake/task_argument_error.rb7
-rw-r--r--lib/rake/task_arguments.rb74
-rw-r--r--lib/rake/task_manager.rb307
-rw-r--r--lib/rake/tasklib.rb1
-rw-r--r--lib/rake/testtask.rb83
-rw-r--r--lib/rake/version.rb10
-rw-r--r--lib/rake/win32.rb45
45 files changed, 3403 insertions, 148 deletions
diff --git a/lib/rake/alt_system.rb b/lib/rake/alt_system.rb
new file mode 100644
index 0000000000..05af19863a
--- /dev/null
+++ b/lib/rake/alt_system.rb
@@ -0,0 +1,109 @@
+#
+# Copyright (c) 2008 James M. Lawrence
+#
+# 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.
+#
+
+require 'rbconfig'
+
+#
+# Alternate implementations of system() and backticks `` on Windows
+# for ruby-1.8 and earlier.
+#
+module Rake::AltSystem
+ WINDOWS = RbConfig::CONFIG["host_os"] =~
+ %r!(msdos|mswin|djgpp|mingw|[Ww]indows)!
+
+ class << self
+ def define_module_function(name, &block)
+ define_method(name, &block)
+ module_function(name)
+ end
+ end
+
+ if WINDOWS and RUBY_VERSION < "1.9.0"
+ RUNNABLE_EXTS = %w[com exe bat cmd]
+ RUNNABLE_PATTERN = %r!\.(#{RUNNABLE_EXTS.join('|')})\Z!i
+
+ define_module_function :kernel_system, &Kernel.method(:system)
+ define_module_function :kernel_backticks, &Kernel.method(:'`')
+
+ module_function
+
+ def repair_command(cmd)
+ "call " + (
+ if cmd =~ %r!\A\s*\".*?\"!
+ # already quoted
+ cmd
+ elsif match = cmd.match(%r!\A\s*(\S+)!)
+ if match[1] =~ %r!/!
+ # avoid x/y.bat interpretation as x with option /y
+ %Q!"#{match[1]}"! + match.post_match
+ else
+ # a shell command will fail if quoted
+ cmd
+ end
+ else
+ # empty or whitespace
+ cmd
+ end
+ )
+ end
+
+ def find_runnable(file)
+ if file =~ RUNNABLE_PATTERN
+ file
+ else
+ RUNNABLE_EXTS.each { |ext|
+ if File.exist?(test = "#{file}.#{ext}")
+ return test
+ end
+ }
+ nil
+ end
+ end
+
+ def system(cmd, *args)
+ repaired = (
+ if args.empty?
+ [repair_command(cmd)]
+ elsif runnable = find_runnable(cmd)
+ [File.expand_path(runnable), *args]
+ else
+ # non-existent file
+ [cmd, *args]
+ end
+ )
+ kernel_system(*repaired)
+ end
+
+ def backticks(cmd)
+ kernel_backticks(repair_command(cmd))
+ end
+
+ define_module_function :'`', &method(:backticks)
+ else
+ # Non-Windows or ruby-1.9+: same as Kernel versions
+ define_module_function :system, &Kernel.method(:system)
+ define_module_function :backticks, &Kernel.method(:'`')
+ define_module_function :'`', &Kernel.method(:'`')
+ end
+end
diff --git a/lib/rake/application.rb b/lib/rake/application.rb
new file mode 100644
index 0000000000..a4954f27e7
--- /dev/null
+++ b/lib/rake/application.rb
@@ -0,0 +1,594 @@
+require 'shellwords'
+require 'optparse'
+
+require 'rake/task_manager'
+require 'rake/win32'
+
+module Rake
+
+ ######################################################################
+ # 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
+
+ # Number of columns on the terminal
+ attr_accessor :terminal_columns
+
+ # 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
+
+ # 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('rb', DefaultLoader.new)
+ add_loader('rf', DefaultLoader.new)
+ add_loader('rake', DefaultLoader.new)
+ @tty_output = STDOUT.tty?
+ @terminal_columns = ENV['RAKE_COLUMNS'].to_i
+ 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. Then 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
+ raise
+ rescue OptionParser::InvalidOption => ex
+ $stderr.puts ex.message
+ exit(false)
+ rescue Exception => ex
+ # Exit with error message
+ display_error_message(ex)
+ exit(false)
+ end
+ end
+
+ # Display the error message that caused the exception.
+ def display_error_message(ex)
+ $stderr.puts "#{name} aborted!"
+ $stderr.puts ex.message
+ if options.trace
+ $stderr.puts ex.backtrace.join("\n")
+ else
+ $stderr.puts rakefile_location(ex.backtrace)
+ end
+ $stderr.puts "Tasks: #{ex.chain}" if has_chain?(ex)
+ $stderr.puts "(See full trace by running task with --trace)" unless options.trace
+ end
+
+ # Warn about deprecated usage.
+ #
+ # Example:
+ # Rake.application.deprecate("import", "Rake.import", caller.first)
+ #
+ def deprecate(old_usage, new_usage, call_site)
+ return if options.ignore_deprecate
+ $stderr.puts "WARNING: '#{old_usage}' is deprecated. " +
+ "Please use '#{new_usage}' instead.\n" +
+ " at #{call_site}"
+ end
+
+ # Does the exception have a task invocation chain?
+ def has_chain?(exception)
+ exception.respond_to?(:chain) && exception.chain
+ end
+ private :has_chain?
+
+ # 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)
+ others = Dir.glob(fn, File::FNM_CASEFOLD)
+ return others.size == 1 ? others.first : fn
+ elsif fn == ''
+ return fn
+ end
+ end
+ return nil
+ end
+
+ # True if we are outputting to TTY, false otherwise
+ def tty_output?
+ @tty_output
+ end
+
+ # Override the detected TTY output state (mostly for testing)
+ def tty_output=( tty_output_state )
+ @tty_output = tty_output_state
+ end
+
+ # We will truncate output if we are outputting to a TTY or if we've been
+ # given an explicit column width to honor
+ def truncate_output?
+ tty_output? || @terminal_columns.nonzero?
+ end
+
+ # Display the tasks and comments.
+ def display_tasks_and_comments
+ displayable_tasks = tasks.select { |t|
+ t.comment && t.name =~ options.show_task_pattern
+ }
+ case options.show_tasks
+ when :tasks
+ width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10
+ max_column = truncate_output? ? terminal_width - name.size - width - 7 : nil
+
+ displayable_tasks.each do |t|
+ printf "#{name} %-#{width}s # %s\n",
+ t.name_with_args, max_column ? truncate(t.comment, max_column) : t.comment
+ end
+ when :describe
+ displayable_tasks.each do |t|
+ puts "#{name} #{t.name_with_args}"
+ t.full_comment.split("\n").each do |line|
+ puts " #{line}"
+ end
+ puts
+ end
+ when :lines
+ displayable_tasks.each do |t|
+ t.locations.each do |loc|
+ printf "#{name} %-30s %s\n",t.name_with_args, loc
+ end
+ end
+ else
+ fail "Unknown show task mode: '#{options.show_tasks}'"
+ end
+ end
+
+ def terminal_width
+ if @terminal_columns.nonzero?
+ result = @terminal_columns
+ else
+ result = unix? ? dynamic_width : 80
+ end
+ (result < 10) ? 80 : result
+ rescue
+ 80
+ end
+
+ # Calculate the dynamic width of the
+ def dynamic_width
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
+ end
+
+ def dynamic_width_stty
+ %x{stty size 2>/dev/null}.split[1].to_i
+ end
+
+ def dynamic_width_tput
+ %x{tput cols 2>/dev/null}.to_i
+ end
+
+ def unix?
+ RbConfig::CONFIG['host_os'] =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
+ end
+
+ def windows?
+ Win32.windows?
+ 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 "#{name} #{t.name}"
+ t.prerequisites.each { |pre| puts " #{pre}" }
+ end
+ end
+
+ # A list of all the standard options used in rake, suitable for
+ # passing to OptionParser.
+ def standard_rake_options
+ [
+ ['--classic-namespace', '-C', "Put Task and FileTask in the top level namespace",
+ lambda { |value|
+ require 'rake/classic_namespace'
+ options.classic_namespace = true
+ }
+ ],
+ ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
+ lambda { |value|
+ options.show_tasks = :describe
+ options.show_task_pattern = Regexp.new(value || '')
+ TaskManager.record_task_metadata = true
+ }
+ ],
+ ['--dry-run', '-n', "Do a dry run without executing actions.",
+ lambda { |value|
+ Rake.verbose(true)
+ Rake.nowrite(true)
+ options.dryrun = true
+ options.trace = true
+ }
+ ],
+ ['--execute', '-e CODE', "Execute some Ruby code and exit.",
+ lambda { |value|
+ eval(value)
+ exit
+ }
+ ],
+ ['--execute-print', '-p CODE', "Execute some Ruby code, print the result, then exit.",
+ lambda { |value|
+ puts eval(value)
+ exit
+ }
+ ],
+ ['--execute-continue', '-E CODE',
+ "Execute some Ruby code, then continue with normal task processing.",
+ lambda { |value| eval(value) }
+ ],
+ ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.",
+ lambda { |value| $:.push(value) }
+ ],
+ ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.",
+ lambda { |value| options.nosearch = true }
+ ],
+ ['--prereqs', '-P', "Display the tasks and dependencies, then exit.",
+ lambda { |value| options.show_prereqs = true }
+ ],
+ ['--quiet', '-q', "Do not log messages to standard output.",
+ lambda { |value| Rake.verbose(false) }
+ ],
+ ['--rakefile', '-f [FILE]', "Use FILE as the rakefile.",
+ lambda { |value|
+ value ||= ''
+ @rakefiles.clear
+ @rakefiles << value
+ }
+ ],
+ ['--rakelibdir', '--rakelib', '-R RAKELIBDIR',
+ "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')",
+ lambda { |value| options.rakelib = value.split(':') }
+ ],
+ ['--require', '-r MODULE', "Require MODULE before executing rakefile.",
+ lambda { |value|
+ begin
+ require value
+ rescue LoadError => ex
+ begin
+ rake_require value
+ rescue LoadError
+ raise ex
+ end
+ end
+ }
+ ],
+ ['--rules', "Trace the rules resolution.",
+ lambda { |value| options.trace_rules = true }
+ ],
+ ['--silent', '-s', "Like --quiet, but also suppresses the 'in directory' announcement.",
+ lambda { |value|
+ Rake.verbose(false)
+ options.silent = true
+ }
+ ],
+ ['--system', '-g',
+ "Using system wide (global) rakefiles (usually '~/.rake/*.rake').",
+ lambda { |value| options.load_system = true }
+ ],
+ ['--no-system', '--nosystem', '-G',
+ "Use standard project Rakefile search paths, ignore system wide rakefiles.",
+ lambda { |value| options.ignore_system = true }
+ ],
+ ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.",
+ lambda { |value|
+ options.show_tasks = :tasks
+ options.show_task_pattern = Regexp.new(value || '')
+ Rake::TaskManager.record_task_metadata = true
+ }
+ ],
+ ['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.",
+ lambda { |value|
+ options.trace = true
+ Rake.verbose(true)
+ }
+ ],
+ ['--verbose', '-v', "Log message to standard output.",
+ lambda { |value| Rake.verbose(true) }
+ ],
+ ['--version', '-V', "Display the program version.",
+ lambda { |value|
+ puts "rake, version #{RAKEVERSION}"
+ exit
+ }
+ ],
+ ['--where', '-W [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
+ lambda { |value|
+ options.show_tasks = :lines
+ options.show_task_pattern = Regexp.new(value || '')
+ Rake::TaskManager.record_task_metadata = true
+ }
+ ],
+ ['--no-deprecation-warnings', '-X', "Disable the deprecation warnings.",
+ lambda { |value|
+ options.ignore_deprecate = true
+ }
+ ],
+ ]
+ end
+
+ # Read and handle the command line options.
+ def handle_options
+ options.rakelib = ['rakelib']
+
+ OptionParser.new do |opts|
+ opts.banner = "rake [-f rakefile] {options} targets..."
+ opts.separator ""
+ opts.separator "Options are ..."
+
+ opts.on_tail("-h", "--help", "-H", "Display this help message.") do
+ puts opts
+ exit
+ end
+
+ standard_rake_options.each { |args| opts.on(*args) }
+ opts.environment('RAKEOPT')
+ end.parse!
+
+ # 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
+ 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=$")
+ fn = file_name + ".rake"
+ return false if loaded.include?(fn)
+ paths.each do |path|
+ full_path = File.join(path, fn)
+ if File.exist?(full_path)
+ Rake.load_rakefile(full_path)
+ loaded << fn
+ return true
+ end
+ end
+ fail LoadError, "Can't find #{file_name}"
+ end
+
+ def find_rakefile_location
+ here = Dir.pwd
+ while ! (fn = have_rakefile)
+ Dir.chdir("..")
+ if Dir.pwd == here || options.nosearch
+ return nil
+ end
+ here = Dir.pwd
+ end
+ [fn, here]
+ ensure
+ Dir.chdir(Rake.original_dir)
+ end
+
+ def print_rakefile_directory(location)
+ $stderr.puts "(in #{Dir.pwd})" unless
+ options.silent or original_dir == location
+ end
+
+ def raw_load_rakefile # :nodoc:
+ rakefile, location = find_rakefile_location
+ if (! options.ignore_system) &&
+ (options.load_system || rakefile.nil?) &&
+ system_dir && File.directory?(system_dir)
+ print_rakefile_directory(location)
+ glob("#{system_dir}/*.rake") do |name|
+ add_import name
+ end
+ else
+ fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if
+ rakefile.nil?
+ @rakefile = rakefile
+ Dir.chdir(location)
+ print_rakefile_directory(location)
+ $rakefile = @rakefile if options.classic_namespace
+ Rake.load_rakefile(File.expand_path(@rakefile)) if @rakefile && @rakefile != ''
+ options.rakelib.each do |rlib|
+ glob("#{rlib}/*.rake") do |name|
+ add_import name
+ end
+ end
+ end
+ load_imports
+ end
+
+ def glob(path, &block)
+ Dir[path.gsub("\\", '/')].each(&block)
+ end
+ private :glob
+
+ # The directory path containing the system wide rakefiles.
+ def system_dir
+ @system_dir ||=
+ begin
+ if ENV['RAKE_SYSTEM']
+ ENV['RAKE_SYSTEM']
+ else
+ standard_system_dir
+ end
+ end
+ end
+
+ # The standard directory containing system wide rake files.
+ if Win32.windows?
+ def standard_system_dir #:nodoc:
+ Win32.win32_system_dir
+ end
+ else
+ def standard_system_dir #:nodoc:
+ File.join(File.expand_path('~'), '.rake')
+ end
+ end
+ private :standard_system_dir
+
+ # 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 unless 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 backtrace = caller
+ backtrace.map { |t| t[/([^:]+):/,1] }
+
+ re = /^#{@rakefile}$/
+ re = /#{re.source}/i if windows?
+
+ backtrace.find { |str| str =~ re } || ''
+ end
+ end
+end
diff --git a/lib/rake/classic_namespace.rb b/lib/rake/classic_namespace.rb
index feb7569966..d87aba0f33 100644
--- a/lib/rake/classic_namespace.rb
+++ b/lib/rake/classic_namespace.rb
@@ -2,6 +2,7 @@
# Loading this file enables compatibility with older Rakefile that
# referenced Task from the top level.
+warn "WARNING: Classic namespaces are deprecated and will be removed from future versions of Rake."
Task = Rake::Task
FileTask = Rake::FileTask
FileCreationTask = Rake::FileCreationTask
diff --git a/lib/rake/cloneable.rb b/lib/rake/cloneable.rb
new file mode 100644
index 0000000000..19c780bff6
--- /dev/null
+++ b/lib/rake/cloneable.rb
@@ -0,0 +1,25 @@
+module Rake
+ # ##########################################################################
+ # 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 dup
+ 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.taint if tainted?
+ sibling
+ end
+
+ def clone
+ sibling = dup
+ sibling.freeze if frozen?
+ sibling
+ end
+ end
+end
diff --git a/lib/rake/contrib/compositepublisher.rb b/lib/rake/contrib/compositepublisher.rb
index 31ef080dd7..69952a0808 100644
--- a/lib/rake/contrib/compositepublisher.rb
+++ b/lib/rake/contrib/compositepublisher.rb
@@ -18,3 +18,4 @@ module Rake
end
end
+
diff --git a/lib/rake/contrib/ftptools.rb b/lib/rake/contrib/ftptools.rb
index 12e4ff25de..78420c7412 100644
--- a/lib/rake/contrib/ftptools.rb
+++ b/lib/rake/contrib/ftptools.rb
@@ -99,7 +99,7 @@ module Rake # :nodoc:
end
end
- # Create an FTP uploader targetting the directory +path+ on +host+
+ # Create an FTP uploader targeting the directory +path+ on +host+
# using the given account and password. +path+ will be the root
# path of the uploader.
def initialize(path, host, account, password)
@@ -118,7 +118,7 @@ module Rake # :nodoc:
current_dir = File.join(route)
if @created[current_dir].nil?
@created[current_dir] = true
- puts "Creating Directory #{current_dir}" if @verbose
+ $stderr.puts "Creating Directory #{current_dir}" if @verbose
@ftp.mkdir(current_dir) rescue nil
end
end
@@ -141,7 +141,7 @@ module Rake # :nodoc:
# Upload a single file to the uploader's root path.
def upload(file)
- puts "Uploading #{file}" if @verbose
+ $stderr.puts "Uploading #{file}" if @verbose
dir = File.dirname(file)
makedirs(dir)
@ftp.putbinaryfile(file, file) unless File.directory?(file)
diff --git a/lib/rake/contrib/publisher.rb b/lib/rake/contrib/publisher.rb
index 7f69d3a654..baa9a3607d 100644
--- a/lib/rake/contrib/publisher.rb
+++ b/lib/rake/contrib/publisher.rb
@@ -1,10 +1,6 @@
-# Copyright 2003, 2004, 2005, 2006, 2007, 2008 by Jim Weirich (jim@weirichhouse.org)
+# Copyright 2003-2010 by Jim Weirich (jim.weirich@gmail.com)
# All rights reserved.
-# Permission is granted for use, copying, modification, distribution,
-# and distribution of modified versions of this work as long as the
-# above copyright notice is included.
-
# Configuration information about an upload host system.
# * name :: Name of host system.
# * webdir :: Base directory for the web information for the
diff --git a/lib/rake/contrib/sshpublisher.rb b/lib/rake/contrib/sshpublisher.rb
index e679716c7b..bd6adc127e 100644
--- a/lib/rake/contrib/sshpublisher.rb
+++ b/lib/rake/contrib/sshpublisher.rb
@@ -1,3 +1,4 @@
+require 'rake/dsl_definition'
require 'rake/contrib/compositepublisher'
module Rake
@@ -5,6 +6,8 @@ module Rake
# Publish an entire directory to an existing remote directory using
# SSH.
class SshDirPublisher
+ include Rake::DSL
+
def initialize(host, remote_dir, local_dir)
@host = host
@remote_dir = remote_dir
@@ -27,6 +30,8 @@ module Rake
# Publish a list of files to an existing remote directory.
class SshFilePublisher
+ include Rake::DSL
+
# Create a publisher using the give host information.
def initialize(host, remote_dir, local_dir, *files)
@host = host
diff --git a/lib/rake/contrib/sys.rb b/lib/rake/contrib/sys.rb
new file mode 100644
index 0000000000..41963f1fef
--- /dev/null
+++ b/lib/rake/contrib/sys.rb
@@ -0,0 +1,191 @@
+warn 'Sys has been deprecated in favor of FileUtils'
+
+#--
+# Copyright 2003-2010 by Jim Weirich (jim.weirich@gmail.com)
+# All rights reserved.
+#++
+#
+begin
+ require 'ftools'
+rescue LoadError
+end
+require 'rbconfig'
+
+######################################################################
+# Sys provides a number of file manipulation tools for the convenience
+# of writing Rakefiles. All commands in this module will announce
+# their activity on standard output if the $verbose flag is set
+# ($verbose = true is the default). You can control this by globally
+# setting $verbose or by using the +verbose+ and +quiet+ methods.
+#
+# Sys has been deprecated in favor of the FileUtils module available
+# in Ruby 1.8.
+#
+module Sys
+ RUBY = RbConfig::CONFIG['ruby_install_name']
+
+ # Install all the files matching +wildcard+ into the +dest_dir+
+ # directory. The permission mode is set to +mode+.
+ def install(wildcard, dest_dir, mode)
+ Dir[wildcard].each do |fn|
+ File.install(fn, dest_dir, mode, $verbose)
+ end
+ end
+
+ # Run the system command +cmd+.
+ def run(cmd)
+ log cmd
+ system(cmd) or fail "Command Failed: [#{cmd}]"
+ end
+
+ # Run a Ruby interpreter with the given arguments.
+ def ruby(*args)
+ run "#{RUBY} #{args.join(' ')}"
+ end
+
+ # Copy a single file from +file_name+ to +dest_file+.
+ def copy(file_name, dest_file)
+ log "Copying file #{file_name} to #{dest_file}"
+ File.copy(file_name, dest_file)
+ end
+
+ # Copy all files matching +wildcard+ into the directory +dest_dir+.
+ def copy_files(wildcard, dest_dir)
+ for_matching_files(wildcard, dest_dir) { |from, to| copy(from, to) }
+ end
+
+ # Link +file_name+ to +dest_file+.
+ def link(file_name, dest_file)
+ log "Linking file #{file_name} to #{dest_file}"
+ File.link(file_name, dest_file)
+ end
+
+ # Link all files matching +wildcard+ into the directory +dest_dir+.
+ def link_files(wildcard, dest_dir)
+ for_matching_files(wildcard, dest_dir) { |from, to| link(from, to) }
+ end
+
+ # Symlink +file_name+ to +dest_file+.
+ def symlink(file_name, dest_file)
+ log "Symlinking file #{file_name} to #{dest_file}"
+ File.symlink(file_name, dest_file)
+ end
+
+ # Symlink all files matching +wildcard+ into the directory +dest_dir+.
+ def symlink_files(wildcard, dest_dir)
+ for_matching_files(wildcard, dest_dir) { |from, to| link(from, to) }
+ end
+
+ # Remove all files matching +wildcard+. If a matching file is a
+ # directory, it must be empty to be removed. used +delete_all+ to
+ # recursively delete directories.
+ def delete(*wildcards)
+ wildcards.each do |wildcard|
+ Dir[wildcard].each do |fn|
+ if File.directory?(fn)
+ log "Deleting directory #{fn}"
+ Dir.delete(fn)
+ else
+ log "Deleting file #{fn}"
+ File.delete(fn)
+ end
+ end
+ end
+ end
+
+ # Recursively delete all files and directories matching +wildcard+.
+ def delete_all(*wildcards)
+ wildcards.each do |wildcard|
+ Dir[wildcard].each do |fn|
+ next if ! File.exist?(fn)
+ if File.directory?(fn)
+ Dir["#{fn}/*"].each do |subfn|
+ next if subfn=='.' || subfn=='..'
+ delete_all(subfn)
+ end
+ log "Deleting directory #{fn}"
+ Dir.delete(fn)
+ else
+ log "Deleting file #{fn}"
+ File.delete(fn)
+ end
+ end
+ end
+ end
+
+ # Make the directories given in +dirs+.
+ def makedirs(*dirs)
+ dirs.each do |fn|
+ log "Making directory #{fn}"
+ File.makedirs(fn)
+ end
+ end
+
+ # Make +dir+ the current working directory for the duration of
+ # executing the given block.
+ def indir(dir)
+ olddir = Dir.pwd
+ Dir.chdir(dir)
+ yield
+ ensure
+ Dir.chdir(olddir)
+ end
+
+ # Split a file path into individual directory names.
+ #
+ # For 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
+
+ # Write a message to standard error if $verbose is enabled.
+ def log(msg)
+ print " " if $trace && $verbose
+ $stderr.puts msg if $verbose
+ end
+
+ # Perform a block with $verbose disabled.
+ def quiet(&block)
+ with_verbose(false, &block)
+ end
+
+ # Perform a block with $verbose enabled.
+ def verbose(&block)
+ with_verbose(true, &block)
+ end
+
+ # Perform a block with each file matching a set of wildcards.
+ def for_files(*wildcards)
+ wildcards.each do |wildcard|
+ Dir[wildcard].each do |fn|
+ yield(fn)
+ end
+ end
+ end
+
+ extend(self)
+
+ private # ----------------------------------------------------------
+
+ def for_matching_files(wildcard, dest_dir)
+ Dir[wildcard].each do |fn|
+ dest_file = File.join(dest_dir, fn)
+ parent = File.dirname(dest_file)
+ makedirs(parent) if ! File.directory?(parent)
+ yield(fn, dest_file)
+ end
+ end
+
+ def with_verbose(v)
+ oldverbose = $verbose
+ $verbose = v
+ yield
+ ensure
+ $verbose = oldverbose
+ end
+
+end
diff --git a/lib/rake/default_loader.rb b/lib/rake/default_loader.rb
new file mode 100644
index 0000000000..5dd3c05617
--- /dev/null
+++ b/lib/rake/default_loader.rb
@@ -0,0 +1,10 @@
+module Rake
+
+ # Default Rakefile loader used by +import+.
+ class DefaultLoader
+ def load(fn)
+ Rake.load_rakefile(File.expand_path(fn))
+ end
+ end
+
+end
diff --git a/lib/rake/dsl_definition.rb b/lib/rake/dsl_definition.rb
new file mode 100644
index 0000000000..e0b9c6fd0e
--- /dev/null
+++ b/lib/rake/dsl_definition.rb
@@ -0,0 +1,167 @@
+# Rake DSL functions.
+require 'rake/file_utils_ext'
+
+module Rake
+ module DSL
+
+ # 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 FileUtilsExt
+ private(*FileUtils.instance_methods(false))
+ private(*FileUtilsExt.instance_methods(false))
+
+ private
+
+ # 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)
+ name = name.to_s if name.kind_of?(Symbol)
+ name = name.to_str if name.respond_to?(:to_str)
+ unless name.kind_of?(String) || name.nil?
+ raise ArgumentError, "Expected a String or Symbol for a namespace name"
+ end
+ 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
+ end
+
+ module DeprecatedObjectDSL
+ Commands = Object.new.extend DSL
+ DSL.private_instance_methods(false).each do |name|
+ line = __LINE__+1
+ class_eval %{
+ def #{name}(*args, &block)
+ unless Rake.application.options.ignore_deprecate
+ unless @rake_dsl_warning
+ $stderr.puts "WARNING: Global access to Rake DSL methods is deprecated. Please include"
+ $stderr.puts " ... Rake::DSL into classes and modules which use the Rake DSL methods."
+ @rake_dsl_warning = true
+ end
+ $stderr.puts "WARNING: DSL method \#{self.class}##{name} called at \#{caller.first}"
+ end
+ Rake::DeprecatedObjectDSL::Commands.send(:#{name}, *args, &block)
+ end
+ private :#{name}
+ }, __FILE__, line
+ end
+ end
+
+ extend FileUtilsExt
+end
+
+self.extend Rake::DSL
+include Rake::DeprecatedObjectDSL
diff --git a/lib/rake/early_time.rb b/lib/rake/early_time.rb
new file mode 100644
index 0000000000..8c0e7d3339
--- /dev/null
+++ b/lib/rake/early_time.rb
@@ -0,0 +1,18 @@
+module Rake
+
+ # 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
diff --git a/lib/rake/ext/core.rb b/lib/rake/ext/core.rb
new file mode 100644
index 0000000000..1f3a738906
--- /dev/null
+++ b/lib/rake/ext/core.rb
@@ -0,0 +1,27 @@
+######################################################################
+# Core extension library
+#
+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 method_defined?(method)
+ $stderr.puts "WARNING: Possible conflict with Rake extension: #{self}##{method} already exists"
+ else
+ yield
+ end
+ end
+end
diff --git a/lib/rake/ext/module.rb b/lib/rake/ext/module.rb
new file mode 100644
index 0000000000..3f64aef6c8
--- /dev/null
+++ b/lib/rake/ext/module.rb
@@ -0,0 +1,39 @@
+require 'rake/ext/core'
+require 'rake/task'
+require 'rake/file_task'
+require 'rake/file_creation_task'
+require 'rake/application'
+require 'rake/task_manager'
+
+######################################################################
+# Rake extensions to Module.
+#
+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
diff --git a/lib/rake/ext/string.rb b/lib/rake/ext/string.rb
new file mode 100644
index 0000000000..fb22a9deb1
--- /dev/null
+++ b/lib/rake/ext/string.rb
@@ -0,0 +1,167 @@
+require 'rake/ext/core'
+
+######################################################################
+# Rake extension methods for 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
+ self.chomp(File.extname(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"). Multiple 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 << File.extname(self)
+ when '%X'
+ result << self.ext
+ 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
+
diff --git a/lib/rake/ext/time.rb b/lib/rake/ext/time.rb
new file mode 100644
index 0000000000..ca3ea2a795
--- /dev/null
+++ b/lib/rake/ext/time.rb
@@ -0,0 +1,14 @@
+# ###########################################################################
+# 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
+
diff --git a/lib/rake/file_creation_task.rb b/lib/rake/file_creation_task.rb
new file mode 100644
index 0000000000..c87e2192bb
--- /dev/null
+++ b/lib/rake/file_creation_task.rb
@@ -0,0 +1,24 @@
+require 'rake/file_task'
+require 'rake/early_time'
+
+module Rake
+
+ # 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
+
+end
diff --git a/lib/rake/file_list.rb b/lib/rake/file_list.rb
new file mode 100644
index 0000000000..9f780346f3
--- /dev/null
+++ b/lib/rake/file_list.rb
@@ -0,0 +1,403 @@
+require 'rake/cloneable'
+require 'rake/file_utils_ext'
+require 'rake/pathmap'
+
+######################################################################
+module Rake
+
+ # #########################################################################
+ # 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
+ @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 = []
+ 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 resolve_add(fn)
+ case fn
+ when %r{[*?\[\{]}
+ add_matching(fn)
+ else
+ self << fn
+ end
+ end
+ private :resolve_add
+
+ def resolve_exclude
+ 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 original 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 FileList 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 emacs style file:linenumber:line message will be printed to
+ # standard out. Returns the number of matched items.
+ def egrep(pattern, *options)
+ matched = 0
+ each do |fn|
+ begin
+ open(fn, "rb", *options) do |inf|
+ count = 0
+ inf.each do |line|
+ count += 1
+ if pattern.match(line)
+ matched += 1
+ if block_given?
+ yield fn, count, line
+ else
+ puts "#{fn}:#{count}:#{line}"
+ end
+ end
+ end
+ end
+ rescue StandardError => ex
+ $stderr.puts "Error while processing '#{fn}': #{ex}"
+ end
+ end
+ matched
+ 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)
+ return true if @exclude_patterns.any? do |pat|
+ case pat
+ when Regexp
+ fn =~ pat
+ when /[*?]/
+ File.fnmatch?(pat, fn, File::FNM_PATHNAME)
+ else
+ fn == pat
+ end
+ end
+ @exclude_procs.any? { |p| p.call(fn) }
+ end
+
+ DEFAULT_IGNORE_PATTERNS = [
+ /(^|[\/\\])CVS([\/\\]|$)/,
+ /(^|[\/\\])\.svn([\/\\]|$)/,
+ /\.bak$/,
+ /~$/
+ ]
+ DEFAULT_IGNORE_PROCS = [
+ proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) }
+ ]
+
+ 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
+end
+
+module Rake
+ class << self
+
+ # Yield each file or directory component.
+ def each_dir_parent(dir) # :nodoc:
+ 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
diff --git a/lib/rake/file_task.rb b/lib/rake/file_task.rb
new file mode 100644
index 0000000000..78902a86fd
--- /dev/null
+++ b/lib/rake/file_task.rb
@@ -0,0 +1,47 @@
+require 'rake/task.rb'
+require 'rake/early_time'
+
+module Rake
+ # #########################################################################
+ # 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?
+ ! File.exist?(name) || out_of_date?(timestamp)
+ 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, @scope].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
+end
+
diff --git a/lib/rake/file_utils.rb b/lib/rake/file_utils.rb
new file mode 100644
index 0000000000..9e0198e644
--- /dev/null
+++ b/lib/rake/file_utils.rb
@@ -0,0 +1,112 @@
+require 'rbconfig'
+require 'fileutils'
+
+# ###########################################################################
+# This a FileUtils extension that defines several additional commands to be
+# added to the FileUtils utility functions.
+#
+module FileUtils
+ # Path to the currently running Ruby program
+ RUBY = File.join(
+ RbConfig::CONFIG['bindir'],
+ RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']).
+ sub(/.*\s.*/m, '"\&"')
+
+ 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 : {}
+ shell_runner = block_given? ? block : create_shell_runner(cmd)
+ set_verbose_option(options)
+ options[:noop] ||= Rake::FileUtilsExt.nowrite_flag
+ Rake.rake_check_options options, :noop, :verbose
+ Rake.rake_output_message cmd.join(" ") if options[:verbose]
+ unless options[:noop]
+ res = rake_system(*cmd)
+ status = $?
+ status = PseudoStatus.new(1) if !res && status.nil?
+ shell_runner.call(res, status)
+ end
+ end
+
+ def create_shell_runner(cmd)
+ show_command = cmd.join(" ")
+ show_command = show_command[0,42] + "..." unless $trace
+ lambda { |ok, status|
+ ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]"
+ }
+ end
+ private :create_shell_runner
+
+ def set_verbose_option(options)
+ if options[:verbose].nil?
+ options[:verbose] = Rake::FileUtilsExt.verbose_flag.nil? || Rake::FileUtilsExt.verbose_flag
+ end
+ end
+ private :set_verbose_option
+
+ def rake_system(*cmd)
+ Rake::AltSystem.system(*cmd)
+ end
+ private :rake_system
+
+ # 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
+ 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
diff --git a/lib/rake/file_utils_ext.rb b/lib/rake/file_utils_ext.rb
new file mode 100644
index 0000000000..7c22f80687
--- /dev/null
+++ b/lib/rake/file_utils_ext.rb
@@ -0,0 +1,142 @@
+require 'rake/file_utils'
+
+module Rake
+ #
+ # FileUtilsExt provides a custom version of the FileUtils methods
+ # that respond to the <tt>verbose</tt> and <tt>nowrite</tt>
+ # commands.
+ #
+ module FileUtilsExt
+ include FileUtils
+
+ class << self
+ attr_accessor :verbose_flag, :nowrite_flag
+ end
+ FileUtilsExt.verbose_flag = nil
+ FileUtilsExt.nowrite_flag = false
+
+ $fileutils_verbose = true
+ $fileutils_nowrite = false
+
+ FileUtils::OPT_TABLE.each do |name, opts|
+ default_options = []
+ if opts.include?(:verbose) || opts.include?("verbose")
+ default_options << ':verbose => FileUtilsExt.verbose_flag'
+ end
+ if opts.include?(:noop) || opts.include?("noop")
+ default_options << ':noop => FileUtilsExt.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 = FileUtilsExt.verbose_flag
+ FileUtilsExt.verbose_flag = value unless value.nil?
+ if block_given?
+ begin
+ yield
+ ensure
+ FileUtilsExt.verbose_flag = oldvalue
+ end
+ end
+ FileUtilsExt.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 = FileUtilsExt.nowrite_flag
+ FileUtilsExt.nowrite_flag = value unless value.nil?
+ if block_given?
+ begin
+ yield
+ ensure
+ FileUtilsExt.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 FileUtilsExt.nowrite_flag
+ $stderr.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
+
+ # Send the message to the default rake output (which is $stderr).
+ def rake_output_message(message)
+ $stderr.puts(message)
+ end
+
+ # 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
+
+ extend self
+ end
+end
diff --git a/lib/rake/gempackagetask.rb b/lib/rake/gempackagetask.rb
index 3a99ff2b87..51e92236fc 100644
--- a/lib/rake/gempackagetask.rb
+++ b/lib/rake/gempackagetask.rb
@@ -1,95 +1,13 @@
-# Define a package task library to aid in the definition of GEM
-# packages.
+# rake/gempackagetask is deprecated in favor of rubygems/package_task
+
+warn 'rake/gempackagetask is deprecated. Use rubygems/package_task instead'
require 'rubygems'
+require 'rubygems/package_task'
+
require 'rake'
-require 'rake/packagetask'
-require 'rubygems/user_interaction'
-require 'rubygems/builder'
module Rake
-
- # Create a package based upon a Gem spec. Gem packages, as well as
- # zip files and tar/gzipped packages can be produced by this task.
- #
- # In addition to the Rake targets generated by PackageTask, a
- # GemPackageTask will also generate the following tasks:
- #
- # [<b>"<em>package_dir</em>/<em>name</em>-<em>version</em>.gem"</b>]
- # Create a Ruby GEM package with the given name and version.
- #
- # Example using a Ruby GEM spec:
- #
- # require 'rubygems'
- #
- # spec = Gem::Specification.new do |s|
- # s.platform = Gem::Platform::RUBY
- # s.summary = "Ruby based make-like utility."
- # s.name = 'rake'
- # s.version = PKG_VERSION
- # s.requirements << 'none'
- # s.require_path = 'lib'
- # s.autorequire = 'rake'
- # s.files = PKG_FILES
- # s.description = <<EOF
- # Rake is a Make-like program implemented in Ruby. Tasks
- # and dependencies are specified in standard Ruby syntax.
- # EOF
- # end
- #
- # Rake::GemPackageTask.new(spec) do |pkg|
- # pkg.need_zip = true
- # pkg.need_tar = true
- # end
- #
- class GemPackageTask < PackageTask
- # Ruby GEM spec containing the metadata for this package. The
- # name, version and package_files are automatically determined
- # from the GEM spec and don't need to be explicitly provided.
- attr_accessor :gem_spec
-
- # Create a GEM Package task library. Automatically define the gem
- # if a block is given. If no block is supplied, then +define+
- # needs to be called to define the task.
- def initialize(gem_spec)
- init(gem_spec)
- yield self if block_given?
- define if block_given?
- end
-
- # Initialization tasks without the "yield self" or define
- # operations.
- def init(gem)
- super(gem.name, gem.version)
- @gem_spec = gem
- @package_files += gem_spec.files if gem_spec.files
- end
-
- # Create the Rake tasks and actions specified by this
- # GemPackageTask. (+define+ is automatically called if a block is
- # given to +new+).
- def define
- super
- task :package => [:gem]
- desc "Build the gem file #{gem_file}"
- task :gem => ["#{package_dir}/#{gem_file}"]
- file "#{package_dir}/#{gem_file}" => [package_dir] + @gem_spec.files do
- when_writing("Creating GEM") {
- Gem::Builder.new(gem_spec).build
- verbose(true) {
- mv gem_file, "#{package_dir}/#{gem_file}"
- }
- }
- end
- end
-
- def gem_file
- if @gem_spec.platform == Gem::Platform::RUBY
- "#{package_name}.gem"
- else
- "#{package_name}-#{@gem_spec.platform}.gem"
- end
- end
-
- end
+ GemPackageTask = Gem::PackageTask
end
+
diff --git a/lib/rake/invocation_chain.rb b/lib/rake/invocation_chain.rb
new file mode 100644
index 0000000000..8a01ab4c29
--- /dev/null
+++ b/lib/rake/invocation_chain.rb
@@ -0,0 +1,51 @@
+module Rake
+
+ ####################################################################
+ # 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
+end
diff --git a/lib/rake/invocation_exception_mixin.rb b/lib/rake/invocation_exception_mixin.rb
new file mode 100644
index 0000000000..84ff3353ba
--- /dev/null
+++ b/lib/rake/invocation_exception_mixin.rb
@@ -0,0 +1,16 @@
+module Rake
+ module InvocationExceptionMixin
+ # Return the invocation chain (list of Rake tasks) that were in
+ # effect when this exception was detected by rake. May be null if
+ # no tasks were active.
+ def chain
+ @rake_invocation_chain ||= nil
+ end
+
+ # Set the invocation chain in effect when this exception was
+ # detected.
+ def chain=(value)
+ @rake_invocation_chain = value
+ end
+ end
+end
diff --git a/lib/rake/lib/project.rake b/lib/rake/lib/project.rake
new file mode 100644
index 0000000000..a5497328a7
--- /dev/null
+++ b/lib/rake/lib/project.rake
@@ -0,0 +1,21 @@
+task "create:project" => ["lib", "test", "Rakefile"]
+
+directory "lib"
+directory "test"
+
+file "Rakefile" do
+ File.open("Rakefile", "w") do |out|
+ out.puts %{# -*- ruby -*-
+
+require 'rake/clean'
+require 'rake/testtask'
+
+task :default => :test
+
+Rake::TestTask.new do |t|
+ t.verbose = false
+ t.test_files = FileList['test/test_*.rb']
+end
+}
+ end
+end
diff --git a/lib/rake/loaders/makefile.rb b/lib/rake/loaders/makefile.rb
index 9a2ac8090e..4ece4323af 100644
--- a/lib/rake/loaders/makefile.rb
+++ b/lib/rake/loaders/makefile.rb
@@ -2,11 +2,13 @@ module Rake
# Makefile loader to be used with the import file loader.
class MakefileLoader
+ include Rake::DSL
+
SPACE_MARK = "\0"
# Load the makefile dependencies in +fn+.
def load(fn)
- lines = open(fn) {|mf| mf.read}
+ lines = File.read fn
lines.gsub!(/\\ /, SPACE_MARK)
lines.gsub!(/#[^\n]*\n/m, "")
lines.gsub!(/\\\n/, ' ')
@@ -21,7 +23,7 @@ module Rake
def process_line(line)
file_tasks, args = line.split(':', 2)
return if args.nil?
- dependents = args.split.map {|arg| respace(arg)}
+ dependents = args.split.map { |d| respace(d) }
file_tasks.scan(/\S+/) do |file_task|
file_task = respace(file_task)
file file_task => dependents
@@ -29,7 +31,7 @@ module Rake
end
def respace(str)
- str.tr(SPACE_MARK, ' ')
+ str.tr SPACE_MARK, ' '
end
end
diff --git a/lib/rake/multi_task.rb b/lib/rake/multi_task.rb
new file mode 100644
index 0000000000..21c8de732f
--- /dev/null
+++ b/lib/rake/multi_task.rb
@@ -0,0 +1,16 @@
+module Rake
+
+ # Same as a regular task, but the immediate prerequisites are done in
+ # parallel using Ruby threads.
+ #
+ class MultiTask < Task
+ private
+ def invoke_prerequisites(args, invocation_chain)
+ threads = @prerequisites.collect { |p|
+ Thread.new(p) { |r| application[r, @scope].invoke_with_call_chain(args, invocation_chain) }
+ }
+ threads.each { |t| t.join }
+ end
+ end
+
+end
diff --git a/lib/rake/name_space.rb b/lib/rake/name_space.rb
new file mode 100644
index 0000000000..e1cc0940b8
--- /dev/null
+++ b/lib/rake/name_space.rb
@@ -0,0 +1,25 @@
+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 and nested namespaces.
+ def tasks
+ @task_manager.tasks_in_scope(@scope)
+ end
+ end
+end
diff --git a/lib/rake/packagetask.rb b/lib/rake/packagetask.rb
index e77345c198..08c1a8c025 100644
--- a/lib/rake/packagetask.rb
+++ b/lib/rake/packagetask.rb
@@ -72,7 +72,10 @@ module Rake
# Zip command for zipped archives. The default is 'zip'.
attr_accessor :zip_command
- # Create a Package Task with the given name and version.
+ # Create a Package Task with the given name and version. Use +:noversion+
+ # as the version to build a package without a version or to provide a
+ # fully-versioned package name.
+
def initialize(name=nil, version=nil)
init(name, version)
yield self if block_given?
diff --git a/lib/rake/pathmap.rb b/lib/rake/pathmap.rb
new file mode 100644
index 0000000000..2275724341
--- /dev/null
+++ b/lib/rake/pathmap.rb
@@ -0,0 +1 @@
+require 'rake/ext/string'
diff --git a/lib/rake/pseudo_status.rb b/lib/rake/pseudo_status.rb
new file mode 100644
index 0000000000..b58df3da18
--- /dev/null
+++ b/lib/rake/pseudo_status.rb
@@ -0,0 +1,24 @@
+module Rake
+
+ ####################################################################
+ # Exit status class for times the system just gives us a nil.
+ class PseudoStatus
+ attr_reader :exitstatus
+ def initialize(code=0)
+ @exitstatus = code
+ end
+ def to_i
+ @exitstatus << 8
+ end
+ def >>(n)
+ to_i >> n
+ end
+ def stopped?
+ false
+ end
+ def exited?
+ true
+ end
+ end
+
+end
diff --git a/lib/rake/rake_module.rb b/lib/rake/rake_module.rb
new file mode 100644
index 0000000000..a9d210c637
--- /dev/null
+++ b/lib/rake/rake_module.rb
@@ -0,0 +1,29 @@
+require 'rake/application'
+
+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
+
+ # Load a rakefile.
+ def load_rakefile(path)
+ load(path)
+ end
+ end
+
+end
diff --git a/lib/rake/rake_test_loader.rb b/lib/rake/rake_test_loader.rb
index 8d7dad3c94..045a12fdce 100644
--- a/lib/rake/rake_test_loader.rb
+++ b/lib/rake/rake_test_loader.rb
@@ -1,5 +1,13 @@
-#!/usr/bin/env ruby
+require 'rake'
# Load the test files from the command line.
-ARGV.each { |f| load f unless f =~ /^-/ }
+ARGV.each do |f|
+ next if f =~ /^-/
+
+ if f =~ /\*/
+ FileList[f].to_a.each { |fn| require File.expand_path(fn) }
+ else
+ require File.expand_path(f)
+ end
+end
diff --git a/lib/rake/rdoctask.rb b/lib/rake/rdoctask.rb
index 59758b85cd..5faf836b37 100644
--- a/lib/rake/rdoctask.rb
+++ b/lib/rake/rdoctask.rb
@@ -1,4 +1,232 @@
-warn 'rake/rdoctask is deprecated. Use rdoc/task instead'
+# rake/rdoctask is deprecated in favor of rdoc/task
-require 'rdoc/task'
+if Rake.application
+ Rake.application.deprecate('require \'rake/rdoctask\'', 'require \'rdoc/task\' (in RDoc 2.4.2+)', __FILE__)
+end
+
+require 'rubygems'
+
+begin
+ gem 'rdoc'
+ require 'rdoc'
+ require 'rdoc/task'
+rescue LoadError, Gem::LoadError
+end
+
+if defined?(RDoc::Task) then
+ module Rake
+ RDocTask = RDoc::Task unless const_defined? :RDocTask
+ end
+else
+ require 'rake'
+ require 'rake/tasklib'
+
+ module Rake
+
+ # NOTE: Rake::RDocTask is deprecated in favor of RDoc:Task which is included
+ # in RDoc 2.4.2+. Use require 'rdoc/task' to require it.
+ #
+ # Create a documentation task that will generate the RDoc files for
+ # a project.
+ #
+ # The RDocTask will create the following targets:
+ #
+ # [<b><em>rdoc</em></b>]
+ # Main task for this RDOC task.
+ #
+ # [<b>:clobber_<em>rdoc</em></b>]
+ # Delete all the rdoc files. This target is automatically
+ # added to the main clobber target.
+ #
+ # [<b>:re<em>rdoc</em></b>]
+ # Rebuild the rdoc files from scratch, even if they are not out
+ # of date.
+ #
+ # Simple Example:
+ #
+ # Rake::RDocTask.new do |rd|
+ # rd.main = "README.rdoc"
+ # rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
+ # end
+ #
+ # The +rd+ object passed to the block is an RDocTask object. See the
+ # attributes list for the RDocTask class for available customization options.
+ #
+ # == Specifying different task names
+ #
+ # You may wish to give the task a different name, such as if you are
+ # generating two sets of documentation. For instance, if you want to have a
+ # development set of documentation including private methods:
+ #
+ # Rake::RDocTask.new(:rdoc_dev) do |rd|
+ # rd.main = "README.doc"
+ # rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
+ # rd.options << "--all"
+ # end
+ #
+ # The tasks would then be named :<em>rdoc_dev</em>, :clobber_<em>rdoc_dev</em>, and
+ # :re<em>rdoc_dev</em>.
+ #
+ # If you wish to have completely different task names, then pass a Hash as
+ # first argument. With the <tt>:rdoc</tt>, <tt>:clobber_rdoc</tt> and
+ # <tt>:rerdoc</tt> options, you can customize the task names to your liking.
+ # For example:
+ #
+ # Rake::RDocTask.new(:rdoc => "rdoc", :clobber_rdoc => "rdoc:clean", :rerdoc => "rdoc:force")
+ #
+ # This will create the tasks <tt>:rdoc</tt>, <tt>:rdoc_clean</tt> and
+ # <tt>:rdoc:force</tt>.
+ #
+ class RDocTask < TaskLib
+ # Name of the main, top level task. (default is :rdoc)
+ attr_accessor :name
+
+ # Name of directory to receive the html output files. (default is "html")
+ attr_accessor :rdoc_dir
+
+ # Title of RDoc documentation. (defaults to rdoc's default)
+ attr_accessor :title
+
+ # Name of file to be used as the main, top level file of the
+ # RDoc. (default is none)
+ attr_accessor :main
+
+ # Name of template to be used by rdoc. (defaults to rdoc's default)
+ attr_accessor :template
+
+ # List of files to be included in the rdoc generation. (default is [])
+ attr_accessor :rdoc_files
+
+ # Additional list of options to be passed rdoc. (default is [])
+ attr_accessor :options
+
+ # Whether to run the rdoc process as an external shell (default is false)
+ attr_accessor :external
+
+ attr_accessor :inline_source
+
+ # Create an RDoc task with the given name. See the RDocTask class overview
+ # for documentation.
+ def initialize(name = :rdoc) # :yield: self
+ if name.is_a?(Hash)
+ invalid_options = name.keys.map { |k| k.to_sym } - [:rdoc, :clobber_rdoc, :rerdoc]
+ if !invalid_options.empty?
+ raise ArgumentError, "Invalid option(s) passed to RDocTask.new: #{invalid_options.join(", ")}"
+ end
+ end
+
+ @name = name
+ @rdoc_files = Rake::FileList.new
+ @rdoc_dir = 'html'
+ @main = nil
+ @title = nil
+ @template = nil
+ @external = false
+ @inline_source = true
+ @options = []
+ yield self if block_given?
+ define
+ end
+
+ # Create the tasks defined by this task lib.
+ def define
+ if rdoc_task_name != "rdoc"
+ desc "Build the RDOC HTML Files"
+ else
+ desc "Build the #{rdoc_task_name} HTML Files"
+ end
+ task rdoc_task_name
+
+ desc "Force a rebuild of the RDOC files"
+ task rerdoc_task_name => [clobber_task_name, rdoc_task_name]
+
+ desc "Remove rdoc products"
+ task clobber_task_name do
+ rm_r rdoc_dir rescue nil
+ end
+
+ task :clobber => [clobber_task_name]
+
+ directory @rdoc_dir
+ task rdoc_task_name => [rdoc_target]
+ file rdoc_target => @rdoc_files + [Rake.application.rakefile] do
+ rm_r @rdoc_dir rescue nil
+ @before_running_rdoc.call if @before_running_rdoc
+ args = option_list + @rdoc_files
+ if @external
+ argstring = args.join(' ')
+ sh %{ruby -Ivendor vendor/rd #{argstring}}
+ else
+ require 'rdoc/rdoc'
+ RDoc::RDoc.new.document(args)
+ end
+ end
+ self
+ end
+
+ def option_list
+ result = @options.dup
+ result << "-o" << @rdoc_dir
+ result << "--main" << quote(main) if main
+ result << "--title" << quote(title) if title
+ result << "-T" << quote(template) if template
+ result << "--inline-source" if inline_source && !@options.include?("--inline-source") && !@options.include?("-S")
+ result
+ end
+
+ def quote(str)
+ if @external
+ "'#{str}'"
+ else
+ str
+ end
+ end
+
+ def option_string
+ option_list.join(' ')
+ end
+
+ # The block passed to this method will be called just before running the
+ # RDoc generator. It is allowed to modify RDocTask attributes inside the
+ # block.
+ def before_running_rdoc(&block)
+ @before_running_rdoc = block
+ end
+
+ private
+
+ def rdoc_target
+ "#{rdoc_dir}/index.html"
+ end
+
+ def rdoc_task_name
+ case name
+ when Hash
+ (name[:rdoc] || "rdoc").to_s
+ else
+ name.to_s
+ end
+ end
+
+ def clobber_task_name
+ case name
+ when Hash
+ (name[:clobber_rdoc] || "clobber_rdoc").to_s
+ else
+ "clobber_#{name}"
+ end
+ end
+
+ def rerdoc_task_name
+ case name
+ when Hash
+ (name[:rerdoc] || "rerdoc").to_s
+ else
+ "re#{name}"
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/rake/ruby182_test_unit_fix.rb b/lib/rake/ruby182_test_unit_fix.rb
new file mode 100755
index 0000000000..9e411ed51a
--- /dev/null
+++ b/lib/rake/ruby182_test_unit_fix.rb
@@ -0,0 +1,25 @@
+# Local Rake override to fix bug in Ruby 0.8.2
+module Test # :nodoc:
+ # Local Rake override to fix bug in Ruby 0.8.2
+ module Unit # :nodoc:
+ # Local Rake override to fix bug in Ruby 0.8.2
+ module Collector # :nodoc:
+ # Local Rake override to fix bug in Ruby 0.8.2
+ class Dir # :nodoc:
+ undef collect_file
+ def collect_file(name, suites, already_gathered) # :nodoc:
+ dir = File.dirname(File.expand_path(name))
+ $:.unshift(dir) unless $:.first == dir
+ if(@req)
+ @req.require(name)
+ else
+ require(name)
+ end
+ find_test_cases(already_gathered).each{|t| add_suite(suites, t.suite)}
+ ensure
+ $:.delete_at $:.rindex(dir)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/rake/rule_recursion_overflow_error.rb b/lib/rake/rule_recursion_overflow_error.rb
new file mode 100644
index 0000000000..da4318da9d
--- /dev/null
+++ b/lib/rake/rule_recursion_overflow_error.rb
@@ -0,0 +1,20 @@
+
+module Rake
+
+ # Error indicating a recursion overflow error in task selection.
+ class RuleRecursionOverflowError < StandardError
+ def initialize(*args)
+ super
+ @targets = []
+ end
+
+ def add_target(target)
+ @targets << target
+ end
+
+ def message
+ super + ": [" + @targets.reverse.join(' => ') + "]"
+ end
+ end
+
+end
diff --git a/lib/rake/runtest.rb b/lib/rake/runtest.rb
index f6928d57b8..2b98a60cae 100644
--- a/lib/rake/runtest.rb
+++ b/lib/rake/runtest.rb
@@ -6,12 +6,12 @@ module Rake
def run_tests(pattern='test/test*.rb', log_enabled=false)
Dir["#{pattern}"].each { |fn|
- puts fn if log_enabled
+ $stderr.puts fn if log_enabled
begin
- load fn
+ require fn
rescue Exception => ex
- puts "Error in #{fn}: #{ex.message}"
- puts ex.backtrace
+ $stderr.puts "Error in #{fn}: #{ex.message}"
+ $stderr.puts ex.backtrace
assert false
end
}
diff --git a/lib/rake/task.rb b/lib/rake/task.rb
new file mode 100644
index 0000000000..f977d18711
--- /dev/null
+++ b/lib/rake/task.rb
@@ -0,0 +1,327 @@
+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
diff --git a/lib/rake/task_argument_error.rb b/lib/rake/task_argument_error.rb
new file mode 100644
index 0000000000..3e1dda64db
--- /dev/null
+++ b/lib/rake/task_argument_error.rb
@@ -0,0 +1,7 @@
+module Rake
+
+ # Error indicating an ill-formed task declaration.
+ class TaskArgumentError < ArgumentError
+ end
+
+end
diff --git a/lib/rake/task_arguments.rb b/lib/rake/task_arguments.rb
new file mode 100644
index 0000000000..ab404d67f5
--- /dev/null
+++ b/lib/rake/task_arguments.rb
@@ -0,0 +1,74 @@
+module Rake
+
+ ####################################################################
+ # TaskArguments manage the arguments passed to a task.
+ #
+ class TaskArguments
+ include Enumerable
+
+ attr_reader :names
+
+ # Create a TaskArgument object with a list of named arguments
+ # (given by :names) and a set of associated values (given by
+ # :values). :parent is the parent argument object.
+ def initialize(names, values, parent=nil)
+ @names = names
+ @parent = parent
+ @hash = {}
+ names.each_with_index { |name, i|
+ @hash[name.to_sym] = values[i] unless values[i].nil?
+ }
+ 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
+
+ # Specify a hash of default values for task arguments. Use the
+ # defaults only if there is no specific value for the given
+ # argument.
+ def with_defaults(defaults)
+ @hash = defaults.merge(@hash)
+ 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 @parent
+ @parent.lookup(name)
+ end
+ end
+ end
+
+ EMPTY_TASK_ARGS = TaskArguments.new([], [])
+end
diff --git a/lib/rake/task_manager.rb b/lib/rake/task_manager.rb
new file mode 100644
index 0000000000..4c3c26aa3a
--- /dev/null
+++ b/lib/rake/task_manager.rb
@@ -0,0 +1,307 @@
+module Rake
+
+ # 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, _, 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?
+ if Rake::TaskManager.record_task_metadata
+ add_location(task)
+ task.add_description(get_description(task))
+ end
+ task.enhance(deps, &block)
+ 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)
+ if args.last.is_a?(Hash)
+ deps = args.pop
+ resolve_args_with_dependencies(args, deps)
+ else
+ resolve_args_without_dependencies(args)
+ end
+ end
+
+ # Resolve task arguments for a task or rule when there are no
+ # dependencies declared.
+ #
+ # The patterns recognized by this argument resolving function are:
+ #
+ # task :t
+ # task :t, [:a]
+ # task :t, :a (deprecated)
+ #
+ def resolve_args_without_dependencies(args)
+ task_name = args.shift
+ if args.size == 1 && args.first.respond_to?(:to_ary)
+ arg_names = args.first.to_ary
+ else
+ arg_names = args
+ end
+ [task_name, arg_names, []]
+ end
+ private :resolve_args_without_dependencies
+
+ # Resolve task arguments for a task or rule when there are
+ # dependencies declared.
+ #
+ # The patterns recognized by this argument resolving function are:
+ #
+ # task :t => [:d]
+ # task :t, [a] => [:d]
+ # task :t, :needs => [:d] (deprecated)
+ # task :t, :a, :needs => [:d] (deprecated)
+ #
+ def resolve_args_with_dependencies(args, hash) # :nodoc:
+ fail "Task Argument Error" if hash.size != 1
+ key, value = hash.map { |k, v| [k,v] }.first
+ if args.empty?
+ task_name = key
+ arg_names = []
+ deps = value
+ elsif key == :needs
+ Rake.application.deprecate(
+ "task :t, arg, :needs => [deps]",
+ "task :t, [args] => [deps]",
+ caller.detect { |c| c !~ /\blib\/rake\b/ })
+ task_name = args.shift
+ arg_names = args
+ deps = value
+ else
+ task_name = args.shift
+ arg_names = key
+ deps = value
+ end
+ deps = [deps] unless deps.respond_to?(:to_ary)
+ [task_name, arg_names, deps]
+ end
+ private :resolve_args_with_dependencies
+
+ # 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 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
+
+ # List of all the tasks defined in the given scope (and its
+ # sub-scopes).
+ def tasks_in_scope(scope)
+ prefix = scope.join(":")
+ tasks.select { |t|
+ /^#{prefix}:/ =~ 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
+
+ # Add a location to the locations field of the given task.
+ def add_location(task)
+ loc = find_location
+ task.locations << loc if loc
+ task
+ end
+
+ # Find the location that called into the dsl layer.
+ def find_location
+ locations = caller
+ i = 0
+ while locations[i]
+ return locations[i+1] if locations[i] =~ /rake\/dsl_definition.rb/
+ i += 1
+ end
+ nil
+ end
+
+ # Generate an anonymous namespace name.
+ def generate_name
+ @seed ||= 0
+ @seed += 1
+ "_anon_#{@seed}"
+ end
+
+ def trace_rule(level, message)
+ $stderr.puts "#{" "*level}#{message}" if Rake.application.options.trace_rules
+ 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|
+ trace_rule level, "Attempting Rule #{task_name} => #{source}"
+ if File.exist?(source) || Rake::Task.task_defined?(source)
+ trace_rule level, "(#{task_name} => #{source} ... EXIST)"
+ source
+ elsif parent = enhance_with_matching_rule(source, level+1)
+ trace_rule level, "(#{task_name} => #{source} ... ENHANCE)"
+ parent.name
+ else
+ trace_rule level, "(#{task_name} => #{source} ... FAIL)"
+ 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)
+ result = 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
+ }
+ result.flatten
+ end
+
+
+ private
+
+ # Return the current description, clearing it in the process.
+ def get_description(task)
+ desc = @last_description
+ @last_description = nil
+ desc
+ end
+
+ class << self
+ attr_accessor :record_task_metadata
+ TaskManager.record_task_metadata = false
+ end
+ end
+
+end
diff --git a/lib/rake/tasklib.rb b/lib/rake/tasklib.rb
index a5a4494369..f1e17dad31 100644
--- a/lib/rake/tasklib.rb
+++ b/lib/rake/tasklib.rb
@@ -5,6 +5,7 @@ module Rake
# Base class for Task Libraries.
class TaskLib
include Cloneable
+ include Rake::DSL
# Make a symbol by pasting two strings together.
#
diff --git a/lib/rake/testtask.rb b/lib/rake/testtask.rb
index c400205ff3..04d3ae473a 100644
--- a/lib/rake/testtask.rb
+++ b/lib/rake/testtask.rb
@@ -93,33 +93,37 @@ module Rake
# Create the tasks defined by this task lib.
def define
- lib_path = @libs.join(File::PATH_SEPARATOR)
desc "Run tests" + (@name==:test ? "" : " for #{@name}")
task @name do
- run_code = ''
- RakeFileUtils.verbose(@verbose) do
- run_code =
- case @loader
- when :direct
- "-e 'ARGV.each{|f| load f}'"
- when :testrb
- "-S testrb"
- when :rake
- rake_loader
- end
- @ruby_opts.unshift( "-I\"#{lib_path}\"" )
- @ruby_opts.unshift( "-w" ) if @warning
- ruby @ruby_opts.join(" ") +
- " \"#{run_code}\" " +
- file_list.collect { |fn| "\"#{fn}\"" }.join(' ') +
- " #{option_list}"
+ FileUtilsExt.verbose(@verbose) do
+ ruby "#{ruby_opts_string} #{run_code} #{file_list_string} #{option_list}"
end
end
self
end
def option_list # :nodoc:
- ENV['TESTOPTS'] || @options || ""
+ (ENV['TESTOPTS'] ||
+ ENV['TESTOPT'] ||
+ ENV['TEST_OPTS'] ||
+ ENV['TEST_OPT'] ||
+ @options ||
+ "")
+ end
+
+ def ruby_opts_string
+ opts = @ruby_opts.dup
+ opts.unshift( "-I\"#{lib_path}\"" ) unless @libs.empty?
+ opts.unshift( "-w" ) if @warning
+ opts.join(" ")
+ end
+
+ def lib_path
+ @libs.join(File::PATH_SEPARATOR)
+ end
+
+ def file_list_string
+ file_list.collect { |fn| "\"#{fn}\"" }.join(' ')
end
def file_list # :nodoc:
@@ -128,8 +132,32 @@ module Rake
else
result = []
result += @test_files.to_a if @test_files
- result += FileList[ @pattern ].to_a if @pattern
- FileList[result]
+ result << @pattern if @pattern
+ result
+ end
+ end
+
+ def fix # :nodoc:
+ case ruby_version
+ when '1.8.2'
+ "\"#{find_file 'rake/ruby182_test_unit_fix'}\""
+ else
+ nil
+ end || ''
+ end
+
+ def ruby_version
+ RUBY_VERSION
+ end
+
+ def run_code
+ case @loader
+ when :direct
+ "-e \"ARGV.each{|f| require f}\""
+ when :testrb
+ "-S testrb #{fix}"
+ when :rake
+ "-I\"#{rake_lib_dir}\" \"#{rake_loader}\""
end
end
@@ -146,5 +174,18 @@ module Rake
nil
end
+ def rake_lib_dir # :nodoc:
+ find_dir('rake') or
+ fail "unable to find rake lib"
+ end
+
+ def find_dir(fn) # :nodoc:
+ $LOAD_PATH.each do |path|
+ file_path = File.join(path, "#{fn}.rb")
+ return path if File.exist? file_path
+ end
+ nil
+ end
+
end
end
diff --git a/lib/rake/version.rb b/lib/rake/version.rb
new file mode 100644
index 0000000000..dbad21e52d
--- /dev/null
+++ b/lib/rake/version.rb
@@ -0,0 +1,10 @@
+module Rake
+ module Version
+ NUMBERS = [
+ MAJOR = 0,
+ MINOR = 9,
+ BUILD = 2,
+ ]
+ end
+ VERSION = Version::NUMBERS.join('.')
+end
diff --git a/lib/rake/win32.rb b/lib/rake/win32.rb
index 0ab31c2822..98289a10b4 100644
--- a/lib/rake/win32.rb
+++ b/lib/rake/win32.rb
@@ -1,46 +1,55 @@
+
module Rake
+ require 'rake/alt_system'
# Win 32 interface methods for Rake. Windows specific functionality
# will be placed here to collect that knowledge in one spot.
module Win32
+
+ # Error indicating a problem in locating the home directory on a
+ # Win32 system.
+ class Win32HomeError < RuntimeError
+ end
+
class << self
# True if running on a windows system.
- if File::ALT_SEPARATOR == '\\' # assume other DOSish systems are extinct.
- def windows?; true end
- else
- def windows?; false end
+ def windows?
+ AltSystem::WINDOWS
+ end
+
+ # Run a command line on windows.
+ def rake_system(*cmd)
+ AltSystem.system(*cmd)
end
- end
- class << self
# The standard directory containing system wide rake files on
# Win 32 systems. Try the following environment variables (in
# order):
#
- # * APPDATA
# * HOME
# * HOMEDRIVE + HOMEPATH
+ # * APPDATA
# * USERPROFILE
#
- # If the above are not defined, retruns the personal folder.
+ # If the above are not defined, the return nil.
def win32_system_dir #:nodoc:
- win32_shared_path = ENV['APPDATA']
- if !win32_shared_path or win32_shared_path.empty?
- win32_shared_path = '~'
+ win32_shared_path = ENV['HOME']
+ if win32_shared_path.nil? && ENV['HOMEDRIVE'] && ENV['HOMEPATH']
+ win32_shared_path = ENV['HOMEDRIVE'] + ENV['HOMEPATH']
end
- File.expand_path('Rake', win32_shared_path)
+
+ win32_shared_path ||= ENV['APPDATA']
+ win32_shared_path ||= ENV['USERPROFILE']
+ raise Win32HomeError, "Unable to determine home path environment variable." if
+ win32_shared_path.nil? or win32_shared_path.empty?
+ normalize(File.join(win32_shared_path, 'Rake'))
end
# Normalize a win32 path so that the slashes are all forward slashes.
def normalize(path)
- path.tr('\\', '/')
+ path.gsub(/\\/, '/')
end
- end if windows?
- end
- if Win32.windows?
- def standard_system_dir
- Win32.win32_system_dir
end
end
end