diff options
Diffstat (limited to 'lib/rake/application.rb')
-rw-r--r-- | lib/rake/application.rb | 384 |
1 files changed, 244 insertions, 140 deletions
diff --git a/lib/rake/application.rb b/lib/rake/application.rb index 2079fb9be6..50930938bf 100644 --- a/lib/rake/application.rb +++ b/lib/rake/application.rb @@ -2,10 +2,14 @@ require 'shellwords' require 'optparse' require 'rake/task_manager' +require 'rake/thread_pool' +require 'rake/thread_history_display' 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. @@ -54,7 +58,7 @@ module Rake # # * Initialize the command line options (+init+). # * Define the tasks (+load_rakefile+). - # * Run the top level tasks (+run_tasks+). + # * 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, @@ -85,7 +89,7 @@ module Rake # Run the top level tasks of a Rake application. def top_level - standard_exception_handling do + run_with_threads do if options.show_tasks display_tasks_and_comments elsif options.show_prereqs @@ -96,6 +100,21 @@ module Rake 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]}" + puts "Total threads in play: #{stats[:total_threads_in_play]}" + 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) @@ -108,6 +127,11 @@ module Rake @options ||= OpenStruct.new end + # Return the thread pool used for multithreaded processing. + def thread_pool # :nodoc: + @thread_pool ||= ThreadPool.new(options.thread_pool_size||FIXNUM_MAX) + end + # private ---------------------------------------------------------------- def invoke_task(task_string) @@ -146,15 +170,15 @@ module Rake # Display the error message that caused the exception. def display_error_message(ex) - $stderr.puts "#{name} aborted!" - $stderr.puts ex.message - if options.trace - $stderr.puts ex.backtrace.join("\n") + trace "#{name} aborted!" + trace ex.message + if options.backtrace + trace ex.backtrace.join("\n") else - $stderr.puts rakefile_location(ex.backtrace) + trace Backtrace.collapse(ex.backtrace) end - $stderr.puts "Tasks: #{ex.chain}" if has_chain?(ex) - $stderr.puts "(See full trace by running task with --trace)" unless options.trace + trace "Tasks: #{ex.chain}" if has_chain?(ex) + trace "(See full trace by running task with --trace)" unless options.backtrace end # Warn about deprecated usage. @@ -180,7 +204,7 @@ module Rake def have_rakefile @rakefiles.each do |fn| if File.exist?(fn) - others = Dir.glob(fn, File::FNM_CASEFOLD) + others = Rake.glob(fn, File::FNM_CASEFOLD) return others.size == 1 ? others.first : fn elsif fn == '' return fn @@ -208,7 +232,7 @@ module Rake # Display the tasks and comments. def display_tasks_and_comments displayable_tasks = tasks.select { |t| - t.comment && t.name =~ options.show_task_pattern + (options.show_all_tasks || t.comment) && t.name =~ options.show_task_pattern } case options.show_tasks when :tasks @@ -222,7 +246,8 @@ module Rake when :describe displayable_tasks.each do |t| puts "#{name} #{t.name_with_args}" - t.full_comment.split("\n").each do |line| + comment = t.full_comment || "" + comment.split("\n").each do |line| puts " #{line}" end puts @@ -271,7 +296,9 @@ module Rake end def truncate(string, width) - if string.length <= width + if string.nil? + "" + elsif string.length <= width string else ( string[0, width-3] || "" ) + "..." @@ -286,141 +313,214 @@ module Rake end end + def trace(*str) + options.trace_output ||= $stderr + options.trace_output.puts(*str) + end + + def sort_options(options) + 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 - [ - ['--classic-namespace', '-C', "Put Task and FileTask in the top level namespace", - lambda { |value| - require 'rake/classic_namespace' - options.classic_namespace = true - } - ], - ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.", - lambda { |value| - options.show_tasks = :describe - options.show_task_pattern = Regexp.new(value || '') - TaskManager.record_task_metadata = true - } - ], - ['--dry-run', '-n', "Do a dry run without executing actions.", - lambda { |value| - Rake.verbose(true) - Rake.nowrite(true) - options.dryrun = true - options.trace = true - } - ], - ['--execute', '-e CODE', "Execute some Ruby code and exit.", - lambda { |value| - eval(value) - exit - } - ], - ['--execute-print', '-p CODE', "Execute some Ruby code, print the result, then exit.", - lambda { |value| - puts eval(value) - exit - } - ], - ['--execute-continue', '-E CODE', - "Execute some Ruby code, then continue with normal task processing.", - lambda { |value| eval(value) } - ], - ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.", - lambda { |value| $:.push(value) } - ], - ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.", - lambda { |value| options.nosearch = true } - ], - ['--prereqs', '-P', "Display the tasks and dependencies, then exit.", - lambda { |value| options.show_prereqs = true } - ], - ['--quiet', '-q', "Do not log messages to standard output.", - lambda { |value| Rake.verbose(false) } - ], - ['--rakefile', '-f [FILE]', "Use FILE as the rakefile.", - lambda { |value| - value ||= '' - @rakefiles.clear - @rakefiles << value - } - ], - ['--rakelibdir', '--rakelib', '-R RAKELIBDIR', - "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')", - # HACK Use File::PATH_SEPARATOR - lambda { |value| options.rakelib = value.split(':') } - ], - ['--require', '-r MODULE', "Require MODULE before executing rakefile.", - lambda { |value| - begin - require value - rescue LoadError => ex + sort_options( + [ + ['--all', '-A', "Show all tasks, even uncommented ones", + 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) + } + ], + ['--classic-namespace', '-C', "Put Task and FileTask in the top level namespace", + lambda { |value| + require 'rake/classic_namespace' + options.classic_namespace = 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:2)", + lambda { |value| options.thread_pool_size = [(value || 2).to_i,2].max } + ], + ['--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 [FILE]', "Use FILE as the rakefile.", + lambda { |value| + value ||= '' + @rakefiles.clear + @rakefiles << value + } + ], + ['--rakelibdir', '--rakelib', '-R RAKELIBDIR', + "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')", + lambda { |value| options.rakelib = value.split(File::PATH_SEPARATOR) } + ], + ['--reduce-compat', "Remove DSL in Object; remove Module#const_missing which defines ::Task etc.", + # Load-time option. + # Handled in bin/rake where Rake::REDUCE_COMPAT is defined (or not). + lambda { |_| } + ], + ['--require', '-r MODULE', "Require MODULE before executing rakefile.", + lambda { |value| begin - rake_require value - rescue LoadError - raise ex + require value + rescue LoadError => ex + begin + rake_require value + rescue LoadError + raise ex + end end - end - } - ], - ['--rules', "Trace the rules resolution.", - lambda { |value| options.trace_rules = true } - ], - ['--silent', '-s', "Like --quiet, but also suppresses the 'in directory' announcement.", - lambda { |value| - Rake.verbose(false) - options.silent = true - } - ], - ['--system', '-g', - "Using system wide (global) rakefiles (usually '~/.rake/*.rake').", - lambda { |value| options.load_system = true } - ], - ['--no-system', '--nosystem', '-G', - "Use standard project Rakefile search paths, ignore system wide rakefiles.", - lambda { |value| options.ignore_system = true } - ], - ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.", - lambda { |value| - options.show_tasks = :tasks - options.show_task_pattern = Regexp.new(value || '') - Rake::TaskManager.record_task_metadata = true - } - ], - ['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.", - lambda { |value| - options.trace = true - Rake.verbose(true) - } - ], - ['--verbose', '-v', "Log message to standard output.", - lambda { |value| Rake.verbose(true) } - ], - ['--version', '-V', "Display the program version.", - lambda { |value| - puts "rake, version #{RAKEVERSION}" - exit - } - ], - ['--where', '-W [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.", - lambda { |value| - options.show_tasks = :lines - options.show_task_pattern = Regexp.new(value || '') - Rake::TaskManager.record_task_metadata = true - } - ], - ['--no-deprecation-warnings', '-X', "Disable the deprecation warnings.", - lambda { |value| - options.ignore_deprecate = true - } - ], - ] + } + ], + ['--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', '-t [OUT]', "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) + 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) + 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. def handle_options options.rakelib = ['rakelib'] + options.trace_output = $stderr OptionParser.new do |opts| opts.banner = "rake [-f rakefile] {options} targets..." @@ -509,7 +609,7 @@ module Rake end def glob(path, &block) - Dir[path.gsub("\\", '/')].each(&block) + Rake.glob(path.gsub("\\", '/')).each(&block) end private :glob @@ -583,7 +683,7 @@ module Rake @const_warning = true end - def rakefile_location backtrace = caller + def rakefile_location(backtrace=caller) backtrace.map { |t| t[/([^:]+):/,1] } re = /^#{@rakefile}$/ @@ -591,5 +691,9 @@ module Rake backtrace.find { |str| str =~ re } || '' end + + private + FIXNUM_MAX = (2**(0.size * 8 - 2) - 1) # :nodoc: + end end |