diff options
Diffstat (limited to 'jni/ruby/lib/rake')
58 files changed, 4875 insertions, 0 deletions
diff --git a/jni/ruby/lib/rake/alt_system.rb b/jni/ruby/lib/rake/alt_system.rb new file mode 100644 index 0000000..aa7b779 --- /dev/null +++ b/jni/ruby/lib/rake/alt_system.rb @@ -0,0 +1,110 @@ +# +# 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. +#-- +# TODO: Remove in Rake 11 + +module Rake::AltSystem # :nodoc: all + 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 && 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| + test = "#{file}.#{ext}" + return test if File.exist?(test) + } + 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/jni/ruby/lib/rake/application.rb b/jni/ruby/lib/rake/application.rb new file mode 100644 index 0000000..bd72a2e --- /dev/null +++ b/jni/ruby/lib/rake/application.rb @@ -0,0 +1,790 @@ +require 'shellwords' +require 'optparse' + +require 'rake/task_manager' +require 'rake/file_list' +require 'rake/thread_pool' +require 'rake/thread_history_display' +require 'rake/trace_output' +require 'rake/win32' + +module Rake + + CommandLineOptionError = Class.new(StandardError) + + ## + # Rake main application object. When invoking +rake+ from the + # command line, a Rake::Application object is created and run. + + class Application + include TaskManager + include TraceOutput + + # 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 (+top_level+). + # + # 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 + args = handle_options + collect_command_line_tasks(args) + 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 + run_with_threads 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 + + # Run the given block with the thread startup and shutdown. + def run_with_threads + thread_pool.gather_history if options.job_stats == :history + + yield + + thread_pool.join + if options.job_stats + stats = thread_pool.statistics + puts "Maximum active threads: #{stats[:max_active_threads]} + main" + puts "Total threads in play: #{stats[:total_threads_in_play]} + main" + end + ThreadHistoryDisplay.new(thread_pool.history).show if + options.job_stats == :history + 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 + + # Return the thread pool used for multithreaded processing. + def thread_pool # :nodoc: + @thread_pool ||= ThreadPool.new(options.thread_pool_size || Rake.suggested_thread_count-1) + end + + # internal ---------------------------------------------------------------- + + # Invokes a task with arguments that are extracted from +task_string+ + def invoke_task(task_string) # :nodoc: + name, args = parse_task_string(task_string) + t = self[name] + t.invoke(*args) + end + + def parse_task_string(string) # :nodoc: + /^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s + + name = $1 + remaining_args = $2 + + return string, [] unless name + return name, [] if remaining_args.empty? + + args = [] + + begin + /((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args + + remaining_args = $2 + args << $1.gsub(/\\(.)/, '\1') + end while remaining_args + + return name, args + end + + # Provide standard exception handling for the given block. + def standard_exception_handling # :nodoc: + yield + rescue SystemExit + # 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_because_of_exception(ex) + end + + # Exit the program because of an unhandle exception. + # (may be overridden by subclasses) + def exit_because_of_exception(ex) # :nodoc: + exit(false) + end + + # Display the error message that caused the exception. + def display_error_message(ex) # :nodoc: + trace "#{name} aborted!" + display_exception_details(ex) + trace "Tasks: #{ex.chain}" if has_chain?(ex) + trace "(See full trace by running task with --trace)" unless + options.backtrace + end + + def display_exception_details(ex) # :nodoc: + seen = Thread.current[:rake_display_exception_details_seen] ||= [] + return if seen.include? ex + seen << ex + + display_exception_message_details(ex) + display_exception_backtrace(ex) + display_exception_details(ex.cause) if has_cause?(ex) + end + + def has_cause?(ex) # :nodoc: + ex.respond_to?(:cause) && ex.cause + end + + def display_exception_message_details(ex) # :nodoc: + if ex.instance_of?(RuntimeError) + trace ex.message + else + trace "#{ex.class.name}: #{ex.message}" + end + end + + def display_exception_backtrace(ex) # :nodoc: + if options.backtrace + trace ex.backtrace.join("\n") + else + trace Backtrace.collapse(ex.backtrace).join("\n") + end + end + + # Warn about deprecated usage. + # + # Example: + # Rake.application.deprecate("import", "Rake.import", caller.first) + # + def deprecate(old_usage, new_usage, call_site) # :nodoc: + unless options.ignore_deprecate + $stderr.puts "WARNING: '#{old_usage}' is deprecated. " + + "Please use '#{new_usage}' instead.\n" + + " at #{call_site}" + end + end + + # Does the exception have a task invocation chain? + def has_chain?(exception) # :nodoc: + 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 # :nodoc: + @rakefiles.each do |fn| + if File.exist?(fn) + others = FileList.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? # :nodoc: + @tty_output + end + + # Override the detected TTY output state (mostly for testing) + def tty_output=(tty_output_state) # :nodoc: + @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? # :nodoc: + tty_output? || @terminal_columns.nonzero? + end + + # Display the tasks and comments. + def display_tasks_and_comments # :nodoc: + displayable_tasks = tasks.select { |t| + (options.show_all_tasks || t.comment) && + t.name =~ options.show_task_pattern + } + case options.show_tasks + when :tasks + width = displayable_tasks.map { |t| t.name_with_args.length }.max || 10 + if truncate_output? + max_column = terminal_width - name.size - width - 7 + else + max_column = nil + end + + 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}" + comment = t.full_comment || "" + 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 # :nodoc: + 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 # :nodoc: + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty # :nodoc: + %x{stty size 2>/dev/null}.split[1].to_i + end + + def dynamic_width_tput # :nodoc: + %x{tput cols 2>/dev/null}.to_i + end + + def unix? # :nodoc: + RbConfig::CONFIG['host_os'] =~ + /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + end + + def windows? # :nodoc: + Win32.windows? + end + + def truncate(string, width) # :nodoc: + if string.nil? + "" + elsif string.length <= width + string + else + (string[0, width - 3] || "") + "..." + end + end + + # Display the tasks and prerequisites + def display_prerequisites # :nodoc: + tasks.each do |t| + puts "#{name} #{t.name}" + t.prerequisites.each { |pre| puts " #{pre}" } + end + end + + def trace(*strings) # :nodoc: + options.trace_output ||= $stderr + trace_on(options.trace_output, *strings) + end + + def sort_options(options) # :nodoc: + options.sort_by { |opt| + opt.select { |o| o =~ /^-/ }.map { |o| o.downcase }.sort.reverse + } + end + private :sort_options + + # A list of all the standard options used in rake, suitable for + # passing to OptionParser. + def standard_rake_options # :nodoc: + sort_options( + [ + ['--all', '-A', + "Show all tasks, even uncommented ones (in combination with -T or -D)", + lambda { |value| + options.show_all_tasks = value + } + ], + ['--backtrace=[OUT]', + "Enable full backtrace. OUT can be stderr (default) or stdout.", + lambda { |value| + options.backtrace = true + select_trace_output(options, 'backtrace', value) + } + ], + ['--build-all', '-B', + "Build all prerequisites, including those which are up-to-date.", + lambda { |value| + options.build_all = true + } + ], + ['--comments', + "Show commented tasks only", + lambda { |value| + options.show_all_tasks = !value + } + ], + ['--describe', '-D [PATTERN]', + "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + select_tasks_to_show(options, :describe, value) + } + ], + ['--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) } + ], + ['--jobs', '-j [NUMBER]', + "Specifies the maximum number of tasks to execute in parallel. " + + "(default is number of CPU cores + 4)", + lambda { |value| + if value.nil? || value == '' + value = FIXNUM_MAX + elsif value =~ /^\d+$/ + value = value.to_i + else + value = Rake.suggested_thread_count + end + value = 1 if value < 1 + options.thread_pool_size = value - 1 + } + ], + ['--job-stats [LEVEL]', + "Display job statistics. " + + "LEVEL=history displays a complete job list", + lambda { |value| + if value =~ /^history/i + options.job_stats = :history + else + options.job_stats = true + end + } + ], + ['--libdir', '-I LIBDIR', + "Include LIBDIR in the search path for required modules.", + lambda { |value| $:.push(value) } + ], + ['--multitask', '-m', + "Treat all tasks as multitasks.", + lambda { |value| options.always_multitask = true } + ], + ['--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 [FILENAME]', + "Use FILENAME as the rakefile to search for.", + 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(File::PATH_SEPARATOR) + } + ], + ['--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 + } + ], + ['--suppress-backtrace PATTERN', + "Suppress backtrace lines matching regexp PATTERN. " + + "Ignored if --trace is on.", + lambda { |value| + options.suppress_backtrace_pattern = Regexp.new(value) + } + ], + ['--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| + select_tasks_to_show(options, :tasks, value) + } + ], + ['--trace=[OUT]', '-t', + "Turn on invoke/execute tracing, enable full backtrace. " + + "OUT can be stderr (default) or stdout.", + lambda { |value| + options.trace = true + options.backtrace = true + select_trace_output(options, 'trace', value) + 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| + select_tasks_to_show(options, :lines, value) + options.show_all_tasks = true + } + ], + ['--no-deprecation-warnings', '-X', + "Disable the deprecation warnings.", + lambda { |value| + options.ignore_deprecate = true + } + ], + ]) + end + + def select_tasks_to_show(options, show_tasks, value) # :nodoc: + options.show_tasks = show_tasks + options.show_task_pattern = Regexp.new(value || '') + Rake::TaskManager.record_task_metadata = true + end + private :select_tasks_to_show + + def select_trace_output(options, trace_option, value) # :nodoc: + value = value.strip unless value.nil? + case value + when 'stdout' + options.trace_output = $stdout + when 'stderr', nil + options.trace_output = $stderr + else + fail CommandLineOptionError, + "Unrecognized --#{trace_option} option '#{value}'" + end + end + private :select_trace_output + + # Read and handle the command line options. Returns the command line + # arguments that we didn't understand, which should (in theory) be just + # task names and env vars. + def handle_options # :nodoc: + options.rakelib = ['rakelib'] + options.trace_output = $stderr + + OptionParser.new do |opts| + opts.banner = "#{Rake.application.name} [-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(ARGV) + 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=$") # :nodoc: + 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 # :nodoc: + here = Dir.pwd + until (fn = have_rakefile) + Dir.chdir("..") + return nil if Dir.pwd == here || options.nosearch + here = Dir.pwd + end + [fn, here] + ensure + Dir.chdir(Rake.original_dir) + end + + def print_rakefile_directory(location) # :nodoc: + $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) + 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) # :nodoc: + FileList.glob(path.gsub("\\", '/')).each(&block) + end + private :glob + + # The directory path containing the system wide rakefiles. + def system_dir # :nodoc: + @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. + # + # `args` is the list of arguments to peruse to get the list of tasks. + # It should be the command line that was given to rake, less any + # recognised command-line options, which OptionParser.parse will + # have taken care of already. + def collect_command_line_tasks(args) # :nodoc: + @top_level_tasks = [] + args.each do |arg| + if arg =~ /^(\w+)=(.*)$/m + ENV[$1] = $2 + else + @top_level_tasks << arg unless arg =~ /^-/ + end + end + @top_level_tasks.push(default_task_name) if @top_level_tasks.empty? + end + + # Default task name ("default"). + # (May be overridden by subclasses) + def default_task_name # :nodoc: + "default" + end + + # Add a file to the list of files to be imported. + def add_import(fn) # :nodoc: + @pending_imports << fn + end + + # Load the pending list of imported files. + def load_imports # :nodoc: + while fn = @pending_imports.shift + next if @imported.member?(fn) + fn_task = lookup(fn) and fn_task.invoke + ext = File.extname(fn) + loader = @loaders[ext] || @default_loader + loader.load(fn) + if fn_task = lookup(fn) and fn_task.needed? + fn_task.reenable + fn_task.invoke + loader.load(fn) + end + @imported << fn + end + end + + def rakefile_location(backtrace=caller) # :nodoc: + backtrace.map { |t| t[/([^:]+):/, 1] } + + re = /^#{@rakefile}$/ + re = /#{re.source}/i if windows? + + backtrace.find { |str| str =~ re } || '' + end + + private + FIXNUM_MAX = (2**(0.size * 8 - 2) - 1) # :nodoc: + + end +end diff --git a/jni/ruby/lib/rake/backtrace.rb b/jni/ruby/lib/rake/backtrace.rb new file mode 100644 index 0000000..dc18773 --- /dev/null +++ b/jni/ruby/lib/rake/backtrace.rb @@ -0,0 +1,23 @@ +module Rake + module Backtrace # :nodoc: all + SYS_KEYS = RbConfig::CONFIG.keys.grep(/(?:[a-z]prefix|libdir)\z/) + SYS_PATHS = RbConfig::CONFIG.values_at(*SYS_KEYS).uniq + + [ File.join(File.dirname(__FILE__), "..") ] + + SUPPRESSED_PATHS = SYS_PATHS. + map { |s| s.gsub("\\", "/") }. + map { |f| File.expand_path(f) }. + reject { |s| s.nil? || s =~ /^ *$/ } + SUPPRESSED_PATHS_RE = SUPPRESSED_PATHS.map { |f| Regexp.quote(f) }.join("|") + SUPPRESSED_PATHS_RE << "|^org\\/jruby\\/\\w+\\.java" if + Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby' + + SUPPRESS_PATTERN = %r!(\A(#{SUPPRESSED_PATHS_RE})|bin/rake:\d+)!i + + def self.collapse(backtrace) + pattern = Rake.application.options.suppress_backtrace_pattern || + SUPPRESS_PATTERN + backtrace.reject { |elem| elem =~ pattern } + end + end +end diff --git a/jni/ruby/lib/rake/clean.rb b/jni/ruby/lib/rake/clean.rb new file mode 100644 index 0000000..a49cd44 --- /dev/null +++ b/jni/ruby/lib/rake/clean.rb @@ -0,0 +1,76 @@ +# The 'rake/clean' file defines two file lists (CLEAN and CLOBBER) and +# two rake tasks (:clean and :clobber). +# +# [:clean] Clean up the project by deleting scratch files and backup +# files. Add files to the CLEAN file list to have the :clean +# target handle them. +# +# [:clobber] Clobber all generated and non-source files in a project. +# The task depends on :clean, so all the clean files will +# be deleted as well as files in the CLOBBER file list. +# The intent of this task is to return a project to its +# pristine, just unpacked state. + +require 'rake' + +# :stopdoc: + +module Rake + module Cleaner + extend FileUtils + + module_function + + def cleanup_files(file_names) + file_names.each do |file_name| + cleanup(file_name) + end + end + + def cleanup(file_name, opts={}) + begin + rm_r file_name, opts + rescue StandardError => ex + puts "Failed to remove #{file_name}: #{ex}" unless file_already_gone?(file_name) + end + end + + def file_already_gone?(file_name) + return false if File.exist?(file_name) + + path = file_name + prev = nil + + while path = File.dirname(path) + return false if cant_be_deleted?(path) + break if [prev, "."].include?(path) + prev = path + end + true + end + private_class_method :file_already_gone? + + def cant_be_deleted?(path_name) + File.exist?(path_name) && + (!File.readable?(path_name) || !File.executable?(path_name)) + end + private_class_method :cant_be_deleted? + end +end + +CLEAN = ::Rake::FileList["**/*~", "**/*.bak", "**/core"] +CLEAN.clear_exclude.exclude { |fn| + fn.pathmap("%f").downcase == 'core' && File.directory?(fn) +} + +desc "Remove any temporary products." +task :clean do + Rake::Cleaner.cleanup_files(CLEAN) +end + +CLOBBER = ::Rake::FileList.new + +desc "Remove any generated file." +task :clobber => [:clean] do + Rake::Cleaner.cleanup_files(CLOBBER) +end diff --git a/jni/ruby/lib/rake/cloneable.rb b/jni/ruby/lib/rake/cloneable.rb new file mode 100644 index 0000000..d53645f --- /dev/null +++ b/jni/ruby/lib/rake/cloneable.rb @@ -0,0 +1,16 @@ +module Rake + ## + # Mixin for creating easily cloned objects. + + module Cloneable # :nodoc: + # The hook that is invoked by 'clone' and 'dup' methods. + def initialize_copy(source) + super + source.instance_variables.each do |var| + src_value = source.instance_variable_get(var) + value = src_value.clone rescue src_value + instance_variable_set(var, value) + end + end + end +end diff --git a/jni/ruby/lib/rake/contrib/.document b/jni/ruby/lib/rake/contrib/.document new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/jni/ruby/lib/rake/contrib/.document @@ -0,0 +1 @@ + diff --git a/jni/ruby/lib/rake/contrib/compositepublisher.rb b/jni/ruby/lib/rake/contrib/compositepublisher.rb new file mode 100644 index 0000000..69952a0 --- /dev/null +++ b/jni/ruby/lib/rake/contrib/compositepublisher.rb @@ -0,0 +1,21 @@ +module Rake + + # Manage several publishers as a single entity. + class CompositePublisher + def initialize + @publishers = [] + end + + # Add a publisher to the composite. + def add(pub) + @publishers << pub + end + + # Upload all the individual publishers. + def upload + @publishers.each { |p| p.upload } + end + end + +end + diff --git a/jni/ruby/lib/rake/contrib/ftptools.rb b/jni/ruby/lib/rake/contrib/ftptools.rb new file mode 100644 index 0000000..b178523 --- /dev/null +++ b/jni/ruby/lib/rake/contrib/ftptools.rb @@ -0,0 +1,137 @@ +# = Tools for FTP uploading. +# +# This file is still under development and is not released for general +# use. + +require 'date' +require 'net/ftp' +require 'rake/file_list' + +module Rake # :nodoc: + + class FtpFile # :nodoc: all + attr_reader :name, :size, :owner, :group, :time + + def self.date + @date_class ||= Date + end + + def self.time + @time_class ||= Time + end + + def initialize(path, entry) + @path = path + @mode, _, @owner, @group, size, d1, d2, d3, @name = entry.split(' ') + @size = size.to_i + @time = determine_time(d1, d2, d3) + end + + def path + File.join(@path, @name) + end + + def directory? + @mode[0] == ?d + end + + def mode + parse_mode(@mode) + end + + def symlink? + @mode[0] == ?l + end + + private # -------------------------------------------------------- + + def parse_mode(m) + result = 0 + (1..9).each do |i| + result = 2 * result + ((m[i] == ?-) ? 0 : 1) + end + result + end + + def determine_time(d1, d2, d3) + now = self.class.time.now + if /:/ !~ d3 + result = Time.parse("#{d1} #{d2} #{d3}") + else + result = Time.parse("#{d1} #{d2} #{now.year} #{d3}") + result = Time.parse("#{d1} #{d2} #{now.year - 1} #{d3}") if + result > now + end + result + end + end + + ## + # Manage the uploading of files to an FTP account. + class FtpUploader # :nodoc: + + # Log uploads to standard output when true. + attr_accessor :verbose + + class << FtpUploader + # Create an uploader and pass it to the given block as +up+. + # When the block is complete, close the uploader. + def connect(path, host, account, password) + up = self.new(path, host, account, password) + begin + yield(up) + ensure + up.close + end + end + end + + # 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) + @created = Hash.new + @path = path + @ftp = Net::FTP.new(host, account, password) + makedirs(@path) + @ftp.chdir(@path) + end + + # Create the directory +path+ in the uploader root path. + def makedirs(path) + route = [] + File.split(path).each do |dir| + route << dir + current_dir = File.join(route) + if @created[current_dir].nil? + @created[current_dir] = true + $stderr.puts "Creating Directory #{current_dir}" if @verbose + @ftp.mkdir(current_dir) rescue nil + end + end + end + + # Upload all files matching +wildcard+ to the uploader's root + # path. + def upload_files(wildcard) + FileList.glob(wildcard).each do |fn| + upload(fn) + end + end + + # Close the uploader. + def close + @ftp.close + end + + private # -------------------------------------------------------- + + # Upload a single file to the uploader's root path. + def upload(file) + $stderr.puts "Uploading #{file}" if @verbose + dir = File.dirname(file) + makedirs(dir) + @ftp.putbinaryfile(file, file) unless File.directory?(file) + end + end +end diff --git a/jni/ruby/lib/rake/contrib/publisher.rb b/jni/ruby/lib/rake/contrib/publisher.rb new file mode 100644 index 0000000..f4ee1ab --- /dev/null +++ b/jni/ruby/lib/rake/contrib/publisher.rb @@ -0,0 +1,81 @@ +# Copyright 2003-2010 by Jim Weirich (jim.weirich@gmail.com) +# All rights reserved. + +# :stopdoc: + +# Configuration information about an upload host system. +# name :: Name of host system. +# webdir :: Base directory for the web information for the +# application. The application name (APP) is appended to +# this directory before using. +# pkgdir :: Directory on the host system where packages can be +# placed. +HostInfo = Struct.new(:name, :webdir, :pkgdir) + +# :startdoc: + +# TODO: Move to contrib/sshpublisher +#-- +# Manage several publishers as a single entity. +class CompositePublisher # :nodoc: + def initialize + @publishers = [] + end + + # Add a publisher to the composite. + def add(pub) + @publishers << pub + end + + # Upload all the individual publishers. + def upload + @publishers.each { |p| p.upload } + end +end + +# TODO: Remove in Rake 11, duplicated +#-- +# Publish an entire directory to an existing remote directory using +# SSH. +class SshDirPublisher # :nodoc: all + def initialize(host, remote_dir, local_dir) + @host = host + @remote_dir = remote_dir + @local_dir = local_dir + end + + def upload + run %{scp -rq #{@local_dir}/* #{@host}:#{@remote_dir}} + end +end + +# TODO: Remove in Rake 11, duplicated +#-- +# Publish an entire directory to a fresh remote directory using SSH. +class SshFreshDirPublisher < SshDirPublisher # :nodoc: all + def upload + run %{ssh #{@host} rm -rf #{@remote_dir}} rescue nil + run %{ssh #{@host} mkdir #{@remote_dir}} + super + end +end + +# TODO: Remove in Rake 11, duplicated +#-- +# Publish a list of files to an existing remote directory. +class SshFilePublisher # :nodoc: all + # Create a publisher using the give host information. + def initialize(host, remote_dir, local_dir, *files) + @host = host + @remote_dir = remote_dir + @local_dir = local_dir + @files = files + end + + # Upload the local directory to the remote directory. + def upload + @files.each do |fn| + run %{scp -q #{@local_dir}/#{fn} #{@host}:#{@remote_dir}} + end + end +end diff --git a/jni/ruby/lib/rake/contrib/rubyforgepublisher.rb b/jni/ruby/lib/rake/contrib/rubyforgepublisher.rb new file mode 100644 index 0000000..00889ad --- /dev/null +++ b/jni/ruby/lib/rake/contrib/rubyforgepublisher.rb @@ -0,0 +1,18 @@ +# TODO: Remove in Rake 11 + +require 'rake/contrib/sshpublisher' + +module Rake + + class RubyForgePublisher < SshDirPublisher # :nodoc: all + attr_reader :project, :proj_id, :user + + def initialize(projname, user) + super( + "#{user}@rubyforge.org", + "/var/www/gforge-projects/#{projname}", + "html") + end + end + +end diff --git a/jni/ruby/lib/rake/contrib/sshpublisher.rb b/jni/ruby/lib/rake/contrib/sshpublisher.rb new file mode 100644 index 0000000..64f5770 --- /dev/null +++ b/jni/ruby/lib/rake/contrib/sshpublisher.rb @@ -0,0 +1,61 @@ +require 'rake/dsl_definition' +require 'rake/contrib/compositepublisher' + +module Rake + + # Publish an entire directory to an existing remote directory using + # SSH. + class SshDirPublisher + include Rake::DSL + + # Creates an SSH publisher which will scp all files in +local_dir+ to + # +remote_dir+ on +host+ + + def initialize(host, remote_dir, local_dir) + @host = host + @remote_dir = remote_dir + @local_dir = local_dir + end + + # Uploads the files + + def upload + sh "scp", "-rq", "#{@local_dir}/*", "#{@host}:#{@remote_dir}" + end + end + + # Publish an entire directory to a fresh remote directory using SSH. + class SshFreshDirPublisher < SshDirPublisher + + # Uploads the files after removing the existing remote directory. + + def upload + sh "ssh", @host, "rm", "-rf", @remote_dir rescue nil + sh "ssh", @host, "mkdir", @remote_dir + super + end + end + + # Publish a list of files to an existing remote directory. + class SshFilePublisher + include Rake::DSL + + # Creates an SSH publisher which will scp all +files+ in +local_dir+ to + # +remote_dir+ on +host+. + + def initialize(host, remote_dir, local_dir, *files) + @host = host + @remote_dir = remote_dir + @local_dir = local_dir + @files = files + end + + # Uploads the files + + def upload + @files.each do |fn| + sh "scp", "-q", "#{@local_dir}/#{fn}", "#{@host}:#{@remote_dir}" + end + end + end +end diff --git a/jni/ruby/lib/rake/contrib/sys.rb b/jni/ruby/lib/rake/contrib/sys.rb new file mode 100644 index 0000000..8d4c735 --- /dev/null +++ b/jni/ruby/lib/rake/contrib/sys.rb @@ -0,0 +1,4 @@ +# TODO: Remove in Rake 11 + +fail "ERROR: 'rake/contrib/sys' is obsolete and no longer supported. " + + "Use 'FileUtils' instead." diff --git a/jni/ruby/lib/rake/cpu_counter.rb b/jni/ruby/lib/rake/cpu_counter.rb new file mode 100644 index 0000000..f29778e --- /dev/null +++ b/jni/ruby/lib/rake/cpu_counter.rb @@ -0,0 +1,125 @@ +module Rake + + # Based on a script at: + # http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed + class CpuCounter # :nodoc: all + def self.count + new.count_with_default + end + + def count_with_default(default=4) + count || default + rescue StandardError + default + end + + begin + require 'etc' + rescue LoadError + else + if Etc.respond_to?(:nprocessors) + def count + return Etc.nprocessors + end + end + end + end +end + +unless Rake::CpuCounter.method_defined?(:count) + Rake::CpuCounter.class_eval <<-'end;', __FILE__, __LINE__+1 + require 'rbconfig' + + # TODO: replace with IO.popen using array-style arguments in Rake 11 + require 'open3' + + def count + if defined?(Java::Java) + count_via_java_runtime + else + case RbConfig::CONFIG['host_os'] + when /darwin9/ + count_via_hwprefs_cpu_count + when /darwin/ + count_via_hwprefs_thread_count || count_via_sysctl + when /linux/ + count_via_cpuinfo + when /bsd/ + count_via_sysctl + when /mswin|mingw/ + count_via_win32 + else + # Try everything + count_via_win32 || + count_via_sysctl || + count_via_hwprefs_thread_count || + count_via_hwprefs_cpu_count || + count_via_cpuinfo + end + end + end + + def count_via_java_runtime + Java::Java.lang.Runtime.getRuntime.availableProcessors + rescue StandardError + nil + end + + def count_via_win32 + require 'win32ole' + wmi = WIN32OLE.connect("winmgmts://") + cpu = wmi.ExecQuery("select NumberOfCores from Win32_Processor") # TODO count hyper-threaded in this + cpu.to_enum.first.NumberOfCores + rescue StandardError, LoadError + nil + end + + def count_via_cpuinfo + open('/proc/cpuinfo') { |f| f.readlines }.grep(/processor/).size + rescue StandardError + nil + end + + def count_via_hwprefs_thread_count + run 'hwprefs', 'thread_count' + end + + def count_via_hwprefs_cpu_count + run 'hwprefs', 'cpu_count' + end + + def count_via_sysctl + run 'sysctl', '-n', 'hw.ncpu' + end + + def run(command, *args) + cmd = resolve_command(command) + if cmd + Open3.popen3 cmd, *args do |inn, out, err,| + inn.close + err.read + out.read.to_i + end + else + nil + end + end + + def resolve_command(command) + look_for_command("/usr/sbin", command) || + look_for_command("/sbin", command) || + in_path_command(command) + end + + def look_for_command(dir, command) + path = File.join(dir, command) + File.exist?(path) ? path : nil + end + + def in_path_command(command) + Open3.popen3 'which', command do |_, out,| + out.eof? ? nil : command + end + end + end; +end diff --git a/jni/ruby/lib/rake/default_loader.rb b/jni/ruby/lib/rake/default_loader.rb new file mode 100644 index 0000000..6154408 --- /dev/null +++ b/jni/ruby/lib/rake/default_loader.rb @@ -0,0 +1,14 @@ +module Rake + + # Default Rakefile loader used by +import+. + class DefaultLoader + + ## + # Loads a rakefile into the current application from +fn+ + + def load(fn) + Rake.load_rakefile(File.expand_path(fn)) + end + end + +end diff --git a/jni/ruby/lib/rake/dsl_definition.rb b/jni/ruby/lib/rake/dsl_definition.rb new file mode 100644 index 0000000..26f4ca8 --- /dev/null +++ b/jni/ruby/lib/rake/dsl_definition.rb @@ -0,0 +1,201 @@ +# Rake DSL functions. +require 'rake/file_utils_ext' + +module Rake + + ## + # DSL is a module that provides #task, #desc, #namespace, etc. Use this + # when you'd like to use rake outside the top level scope. + # + # For a Rakefile you run from the comamnd line this module is automatically + # included. + + 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 + + # :call-seq: + # task task_name + # task task_name: dependencies + # task task_name, arguments => dependencies + # task task_name, argument[, argument ...], :needs: dependencies + # + # Declare a basic task. The +task_name+ is always the first argument. If + # the task name contains a ":" it is defined in that namespace. + # + # The +dependencies+ may be a single task name or an Array of task names. + # The +argument+ (a single name) or +arguments+ (an Array of names) define + # the arguments provided to the task. + # + # The task, argument and dependency names may be either symbols or + # strings. + # + # A task with a single dependency: + # + # task clobber: %w[clean] do + # rm_rf "html" + # end + # + # A task with an argument and a dependency: + # + # task :package, [:version] => :test do |t, args| + # # ... + # end + # + # To invoke this task from the command line: + # + # $ rake package[1.2.3] + # + # Alternate definition: + # + # task :package, :version, needs: :test do |t, args| + # # ... + # end + # + def task(*args, &block) # :doc: + 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) # :doc: + 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(*args, &block) # :doc: + result = file_create(*args, &block) + dir, _ = *Rake.application.resolve_args(args) + dir = Rake.from_pathname(dir) + Rake.each_dir_parent(dir) do |d| + file_create d do |t| + mkdir_p t.name unless File.exist?(t.name) + end + end + result + 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: %w[deploy_gem deploy_rdoc] + # + def multitask(*args, &block) # :doc: + 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. + # + # Example: + # + # ns = namespace "nested" do + # # the "nested:run" task + # task :run + # end + # task_run = ns[:run] # find :run in the given namespace. + # + # Tasks can also be defined in a namespace by using a ":" in the task + # name: + # + # task "nested:test" do + # # ... + # end + # + def namespace(name=nil, &block) # :doc: + 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) # :doc: + Rake::Task.create_rule(*args, &block) + end + + # Describes the next rake task. Duplicate descriptions are discarded. + # Descriptions are shown with <code>rake -T</code> (up to the first + # sentence) and <code>rake -D</code> (the entire description). + # + # Example: + # desc "Run the Unit Tests" + # task test: [:build] + # # ... run tests + # end + # + def desc(description) # :doc: + 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) # :doc: + fns.each do |fn| + Rake.application.add_import(fn) + end + end + end + extend FileUtilsExt +end + +# Extend the main object with the DSL commands. This allows top-level +# calls to task, etc. to work from a Rakefile without polluting the +# object inheritance tree. +self.extend Rake::DSL diff --git a/jni/ruby/lib/rake/early_time.rb b/jni/ruby/lib/rake/early_time.rb new file mode 100644 index 0000000..abcb187 --- /dev/null +++ b/jni/ruby/lib/rake/early_time.rb @@ -0,0 +1,21 @@ +module Rake + + # EarlyTime is a fake timestamp that occurs _before_ any other time value. + class EarlyTime + include Comparable + include Singleton + + ## + # The EarlyTime always comes before +other+! + + def <=>(other) + -1 + end + + def to_s # :nodoc: + "<EARLY TIME>" + end + end + + EARLY = EarlyTime.instance +end diff --git a/jni/ruby/lib/rake/ext/core.rb b/jni/ruby/lib/rake/ext/core.rb new file mode 100644 index 0000000..7575df1 --- /dev/null +++ b/jni/ruby/lib/rake/ext/core.rb @@ -0,0 +1,25 @@ +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) # :nodoc: + if method_defined?(method) + $stderr.puts "WARNING: Possible conflict with Rake extension: " + + "#{self}##{method} already exists" + else + yield + end + end +end diff --git a/jni/ruby/lib/rake/ext/module.rb b/jni/ruby/lib/rake/ext/module.rb new file mode 100644 index 0000000..3ee155f --- /dev/null +++ b/jni/ruby/lib/rake/ext/module.rb @@ -0,0 +1,2 @@ + +# TODO: remove in Rake 11 diff --git a/jni/ruby/lib/rake/ext/pathname.rb b/jni/ruby/lib/rake/ext/pathname.rb new file mode 100644 index 0000000..49e2cd4 --- /dev/null +++ b/jni/ruby/lib/rake/ext/pathname.rb @@ -0,0 +1,25 @@ +require 'rake/ext/core' +require 'pathname' + +class Pathname + + rake_extension("ext") do + # Return a new Pathname with <tt>String#ext</tt> applied to it. + # + # This Pathname extension comes from Rake + def ext(newext='') + Pathname.new(Rake.from_pathname(self).ext(newext)) + end + end + + rake_extension("pathmap") do + # Apply the pathmap spec to the Pathname, returning a + # new Pathname with the modified paths. (See String#pathmap for + # details.) + # + # This Pathname extension comes from Rake + def pathmap(spec=nil, &block) + Pathname.new(Rake.from_pathname(self).pathmap(spec, &block)) + end + end +end diff --git a/jni/ruby/lib/rake/ext/string.rb b/jni/ruby/lib/rake/ext/string.rb new file mode 100644 index 0000000..b47b055 --- /dev/null +++ b/jni/ruby/lib/rake/ext/string.rb @@ -0,0 +1,173 @@ +require 'rake/ext/core' + +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. + # + # This String extension comes from Rake + def ext(newext='') + return self.dup if ['.', '..'].include? self + newext = (newext =~ /^\./) ? newext : ("." + newext) if newext != '' + self.chomp(File.extname(self)) << newext + end + end + + rake_extension("pathmap") do + # Explode a path into individual components. Used by +pathmap+. + # + # This String extension comes from Rake + 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. + # + # This String extension comes from Rake + 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 + + # Perform the pathmap replacement operations on the given path. The + # patterns take the form 'pat1,rep1;pat2,rep2...'. + # + # This String extension comes from Rake + 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: + # + # <tt>%p</tt> :: The complete path. + # <tt>%f</tt> :: The base file name of the path, with its file extension, + # but without any directories. + # <tt>%n</tt> :: The file name of the path without its file extension. + # <tt>%d</tt> :: The directory list of the path. + # <tt>%x</tt> :: The file extension of the path. An empty string if there + # is no extension. + # <tt>%X</tt> :: Everything *but* the file extension. + # <tt>%s</tt> :: The alternate file separator if defined, otherwise use # + # the standard file separator. + # <tt>%%</tt> :: A percent sign. + # + # The <tt>%d</tt> 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 <tt>%d</tt>, <tt>%p</tt>, <tt>%f</tt>, <tt>%n</tt>, + # <tt>%x</tt>, and <tt>%X</tt> 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,class}X.class") + # + # returns: + # + # "class/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" + # + # This String extension comes from Rake + 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 diff --git a/jni/ruby/lib/rake/ext/time.rb b/jni/ruby/lib/rake/ext/time.rb new file mode 100644 index 0000000..d3b8cf9 --- /dev/null +++ b/jni/ruby/lib/rake/ext/time.rb @@ -0,0 +1,16 @@ +#-- +# Extensions to time to allow comparisons with early and late time classes. + +require 'rake/early_time' +require 'rake/late_time' + +class Time # :nodoc: all + alias rake_original_time_compare :<=> + def <=>(other) + if Rake::EarlyTime === other || Rake::LateTime === other + - other.<=>(self) + else + rake_original_time_compare(other) + end + end +end diff --git a/jni/ruby/lib/rake/file_creation_task.rb b/jni/ruby/lib/rake/file_creation_task.rb new file mode 100644 index 0000000..c87e219 --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/rake/file_list.rb b/jni/ruby/lib/rake/file_list.rb new file mode 100644 index 0000000..006ec77 --- /dev/null +++ b/jni/ruby/lib/rake/file_list.rb @@ -0,0 +1,428 @@ +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[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). + map { |s| s.to_s }.sort.uniq + + # Now do the delegation. + DELEGATING_METHODS.each do |sym| + 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 << Rake.from_pathname(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 << Rake.from_pathname(pat) + end + @exclude_procs << block if block_given? + resolve_exclude unless @pending + self + end + + # Clear all the exclude patterns so that we exclude nothing. + def clear_exclude + @exclude_patterns = [] + @exclude_procs = [] + self + end + + # A FileList is equal through array 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 + + def <<(obj) + resolve + @items << Rake.from_pathname(obj) + self + 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) # :nodoc: + case fn + when %r{[*?\[\{]} + add_matching(fn) + else + self << fn + end + end + private :resolve_add + + def resolve_exclude # :nodoc: + reject! { |fn| excluded_from_list?(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, "r", *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) + FileList.glob(pattern).each do |fn| + self << fn unless excluded_from_list?(fn) + end + end + private :add_matching + + # Should the given file name be excluded from the list? + # + # NOTE: This method was formerly named "exclude?", but Rails + # introduced an exclude? method as an array method and setup a + # conflict with file list. We renamed the method to avoid + # confusion. If you were using "FileList#exclude?" in your user + # code, you will need to update. + def excluded_from_list?(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) # :nodoc: + @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 + + # Get a sorted list of files matching the pattern. This method + # should be preferred to Dir[pattern] and Dir.glob(pattern) because + # the files returned are guaranteed to be sorted. + def glob(pattern, *args) + Dir.glob(pattern, *args).sort + 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 + + # Convert Pathname and Pathname-like objects to strings; + # leave everything else alone + def from_pathname(path) # :nodoc: + path = path.to_path if path.respond_to?(:to_path) + path = path.to_str if path.respond_to?(:to_str) + path + end + end +end # module Rake diff --git a/jni/ruby/lib/rake/file_task.rb b/jni/ruby/lib/rake/file_task.rb new file mode 100644 index 0000000..4c9b040 --- /dev/null +++ b/jni/ruby/lib/rake/file_task.rb @@ -0,0 +1,46 @@ +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) || @application.options.build_all + end + + # Time stamp for file task. + def timestamp + if File.exist?(name) + File.mtime(name.to_s) + else + Rake::LATE + 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) + Rake.from_pathname(task_name) + end + end + end +end diff --git a/jni/ruby/lib/rake/file_utils.rb b/jni/ruby/lib/rake/file_utils.rb new file mode 100644 index 0000000..27f4e2e --- /dev/null +++ b/jni/ruby/lib/rake/file_utils.rb @@ -0,0 +1,128 @@ +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 = ENV['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 run directly (without the shell, same semantics as Kernel::exec and + # Kernel::system). + # + # It is recommended you use the multiple argument form over interpolating + # user input for both usability and security reasons. With the multiple + # argument form you can easily process files with spaces or other shell + # reserved characters in them. With the multiple argument form your rake + # tasks are not vulnerable to users providing an argument like + # <code>; rm # -rf /</code>. + # + # If a block is given, upon command completion the block is called with an + # OK flag (true on a zero exit status) and a Process::Status object. + # Without a block a RuntimeError is raised when the command exits non-zero. + # + # Examples: + # + # 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 = Rake::PseudoStatus.new(1) if !res && status.nil? + shell_runner.call(res, status) + end + end + + def create_shell_runner(cmd) # :nodoc: + show_command = cmd.join(" ") + show_command = show_command[0, 42] + "..." unless $trace + lambda do |ok, status| + ok or + fail "Command failed with status (#{status.exitstatus}): " + + "[#{show_command}]" + end + end + private :create_shell_runner + + def set_verbose_option(options) # :nodoc: + unless options.key? :verbose + options[:verbose] = + (Rake::FileUtilsExt.verbose_flag == Rake::FileUtilsExt::DEFAULT) || + Rake::FileUtilsExt.verbose_flag + end + end + private :set_verbose_option + + def rake_system(*cmd) # :nodoc: + 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 + 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) + if ! 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/jni/ruby/lib/rake/file_utils_ext.rb b/jni/ruby/lib/rake/file_utils_ext.rb new file mode 100644 index 0000000..309159a --- /dev/null +++ b/jni/ruby/lib/rake/file_utils_ext.rb @@ -0,0 +1,144 @@ +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 + + DEFAULT = Object.new + + FileUtilsExt.verbose_flag = DEFAULT + FileUtilsExt.nowrite_flag = false + + FileUtils.commands.each do |name| + opts = FileUtils.options_of name + default_options = [] + if opts.include?("verbose") + default_options << ':verbose => FileUtilsExt.verbose_flag' + end + if 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/jni/ruby/lib/rake/gempackagetask.rb b/jni/ruby/lib/rake/gempackagetask.rb new file mode 100644 index 0000000..16e7ce0 --- /dev/null +++ b/jni/ruby/lib/rake/gempackagetask.rb @@ -0,0 +1,4 @@ +# TODO: Remove in Rake 11 + +fail "ERROR: 'rake/gempackagetask' is obsolete and no longer supported. " + + "Use 'rubygems/package_task' instead." diff --git a/jni/ruby/lib/rake/invocation_chain.rb b/jni/ruby/lib/rake/invocation_chain.rb new file mode 100644 index 0000000..5406289 --- /dev/null +++ b/jni/ruby/lib/rake/invocation_chain.rb @@ -0,0 +1,56 @@ +module Rake + + # InvocationChain tracks the chain of task invocations to detect + # circular dependencies. + class InvocationChain < LinkedList + + # Is the invocation already in the chain? + def member?(invocation) + head == invocation || tail.member?(invocation) + end + + # Append an invocation to the chain of invocations. It is an error + # if the invocation already listed. + def append(invocation) + if member?(invocation) + fail RuntimeError, "Circular dependency detected: #{to_s} => #{invocation}" + end + conj(invocation) + end + + # Convert to string, ie: TOP => invocation => invocation + def to_s + "#{prefix}#{head}" + end + + # Class level append. + def self.append(invocation, chain) + chain.append(invocation) + end + + private + + def prefix + "#{tail} => " + end + + # Null object for an empty chain. + class EmptyInvocationChain < LinkedList::EmptyLinkedList + @parent = InvocationChain + + def member?(obj) + false + end + + def append(invocation) + conj(invocation) + end + + def to_s + "TOP" + end + end + + EMPTY = EmptyInvocationChain.new + end +end diff --git a/jni/ruby/lib/rake/invocation_exception_mixin.rb b/jni/ruby/lib/rake/invocation_exception_mixin.rb new file mode 100644 index 0000000..84ff335 --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/rake/late_time.rb b/jni/ruby/lib/rake/late_time.rb new file mode 100644 index 0000000..d959a78 --- /dev/null +++ b/jni/ruby/lib/rake/late_time.rb @@ -0,0 +1,17 @@ +module Rake + # LateTime is a fake timestamp that occurs _after_ any other time value. + class LateTime + include Comparable + include Singleton + + def <=>(other) + 1 + end + + def to_s + '<LATE TIME>' + end + end + + LATE = LateTime.instance +end diff --git a/jni/ruby/lib/rake/linked_list.rb b/jni/ruby/lib/rake/linked_list.rb new file mode 100644 index 0000000..b5ab797 --- /dev/null +++ b/jni/ruby/lib/rake/linked_list.rb @@ -0,0 +1,103 @@ +module Rake + + # Polylithic linked list structure used to implement several data + # structures in Rake. + class LinkedList + include Enumerable + + attr_reader :head, :tail + + def initialize(head, tail=EMPTY) + @head = head + @tail = tail + end + + # Polymorphically add a new element to the head of a list. The + # type of head node will be the same list type as the tail. + def conj(item) + self.class.cons(item, self) + end + + # Is the list empty? + def empty? + false + end + + # Lists are structurally equivalent. + def ==(other) + current = self + while ! current.empty? && ! other.empty? + return false if current.head != other.head + current = current.tail + other = other.tail + end + current.empty? && other.empty? + end + + # Convert to string: LL(item, item...) + def to_s + items = map { |item| item.to_s }.join(", ") + "LL(#{items})" + end + + # Same as +to_s+, but with inspected items. + def inspect + items = map { |item| item.inspect }.join(", ") + "LL(#{items})" + end + + # For each item in the list. + def each + current = self + while ! current.empty? + yield(current.head) + current = current.tail + end + self + end + + # Make a list out of the given arguments. This method is + # polymorphic + def self.make(*args) + result = empty + args.reverse_each do |item| + result = cons(item, result) + end + result + end + + # Cons a new head onto the tail list. + def self.cons(head, tail) + new(head, tail) + end + + # The standard empty list class for the given LinkedList class. + def self.empty + self::EMPTY + end + + # Represent an empty list, using the Null Object Pattern. + # + # When inheriting from the LinkedList class, you should implement + # a type specific Empty class as well. Make sure you set the class + # instance variable @parent to the associated list class (this + # allows conj, cons and make to work polymorphically). + class EmptyLinkedList < LinkedList + @parent = LinkedList + + def initialize + end + + def empty? + true + end + + def self.cons(head, tail) + @parent.cons(head, tail) + end + end + + EMPTY = EmptyLinkedList.new + end + +end diff --git a/jni/ruby/lib/rake/loaders/makefile.rb b/jni/ruby/lib/rake/loaders/makefile.rb new file mode 100644 index 0000000..4ece432 --- /dev/null +++ b/jni/ruby/lib/rake/loaders/makefile.rb @@ -0,0 +1,40 @@ +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 = File.read fn + lines.gsub!(/\\ /, SPACE_MARK) + lines.gsub!(/#[^\n]*\n/m, "") + lines.gsub!(/\\\n/, ' ') + lines.each_line do |line| + process_line(line) + end + end + + private + + # Process one logical line of makefile data. + def process_line(line) + file_tasks, args = line.split(':', 2) + return if args.nil? + dependents = args.split.map { |d| respace(d) } + file_tasks.scan(/\S+/) do |file_task| + file_task = respace(file_task) + file file_task => dependents + end + end + + def respace(str) + str.tr SPACE_MARK, ' ' + end + end + + # Install the handler + Rake.application.add_loader('mf', MakefileLoader.new) +end diff --git a/jni/ruby/lib/rake/multi_task.rb b/jni/ruby/lib/rake/multi_task.rb new file mode 100644 index 0000000..5418a7a --- /dev/null +++ b/jni/ruby/lib/rake/multi_task.rb @@ -0,0 +1,13 @@ +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(task_args, invocation_chain) # :nodoc: + invoke_prerequisites_concurrently(task_args, invocation_chain) + end + end + +end diff --git a/jni/ruby/lib/rake/name_space.rb b/jni/ruby/lib/rake/name_space.rb new file mode 100644 index 0000000..58f911e --- /dev/null +++ b/jni/ruby/lib/rake/name_space.rb @@ -0,0 +1,38 @@ +## +# The NameSpace class will lookup task names in the scope defined by a +# +namespace+ command. + +class Rake::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 + + ## + # The scope of the namespace (a LinkedList) + + def scope + @scope.dup + end + + ## + # Return the list of tasks defined in this and nested namespaces. + + def tasks + @task_manager.tasks_in_scope(@scope) + end + +end + diff --git a/jni/ruby/lib/rake/packagetask.rb b/jni/ruby/lib/rake/packagetask.rb new file mode 100644 index 0000000..249ee72 --- /dev/null +++ b/jni/ruby/lib/rake/packagetask.rb @@ -0,0 +1,199 @@ +# Define a package task library to aid in the definition of +# redistributable package files. + +require 'rake' +require 'rake/tasklib' + +module Rake + + # Create a packaging task that will package the project into + # distributable files (e.g zip archive or tar files). + # + # The PackageTask will create the following targets: + # + # +:package+ :: + # Create all the requested package files. + # + # +:clobber_package+ :: + # Delete all the package files. This target is automatically + # added to the main clobber target. + # + # +:repackage+ :: + # Rebuild the package files from scratch, even if they are not out + # of date. + # + # <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tgz"</tt> :: + # Create a gzipped tar package (if <em>need_tar</em> is true). + # + # <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tar.gz"</tt> :: + # Create a gzipped tar package (if <em>need_tar_gz</em> is true). + # + # <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.tar.bz2"</tt> :: + # Create a bzip2'd tar package (if <em>need_tar_bz2</em> is true). + # + # <tt>"<em>package_dir</em>/<em>name</em>-<em>version</em>.zip"</tt> :: + # Create a zip package archive (if <em>need_zip</em> is true). + # + # Example: + # + # Rake::PackageTask.new("rake", "1.2.3") do |p| + # p.need_tar = true + # p.package_files.include("lib/**/*.rb") + # end + # + class PackageTask < TaskLib + # Name of the package (from the GEM Spec). + attr_accessor :name + + # Version of the package (e.g. '1.3.2'). + attr_accessor :version + + # Directory used to store the package files (default is 'pkg'). + attr_accessor :package_dir + + # True if a gzipped tar file (tgz) should be produced (default is + # false). + attr_accessor :need_tar + + # True if a gzipped tar file (tar.gz) should be produced (default + # is false). + attr_accessor :need_tar_gz + + # True if a bzip2'd tar file (tar.bz2) should be produced (default + # is false). + attr_accessor :need_tar_bz2 + + # True if a zip file should be produced (default is false) + attr_accessor :need_zip + + # List of files to be included in the package. + attr_accessor :package_files + + # Tar command for gzipped or bzip2ed archives. The default is 'tar'. + attr_accessor :tar_command + + # Zip command for zipped archives. The default is 'zip'. + attr_accessor :zip_command + + # 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? + define unless name.nil? + end + + # Initialization that bypasses the "yield self" and "define" step. + def init(name, version) + @name = name + @version = version + @package_files = Rake::FileList.new + @package_dir = 'pkg' + @need_tar = false + @need_tar_gz = false + @need_tar_bz2 = false + @need_zip = false + @tar_command = 'tar' + @zip_command = 'zip' + end + + # Create the tasks defined by this task library. + def define + fail "Version required (or :noversion)" if @version.nil? + @version = nil if :noversion == @version + + desc "Build all the packages" + task :package + + desc "Force a rebuild of the package files" + task :repackage => [:clobber_package, :package] + + desc "Remove package products" + task :clobber_package do + rm_r package_dir rescue nil + end + + task :clobber => [:clobber_package] + + [ + [need_tar, tgz_file, "z"], + [need_tar_gz, tar_gz_file, "z"], + [need_tar_bz2, tar_bz2_file, "j"] + ].each do |(need, file, flag)| + if need + task :package => ["#{package_dir}/#{file}"] + file "#{package_dir}/#{file}" => + [package_dir_path] + package_files do + chdir(package_dir) do + sh @tar_command, "#{flag}cvf", file, package_name + end + end + end + end + + if need_zip + task :package => ["#{package_dir}/#{zip_file}"] + file "#{package_dir}/#{zip_file}" => + [package_dir_path] + package_files do + chdir(package_dir) do + sh @zip_command, "-r", zip_file, package_name + end + end + end + + directory package_dir_path => @package_files do + @package_files.each do |fn| + f = File.join(package_dir_path, fn) + fdir = File.dirname(f) + mkdir_p(fdir) unless File.exist?(fdir) + if File.directory?(fn) + mkdir_p(f) + else + rm_f f + safe_ln(fn, f) + end + end + end + self + end + + # The name of this package + + def package_name + @version ? "#{@name}-#{@version}" : @name + end + + # The directory this package will be built in + + def package_dir_path + "#{package_dir}/#{package_name}" + end + + # The package name with .tgz added + + def tgz_file + "#{package_name}.tgz" + end + + # The package name with .tar.gz added + + def tar_gz_file + "#{package_name}.tar.gz" + end + + # The package name with .tar.bz2 added + + def tar_bz2_file + "#{package_name}.tar.bz2" + end + + # The package name with .zip added + + def zip_file + "#{package_name}.zip" + end + end + +end diff --git a/jni/ruby/lib/rake/pathmap.rb b/jni/ruby/lib/rake/pathmap.rb new file mode 100644 index 0000000..9a840cd --- /dev/null +++ b/jni/ruby/lib/rake/pathmap.rb @@ -0,0 +1,3 @@ +# TODO: Remove in Rake 11 + +require 'rake/ext/string' diff --git a/jni/ruby/lib/rake/phony.rb b/jni/ruby/lib/rake/phony.rb new file mode 100644 index 0000000..29633ae --- /dev/null +++ b/jni/ruby/lib/rake/phony.rb @@ -0,0 +1,15 @@ +# Defines a :phony task that you can use as a dependency. This allows +# file-based tasks to use non-file-based tasks as prerequisites +# without forcing them to rebuild. +# +# See FileTask#out_of_date? and Task#timestamp for more info. + +require 'rake' + +task :phony + +Rake::Task[:phony].tap do |task| + def task.timestamp # :nodoc: + Time.at 0 + end +end diff --git a/jni/ruby/lib/rake/private_reader.rb b/jni/ruby/lib/rake/private_reader.rb new file mode 100644 index 0000000..1620978 --- /dev/null +++ b/jni/ruby/lib/rake/private_reader.rb @@ -0,0 +1,20 @@ +module Rake + + # Include PrivateReader to use +private_reader+. + module PrivateReader # :nodoc: all + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + + # Declare a list of private accessors + def private_reader(*names) + attr_reader(*names) + private(*names) + end + end + + end +end diff --git a/jni/ruby/lib/rake/promise.rb b/jni/ruby/lib/rake/promise.rb new file mode 100644 index 0000000..31c4563 --- /dev/null +++ b/jni/ruby/lib/rake/promise.rb @@ -0,0 +1,99 @@ +module Rake + + # A Promise object represents a promise to do work (a chore) in the + # future. The promise is created with a block and a list of + # arguments for the block. Calling value will return the value of + # the promised chore. + # + # Used by ThreadPool. + # + class Promise # :nodoc: all + NOT_SET = Object.new.freeze # :nodoc: + + attr_accessor :recorder + + # Create a promise to do the chore specified by the block. + def initialize(args, &block) + @mutex = Mutex.new + @result = NOT_SET + @error = NOT_SET + @args = args + @block = block + end + + # Return the value of this promise. + # + # If the promised chore is not yet complete, then do the work + # synchronously. We will wait. + def value + unless complete? + stat :sleeping_on, :item_id => object_id + @mutex.synchronize do + stat :has_lock_on, :item_id => object_id + chore + stat :releasing_lock_on, :item_id => object_id + end + end + error? ? raise(@error) : @result + end + + # If no one else is working this promise, go ahead and do the chore. + def work + stat :attempting_lock_on, :item_id => object_id + if @mutex.try_lock + stat :has_lock_on, :item_id => object_id + chore + stat :releasing_lock_on, :item_id => object_id + @mutex.unlock + else + stat :bailed_on, :item_id => object_id + end + end + + private + + # Perform the chore promised + def chore + if complete? + stat :found_completed, :item_id => object_id + return + end + stat :will_execute, :item_id => object_id + begin + @result = @block.call(*@args) + rescue Exception => e + @error = e + end + stat :did_execute, :item_id => object_id + discard + end + + # Do we have a result for the promise + def result? + ! @result.equal?(NOT_SET) + end + + # Did the promise throw an error + def error? + ! @error.equal?(NOT_SET) + end + + # Are we done with the promise + def complete? + result? || error? + end + + # free up these items for the GC + def discard + @args = nil + @block = nil + end + + # Record execution statistics if there is a recorder + def stat(*args) + @recorder.call(*args) if @recorder + end + + end + +end diff --git a/jni/ruby/lib/rake/pseudo_status.rb b/jni/ruby/lib/rake/pseudo_status.rb new file mode 100644 index 0000000..16e1903 --- /dev/null +++ b/jni/ruby/lib/rake/pseudo_status.rb @@ -0,0 +1,29 @@ +module Rake + + ## + # Exit status class for times the system just gives us a nil. + class PseudoStatus # :nodoc: all + 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/jni/ruby/lib/rake/rake_module.rb b/jni/ruby/lib/rake/rake_module.rb new file mode 100644 index 0000000..3692753 --- /dev/null +++ b/jni/ruby/lib/rake/rake_module.rb @@ -0,0 +1,38 @@ +require 'rake/application' + +module Rake + + class << self + # Current Rake Application + def application + @application ||= Rake::Application.new + end + + # Set the current Rake application object. + def application=(app) + @application = app + end + + def suggested_thread_count # :nodoc: + @cpu_count ||= Rake::CpuCounter.count + @cpu_count + 4 + 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 + + # Add files to the rakelib list + def add_rakelib(*files) + application.options.rakelib ||= [] + application.options.rakelib.concat(files) + end + end + +end diff --git a/jni/ruby/lib/rake/rake_test_loader.rb b/jni/ruby/lib/rake/rake_test_loader.rb new file mode 100644 index 0000000..7e3a6b3 --- /dev/null +++ b/jni/ruby/lib/rake/rake_test_loader.rb @@ -0,0 +1,22 @@ +require 'rake' + +# Load the test files from the command line. +argv = ARGV.select do |argument| + case argument + when /^-/ then + argument + when /\*/ then + FileList[argument].to_a.each do |file| + require File.expand_path file + end + + false + else + require File.expand_path argument + + false + end +end + +ARGV.replace argv + diff --git a/jni/ruby/lib/rake/rdoctask.rb b/jni/ruby/lib/rake/rdoctask.rb new file mode 100644 index 0000000..8d7df4f --- /dev/null +++ b/jni/ruby/lib/rake/rdoctask.rb @@ -0,0 +1,4 @@ +# TODO: Remove in Rake 11 + +fail "ERROR: 'rake/rdoctask' is obsolete and no longer supported. " + + "Use 'rdoc/task' (available in RDoc 2.4.2+) instead." diff --git a/jni/ruby/lib/rake/ruby182_test_unit_fix.rb b/jni/ruby/lib/rake/ruby182_test_unit_fix.rb new file mode 100644 index 0000000..40b30a6 --- /dev/null +++ b/jni/ruby/lib/rake/ruby182_test_unit_fix.rb @@ -0,0 +1,29 @@ +# TODO: Remove in rake 11 + +# 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 do |t| + add_suite(suites, t.suite) + end + ensure + $:.delete_at $:.rindex(dir) + end + end + end + end +end diff --git a/jni/ruby/lib/rake/rule_recursion_overflow_error.rb b/jni/ruby/lib/rake/rule_recursion_overflow_error.rb new file mode 100644 index 0000000..da4318d --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/rake/runtest.rb b/jni/ruby/lib/rake/runtest.rb new file mode 100644 index 0000000..4774b0e --- /dev/null +++ b/jni/ruby/lib/rake/runtest.rb @@ -0,0 +1,27 @@ +require 'test/unit' +require 'test/unit/assertions' +require 'rake/file_list' + +module Rake + include Test::Unit::Assertions + + ## + # Deprecated way of running tests in process, but only for Test::Unit. + #-- + # TODO: Remove in rake 11 + + def run_tests(pattern='test/test*.rb', log_enabled=false) # :nodoc: + FileList.glob(pattern).each do |fn| + $stderr.puts fn if log_enabled + begin + require fn + rescue Exception => ex + $stderr.puts "Error in #{fn}: #{ex.message}" + $stderr.puts ex.backtrace + assert false + end + end + end + + extend self +end diff --git a/jni/ruby/lib/rake/scope.rb b/jni/ruby/lib/rake/scope.rb new file mode 100644 index 0000000..dbefcea --- /dev/null +++ b/jni/ruby/lib/rake/scope.rb @@ -0,0 +1,42 @@ +module Rake + class Scope < LinkedList # :nodoc: all + + # Path for the scope. + def path + map { |item| item.to_s }.reverse.join(":") + end + + # Path for the scope + the named path. + def path_with_task_name(task_name) + "#{path}:#{task_name}" + end + + # Trim +n+ innermost scope levels from the scope. In no case will + # this trim beyond the toplevel scope. + def trim(n) + result = self + while n > 0 && ! result.empty? + result = result.tail + n -= 1 + end + result + end + + # Scope lists always end with an EmptyScope object. See Null + # Object Pattern) + class EmptyScope < EmptyLinkedList + @parent = Scope + + def path + "" + end + + def path_with_task_name(task_name) + task_name + end + end + + # Singleton null object for an empty scope. + EMPTY = EmptyScope.new + end +end diff --git a/jni/ruby/lib/rake/task.rb b/jni/ruby/lib/rake/task.rb new file mode 100644 index 0000000..9bcf725 --- /dev/null +++ b/jni/ruby/lib/rake/task.rb @@ -0,0 +1,383 @@ +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 + + # 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 # :nodoc: + "<#{self.class} #{name} => [#{prerequisites.join(', ')}]>" + end + + # List of sources for task. + attr_writer :sources + def sources + if defined?(@sources) + @sources + else + prerequisites + end + end + + # List of prerequisite tasks + def prerequisite_tasks + prerequisites.map { |pre| lookup_prerequisite(pre) } + end + + def lookup_prerequisite(prerequisite_name) # :nodoc: + application[prerequisite_name, @scope] + end + private :lookup_prerequisite + + # List of all unique prerequisite tasks including prerequisite tasks' + # prerequisites. + # Includes self when cyclic dependencies are found. + def all_prerequisite_tasks + seen = {} + collect_prerequisites(seen) + seen.values + end + + def collect_prerequisites(seen) # :nodoc: + prerequisite_tasks.each do |pre| + next if seen[pre.name] + seen[pre.name] = pre + pre.collect_prerequisites(seen) + end + end + protected :collect_prerequisites + + # First source from a rule (nil if no sources) + def source + sources.first + 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 + @comments = [] + @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 + clear_comments + 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 + + # Clear the existing comments on a rake task. + def clear_comments + @comments = [] + 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 + application.trace "** 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) # :nodoc: + 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: + if application.options.always_multitask + invoke_prerequisites_concurrently(task_args, invocation_chain) + else + prerequisite_tasks.each { |p| + prereq_args = task_args.new_scope(p.arg_names) + p.invoke_with_call_chain(prereq_args, invocation_chain) + } + end + end + + # Invoke all the prerequisites of a task in parallel. + def invoke_prerequisites_concurrently(task_args, invocation_chain)# :nodoc: + futures = prerequisite_tasks.map do |p| + prereq_args = task_args.new_scope(p.arg_names) + application.thread_pool.future(p) do |r| + r.invoke_with_call_chain(prereq_args, invocation_chain) + end + end + futures.each { |f| f.value } + 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 + application.trace "** Execute (dry run) #{name}" + return + end + application.trace "** Execute #{name}" if application.options.trace + 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 + 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 unless description + comment = description.strip + add_comment(comment) if comment && ! comment.empty? + end + + def comment=(comment) # :nodoc: + add_comment(comment) + end + + def add_comment(comment) # :nodoc: + return if comment.nil? + @comments << comment unless @comments.include?(comment) + end + private :add_comment + + # Full collection of comments. Multiple comments are separated by + # newlines. + def full_comment + transform_comments("\n") + end + + # First line (or sentence) of all comments. Multiple comments are + # separated by a "/". + def comment + transform_comments(" / ") { |c| first_sentence(c) } + end + + # Transform the list of comments as specified by the block and + # join with the separator. + def transform_comments(separator, &block) + if @comments.empty? + nil + else + block ||= lambda { |c| c } + @comments.map(&block).join(separator) + end + end + private :transform_comments + + # Get the first sentence in a string. The sentence is terminated + # by the first period or the end of the line. Decimal points do + # not count as periods. + def first_sentence(string) + string.split(/\.[ \t]|\.$|\n/).first + end + private :first_sentence + + # 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.map { |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(':') + scope.path_with_task_name(task_name) + end + + end # class << Rake::Task + end # class Rake::Task +end diff --git a/jni/ruby/lib/rake/task_argument_error.rb b/jni/ruby/lib/rake/task_argument_error.rb new file mode 100644 index 0000000..3e1dda6 --- /dev/null +++ b/jni/ruby/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/jni/ruby/lib/rake/task_arguments.rb b/jni/ruby/lib/rake/task_arguments.rb new file mode 100644 index 0000000..fc0d657 --- /dev/null +++ b/jni/ruby/lib/rake/task_arguments.rb @@ -0,0 +1,98 @@ +module Rake + + ## + # TaskArguments manage the arguments passed to a task. + # + class TaskArguments + include Enumerable + + # Argument names + attr_reader :names + + # Create a TaskArgument object with a list of argument +names+ and a set + # of associated +values+. +parent+ is the parent argument object. + def initialize(names, values, parent=nil) + @names = names + @parent = parent + @hash = {} + @values = values + names.each_with_index { |name, i| + @hash[name.to_sym] = values[i] unless values[i].nil? + } + end + + # Retrieve the complete array of sequential values + def to_a + @values.dup + end + + # Retrieve the list of values not associated with named arguments + def extras + @values[@names.length..-1] || [] + end + + # Create a new argument scope using the prerequisite argument + # names. + def new_scope(names) + values = names.map { |n| self[n] } + self.class.new(names, values + extras, 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 + + # Enumerates the arguments and their values + def each(&block) + @hash.each(&block) + end + + # Extracts the argument values at +keys+ + def values_at(*keys) + keys.map { |k| lookup(k) } + end + + # Returns the value of the given argument via method_missing + def method_missing(sym, *args) + lookup(sym.to_sym) + end + + # Returns a Hash of arguments and their values + def to_hash + @hash + end + + def to_s # :nodoc: + @hash.inspect + end + + def inspect # :nodoc: + to_s + end + + # Returns true if +key+ is one of the arguments + def has_key?(key) + @hash.has_key?(key) + end + + protected + + def lookup(name) # :nodoc: + if @hash.has_key?(name) + @hash[name] + elsif @parent + @parent.lookup(name) + end + end + end + + EMPTY_TASK_ARGS = TaskArguments.new([], []) # :nodoc: +end diff --git a/jni/ruby/lib/rake/task_manager.rb b/jni/ruby/lib/rake/task_manager.rb new file mode 100644 index 0000000..cbb9f5e --- /dev/null +++ b/jni/ruby/lib/rake/task_manager.rb @@ -0,0 +1,310 @@ +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 + + # TODO: Remove in Rake 11 + + alias :last_comment :last_description # :nodoc: Backwards compatibility + + def initialize # :nodoc: + super + @tasks = Hash.new + @rules = Array.new + @scope = Scope.make + @last_description = nil + end + + def create_rule(*args, &block) # :nodoc: + pattern, args, deps = resolve_args(args) + pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern + @rules << [pattern, args, deps, block] + end + + def define_task(task_class, *args, &block) # :nodoc: + task_name, arg_names, deps = resolve_args(args) + + original_scope = @scope + if String === task_name and + not task_class.ancestors.include? Rake::FileTask then + task_name, *definition_scope = *(task_name.split(":").reverse) + @scope = Scope.make(*(definition_scope + @scope.to_a)) + end + + task_name = task_class.scope_name(@scope, task_name) + deps = [deps] unless deps.respond_to?(:to_ary) + deps = deps.map { |d| Rake.from_pathname(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) + ensure + @scope = original_scope + 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) # :nodoc: + 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] + # + 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] + # + 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 || [] + 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, args, extensions, block| + if pattern.match(task_name) + task = attempt_rule(task_name, args, 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.path + 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 = Scope.make + task_name = task_name.sub(/^rake:/, '') + elsif task_name =~ /^(\^+)/ + scopes = initial_scope.trim($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) + loop do + tn = scope.path_with_task_name(name) + task = @tasks[tn] + return task if task + break if scope.empty? + scope = scope.tail + end + nil + end + private :lookup_in_scope + + # Return the list of scope names currently active in the task + # manager. + def current_scope + @scope + 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 = Scope.new(name, @scope) + ns = NameSpace.new(self, @scope) + yield(ns) + ns + ensure + @scope = @scope.tail + 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) # :nodoc: + options.trace_output.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, args, extensions, block, level) + sources = make_sources(task_name, extensions) + prereqs = sources.map { |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, {args => 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.map { |ext| + case ext + when /%/ + task_name.pathmap(ext) + when %r{/} + ext + when /^\./ + task_name.ext(ext) + when String + ext + when Proc, Method + 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 # :nodoc: + TaskManager.record_task_metadata = false + end + end + +end diff --git a/jni/ruby/lib/rake/tasklib.rb b/jni/ruby/lib/rake/tasklib.rb new file mode 100644 index 0000000..6203d94 --- /dev/null +++ b/jni/ruby/lib/rake/tasklib.rb @@ -0,0 +1,24 @@ +require 'rake' + +module Rake + + # Base class for Task Libraries. + class TaskLib + include Cloneable + include Rake::DSL + + # Make a symbol by pasting two strings together. + # + # NOTE: DEPRECATED! This method is kinda stupid. I don't know why + # I didn't just use string interpolation. But now other task + # libraries depend on this so I can't remove it without breaking + # other people's code. So for now it stays for backwards + # compatibility. BUT DON'T USE IT. + #-- + # TODO: Remove in Rake 11 + def paste(a, b) # :nodoc: + (a.to_s + b.to_s).intern + end + end + +end diff --git a/jni/ruby/lib/rake/testtask.rb b/jni/ruby/lib/rake/testtask.rb new file mode 100644 index 0000000..2daa589 --- /dev/null +++ b/jni/ruby/lib/rake/testtask.rb @@ -0,0 +1,212 @@ +require 'rake' +require 'rake/tasklib' + +module Rake + + # Create a task that runs a set of tests. + # + # Example: + # + # Rake::TestTask.new do |t| + # t.libs << "test" + # t.test_files = FileList['test/test*.rb'] + # t.verbose = true + # end + # + # If rake is invoked with a "TEST=filename" command line option, + # then the list of test files will be overridden to include only the + # filename specified on the command line. This provides an easy way + # to run just one test. + # + # If rake is invoked with a "TESTOPTS=options" command line option, + # then the given options are passed to the test process after a + # '--'. This allows Test::Unit options to be passed to the test + # suite. + # + # Examples: + # + # rake test # run tests normally + # rake test TEST=just_one_file.rb # run just one test file. + # rake test TESTOPTS="-v" # run in verbose mode + # rake test TESTOPTS="--runner=fox" # use the fox test runner + # + class TestTask < TaskLib + + # Name of test task. (default is :test) + attr_accessor :name + + # List of directories to added to $LOAD_PATH before running the + # tests. (default is 'lib') + attr_accessor :libs + + # True if verbose test output desired. (default is false) + attr_accessor :verbose + + # Test options passed to the test suite. An explicit + # TESTOPTS=opts on the command line will override this. (default + # is NONE) + attr_accessor :options + + # Request that the tests be run with the warning flag set. + # E.g. warning=true implies "ruby -w" used to run the tests. + attr_accessor :warning + + # Glob pattern to match test files. (default is 'test/test*.rb') + attr_accessor :pattern + + # Style of test loader to use. Options are: + # + # * :rake -- Rake provided test loading script (default). + # * :testrb -- Ruby provided test loading script. + # * :direct -- Load tests using command line loader. + # + attr_accessor :loader + + # Array of commandline options to pass to ruby when running test loader. + attr_accessor :ruby_opts + + # Description of the test task. (default is 'Run tests') + attr_accessor :description + + # Explicitly define the list of test files to be included in a + # test. +list+ is expected to be an array of file names (a + # FileList is acceptable). If both +pattern+ and +test_files+ are + # used, then the list of test files is the union of the two. + def test_files=(list) + @test_files = list + end + + # Create a testing task. + def initialize(name=:test) + @name = name + @libs = ["lib"] + @pattern = nil + @options = nil + @test_files = nil + @verbose = false + @warning = false + @loader = :rake + @ruby_opts = [] + @description = "Run tests" + (@name == :test ? "" : " for #{@name}") + yield self if block_given? + @pattern = 'test/test*.rb' if @pattern.nil? && @test_files.nil? + define + end + + # Create the tasks defined by this task lib. + def define + desc @description + task @name do + FileUtilsExt.verbose(@verbose) do + args = + "#{ruby_opts_string} #{run_code} " + + "#{file_list_string} #{option_list}" + ruby args do |ok, status| + if !ok && status.respond_to?(:signaled?) && status.signaled? + raise SignalException.new(status.termsig) + elsif !ok + fail "Command failed with status (#{status.exitstatus}): " + + "[ruby #{args}]" + end + end + end + end + self + end + + def option_list # :nodoc: + (ENV['TESTOPTS'] || + ENV['TESTOPT'] || + ENV['TEST_OPTS'] || + ENV['TEST_OPT'] || + @options || + "") + end + + def ruby_opts_string # :nodoc: + opts = @ruby_opts.dup + opts.unshift("-I\"#{lib_path}\"") unless @libs.empty? + opts.unshift("-w") if @warning + opts.join(" ") + end + + def lib_path # :nodoc: + @libs.join(File::PATH_SEPARATOR) + end + + def file_list_string # :nodoc: + file_list.map { |fn| "\"#{fn}\"" }.join(' ') + end + + def file_list # :nodoc: + if ENV['TEST'] + FileList[ENV['TEST']] + else + result = [] + result += @test_files.to_a if @test_files + 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 # :nodoc: + RUBY_VERSION + end + + def run_code # :nodoc: + case @loader + when :direct + "-e \"ARGV.each{|f| require f}\"" + when :testrb + "-S testrb #{fix}" + when :rake + "#{rake_include_arg} \"#{rake_loader}\"" + end + end + + def rake_loader # :nodoc: + find_file('rake/rake_test_loader') or + fail "unable to find rake test loader" + end + + def find_file(fn) # :nodoc: + $LOAD_PATH.each do |path| + file_path = File.join(path, "#{fn}.rb") + return file_path if File.exist? file_path + end + nil + end + + def rake_include_arg # :nodoc: + spec = Gem.loaded_specs['rake'] + if spec.respond_to?(:default_gem?) && spec.default_gem? + "" + else + "-I\"#{rake_lib_dir}\"" + end + 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/jni/ruby/lib/rake/thread_history_display.rb b/jni/ruby/lib/rake/thread_history_display.rb new file mode 100644 index 0000000..c2af9ec --- /dev/null +++ b/jni/ruby/lib/rake/thread_history_display.rb @@ -0,0 +1,48 @@ +require 'rake/private_reader' + +module Rake + + class ThreadHistoryDisplay # :nodoc: all + include Rake::PrivateReader + + private_reader :stats, :items, :threads + + def initialize(stats) + @stats = stats + @items = { :_seq_ => 1 } + @threads = { :_seq_ => "A" } + end + + def show + puts "Job History:" + stats.each do |stat| + stat[:data] ||= {} + rename(stat, :thread, threads) + rename(stat[:data], :item_id, items) + rename(stat[:data], :new_thread, threads) + rename(stat[:data], :deleted_thread, threads) + printf("%8d %2s %-20s %s\n", + (stat[:time] * 1_000_000).round, + stat[:thread], + stat[:event], + stat[:data].map do |k, v| "#{k}:#{v}" end.join(" ")) + end + end + + private + + def rename(hash, key, renames) + if hash && hash[key] + original = hash[key] + value = renames[original] + unless value + value = renames[:_seq_] + renames[:_seq_] = renames[:_seq_].succ + renames[original] = value + end + hash[key] = value + end + end + end + +end diff --git a/jni/ruby/lib/rake/thread_pool.rb b/jni/ruby/lib/rake/thread_pool.rb new file mode 100644 index 0000000..d2ac6e7 --- /dev/null +++ b/jni/ruby/lib/rake/thread_pool.rb @@ -0,0 +1,164 @@ +require 'thread' +require 'set' + +require 'rake/promise' + +module Rake + + class ThreadPool # :nodoc: all + + # Creates a ThreadPool object. The +thread_count+ parameter is the size + # of the pool. + def initialize(thread_count) + @max_active_threads = [thread_count, 0].max + @threads = Set.new + @threads_mon = Monitor.new + @queue = Queue.new + @join_cond = @threads_mon.new_cond + + @history_start_time = nil + @history = [] + @history_mon = Monitor.new + @total_threads_in_play = 0 + end + + # Creates a future executed by the +ThreadPool+. + # + # The args are passed to the block when executing (similarly to + # Thread#new) The return value is an object representing + # a future which has been created and added to the queue in the + # pool. Sending #value to the object will sleep the + # current thread until the future is finished and will return the + # result (or raise an exception thrown from the future) + def future(*args, &block) + promise = Promise.new(args, &block) + promise.recorder = lambda { |*stats| stat(*stats) } + + @queue.enq promise + stat :queued, :item_id => promise.object_id + start_thread + promise + end + + # Waits until the queue of futures is empty and all threads have exited. + def join + @threads_mon.synchronize do + begin + stat :joining + @join_cond.wait unless @threads.empty? + stat :joined + rescue Exception => e + stat :joined + $stderr.puts e + $stderr.print "Queue contains #{@queue.size} items. " + + "Thread pool contains #{@threads.count} threads\n" + $stderr.print "Current Thread #{Thread.current} status = " + + "#{Thread.current.status}\n" + $stderr.puts e.backtrace.join("\n") + @threads.each do |t| + $stderr.print "Thread #{t} status = #{t.status}\n" + # 1.8 doesn't support Thread#backtrace + $stderr.puts t.backtrace.join("\n") if t.respond_to? :backtrace + end + raise e + end + end + end + + # Enable the gathering of history events. + def gather_history #:nodoc: + @history_start_time = Time.now if @history_start_time.nil? + end + + # Return a array of history events for the thread pool. + # + # History gathering must be enabled to be able to see the events + # (see #gather_history). Best to call this when the job is + # complete (i.e. after ThreadPool#join is called). + def history # :nodoc: + @history_mon.synchronize { @history.dup }. + sort_by { |i| i[:time] }. + each { |i| i[:time] -= @history_start_time } + end + + # Return a hash of always collected statistics for the thread pool. + def statistics # :nodoc: + { + :total_threads_in_play => @total_threads_in_play, + :max_active_threads => @max_active_threads, + } + end + + private + + # processes one item on the queue. Returns true if there was an + # item to process, false if there was no item + def process_queue_item #:nodoc: + return false if @queue.empty? + + # Even though we just asked if the queue was empty, it + # still could have had an item which by this statement + # is now gone. For this reason we pass true to Queue#deq + # because we will sleep indefinitely if it is empty. + promise = @queue.deq(true) + stat :dequeued, :item_id => promise.object_id + promise.work + return true + + rescue ThreadError # this means the queue is empty + false + end + + def safe_thread_count + @threads_mon.synchronize do + @threads.count + end + end + + def start_thread # :nodoc: + @threads_mon.synchronize do + next unless @threads.count < @max_active_threads + + t = Thread.new do + begin + while safe_thread_count <= @max_active_threads + break unless process_queue_item + end + ensure + @threads_mon.synchronize do + @threads.delete Thread.current + stat :ended, :thread_count => @threads.count + @join_cond.broadcast if @threads.empty? + end + end + end + + @threads << t + stat( + :spawned, + :new_thread => t.object_id, + :thread_count => @threads.count) + @total_threads_in_play = @threads.count if + @threads.count > @total_threads_in_play + end + end + + def stat(event, data=nil) # :nodoc: + return if @history_start_time.nil? + info = { + :event => event, + :data => data, + :time => Time.now, + :thread => Thread.current.object_id, + } + @history_mon.synchronize { @history << info } + end + + # for testing only + + def __queue__ # :nodoc: + @queue + end + end + +end diff --git a/jni/ruby/lib/rake/trace_output.rb b/jni/ruby/lib/rake/trace_output.rb new file mode 100644 index 0000000..396096d --- /dev/null +++ b/jni/ruby/lib/rake/trace_output.rb @@ -0,0 +1,22 @@ +module Rake + module TraceOutput # :nodoc: all + + # Write trace output to output stream +out+. + # + # The write is done as a single IO call (to print) to lessen the + # chance that the trace output is interrupted by other tasks also + # producing output. + def trace_on(out, *strings) + sep = $\ || "\n" + if strings.empty? + output = sep + else + output = strings.map { |s| + next if s.nil? + s =~ /#{sep}$/ ? s : s + sep + }.join + end + out.print(output) + end + end +end diff --git a/jni/ruby/lib/rake/version.rb b/jni/ruby/lib/rake/version.rb new file mode 100644 index 0000000..b9b1b2d --- /dev/null +++ b/jni/ruby/lib/rake/version.rb @@ -0,0 +1,7 @@ +module Rake + module Version # :nodoc: all + MAJOR, MINOR, BUILD, *OTHER = Rake::VERSION.split '.' + + NUMBERS = [MAJOR, MINOR, BUILD, *OTHER] + end +end diff --git a/jni/ruby/lib/rake/win32.rb b/jni/ruby/lib/rake/win32.rb new file mode 100644 index 0000000..6b4873d --- /dev/null +++ b/jni/ruby/lib/rake/win32.rb @@ -0,0 +1,56 @@ + +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 # :nodoc: all + + # 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. + def windows? + AltSystem::WINDOWS + end + + # Run a command line on windows. + def rake_system(*cmd) + AltSystem.system(*cmd) + end + + # The standard directory containing system wide rake files on + # Win 32 systems. Try the following environment variables (in + # order): + # + # * HOME + # * HOMEDRIVE + HOMEPATH + # * APPDATA + # * USERPROFILE + # + # If the above are not defined, the return nil. + def win32_system_dir #:nodoc: + win32_shared_path = ENV['HOME'] + if win32_shared_path.nil? && ENV['HOMEDRIVE'] && ENV['HOMEPATH'] + win32_shared_path = ENV['HOMEDRIVE'] + ENV['HOMEPATH'] + end + + 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.gsub(/\\/, '/') + end + + end + end +end |