PageRenderTime 123ms CodeModel.GetById 77ms app.highlight 41ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/Ruby/lib/ruby/gems/1.8/gems/rake-0.9.2/lib/rake/application.rb

http://github.com/agross/netopenspace
Ruby | 589 lines | 492 code | 45 blank | 52 comment | 24 complexity | aef09d693860a3c83b52bbeac10536e4 MD5 | raw file
  1require 'shellwords'
  2require 'optparse'
  3
  4require 'rake/task_manager'
  5require 'rake/win32'
  6
  7module Rake
  8
  9  ######################################################################
 10  # Rake main application object.  When invoking +rake+ from the
 11  # command line, a Rake::Application object is created and run.
 12  #
 13  class Application
 14    include TaskManager
 15
 16    # The name of the application (typically 'rake')
 17    attr_reader :name
 18
 19    # The original directory where rake was invoked.
 20    attr_reader :original_dir
 21
 22    # Name of the actual rakefile used.
 23    attr_reader :rakefile
 24
 25    # List of the top level task names (task names from the command line).
 26    attr_reader :top_level_tasks
 27
 28    DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze
 29
 30    # Initialize a Rake::Application object.
 31    def initialize
 32      super
 33      @name = 'rake'
 34      @rakefiles = DEFAULT_RAKEFILES.dup
 35      @rakefile = nil
 36      @pending_imports = []
 37      @imported = []
 38      @loaders = {}
 39      @default_loader = Rake::DefaultLoader.new
 40      @original_dir = Dir.pwd
 41      @top_level_tasks = []
 42      add_loader('rb', DefaultLoader.new)
 43      add_loader('rf', DefaultLoader.new)
 44      add_loader('rake', DefaultLoader.new)
 45      @tty_output = STDOUT.tty?
 46    end
 47
 48    # Run the Rake application.  The run method performs the following
 49    # three steps:
 50    #
 51    # * Initialize the command line options (+init+).
 52    # * Define the tasks (+load_rakefile+).
 53    # * Run the top level tasks (+run_tasks+).
 54    #
 55    # If you wish to build a custom rake command, you should call
 56    # +init+ on your application.  Then define any tasks.  Finally,
 57    # call +top_level+ to run your top level tasks.
 58    def run
 59      standard_exception_handling do
 60        init
 61        load_rakefile
 62        top_level
 63      end
 64    end
 65
 66    # Initialize the command line parameters and app name.
 67    def init(app_name='rake')
 68      standard_exception_handling do
 69        @name = app_name
 70        handle_options
 71        collect_tasks
 72      end
 73    end
 74
 75    # Find the rakefile and then load it and any pending imports.
 76    def load_rakefile
 77      standard_exception_handling do
 78        raw_load_rakefile
 79      end
 80    end
 81
 82    # Run the top level tasks of a Rake application.
 83    def top_level
 84      standard_exception_handling do
 85        if options.show_tasks
 86          display_tasks_and_comments
 87        elsif options.show_prereqs
 88          display_prerequisites
 89        else
 90          top_level_tasks.each { |task_name| invoke_task(task_name) }
 91        end
 92      end
 93    end
 94
 95    # Add a loader to handle imported files ending in the extension
 96    # +ext+.
 97    def add_loader(ext, loader)
 98      ext = ".#{ext}" unless ext =~ /^\./
 99      @loaders[ext] = loader
100    end
101
102    # Application options from the command line
103    def options
104      @options ||= OpenStruct.new
105    end
106
107    # private ----------------------------------------------------------------
108
109    def invoke_task(task_string)
110      name, args = parse_task_string(task_string)
111      t = self[name]
112      t.invoke(*args)
113    end
114
115    def parse_task_string(string)
116      if string =~ /^([^\[]+)(\[(.*)\])$/
117        name = $1
118        args = $3.split(/\s*,\s*/)
119      else
120        name = string
121        args = []
122      end
123      [name, args]
124    end
125
126    # Provide standard exception handling for the given block.
127    def standard_exception_handling
128      begin
129        yield
130      rescue SystemExit => ex
131        # Exit silently with current status
132        raise
133      rescue OptionParser::InvalidOption => ex
134        $stderr.puts ex.message
135        exit(false)
136      rescue Exception => ex
137        # Exit with error message
138        display_error_message(ex)
139        exit(false)
140      end
141    end
142
143    # Display the error message that caused the exception.
144    def display_error_message(ex)
145      $stderr.puts "#{name} aborted!"
146      $stderr.puts ex.message
147      if options.trace
148        $stderr.puts ex.backtrace.join("\n")
149      else
150        $stderr.puts rakefile_location(ex.backtrace)
151      end
152      $stderr.puts "Tasks: #{ex.chain}" if has_chain?(ex)
153      $stderr.puts "(See full trace by running task with --trace)" unless options.trace
154    end
155
156    # Warn about deprecated usage.
157    #
158    # Example:
159    #    Rake.application.deprecate("import", "Rake.import", caller.first)
160    #
161    def deprecate(old_usage, new_usage, call_site)
162      return if options.ignore_deprecate
163      $stderr.puts "WARNING: '#{old_usage}' is deprecated.  " +
164        "Please use '#{new_usage}' instead.\n" +
165        "    at #{call_site}"
166    end
167
168    # Does the exception have a task invocation chain?
169    def has_chain?(exception)
170      exception.respond_to?(:chain) && exception.chain
171    end
172    private :has_chain?
173
174    # True if one of the files in RAKEFILES is in the current directory.
175    # If a match is found, it is copied into @rakefile.
176    def have_rakefile
177      @rakefiles.each do |fn|
178        if File.exist?(fn)
179          others = Dir.glob(fn, File::FNM_CASEFOLD)
180          return others.size == 1 ? others.first : fn
181        elsif fn == ''
182          return fn
183        end
184      end
185      return nil
186    end
187
188    # True if we are outputting to TTY, false otherwise
189    def tty_output?
190      @tty_output
191    end
192
193    # Override the detected TTY output state (mostly for testing)
194    def tty_output=( tty_output_state )
195      @tty_output = tty_output_state
196    end
197
198    # We will truncate output if we are outputting to a TTY or if we've been
199    # given an explicit column width to honor
200    def truncate_output?
201      tty_output? || ENV['RAKE_COLUMNS']
202    end
203
204    # Display the tasks and comments.
205    def display_tasks_and_comments
206      displayable_tasks = tasks.select { |t|
207        t.comment && t.name =~ options.show_task_pattern
208      }
209      case options.show_tasks
210      when :tasks
211        width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10
212        max_column = truncate_output? ? terminal_width - name.size - width - 7 : nil
213        displayable_tasks.each do |t|
214          printf "#{name} %-#{width}s  # %s\n",
215            t.name_with_args, max_column ? truncate(t.comment, max_column) : t.comment
216        end
217      when :describe
218        displayable_tasks.each do |t|
219          puts "#{name} #{t.name_with_args}"
220          t.full_comment.split("\n").each do |line|
221            puts "    #{line}"
222          end
223          puts
224        end
225      when :lines
226        displayable_tasks.each do |t|
227          t.locations.each do |loc|
228            printf "#{name} %-30s %s\n",t.name_with_args, loc
229          end
230        end
231      else
232        fail "Unknown show task mode: '#{options.show_tasks}'"
233      end
234    end
235
236    def terminal_width
237      if ENV['RAKE_COLUMNS']
238        result = ENV['RAKE_COLUMNS'].to_i
239      else
240        result = unix? ? dynamic_width : 80
241      end
242      (result < 10) ? 80 : result
243    rescue
244      80
245    end
246
247    # Calculate the dynamic width of the
248    def dynamic_width
249      @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
250    end
251
252    def dynamic_width_stty
253      %x{stty size 2>/dev/null}.split[1].to_i
254    end
255
256    def dynamic_width_tput
257      %x{tput cols 2>/dev/null}.to_i
258    end
259
260    def unix?
261      RbConfig::CONFIG['host_os'] =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
262    end
263
264    def windows?
265      Win32.windows?
266    end
267
268    def truncate(string, width)
269      if string.length <= width
270        string
271      else
272        ( string[0, width-3] || "" ) + "..."
273      end
274    end
275
276    # Display the tasks and prerequisites
277    def display_prerequisites
278      tasks.each do |t|
279        puts "#{name} #{t.name}"
280        t.prerequisites.each { |pre| puts "    #{pre}" }
281      end
282    end
283
284    # A list of all the standard options used in rake, suitable for
285    # passing to OptionParser.
286    def standard_rake_options
287      [
288        ['--classic-namespace', '-C', "Put Task and FileTask in the top level namespace",
289          lambda { |value|
290            require 'rake/classic_namespace'
291            options.classic_namespace = true
292          }
293        ],
294        ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
295          lambda { |value|
296            options.show_tasks = :describe
297            options.show_task_pattern = Regexp.new(value || '')
298            TaskManager.record_task_metadata = true
299          }
300        ],
301        ['--dry-run', '-n', "Do a dry run without executing actions.",
302          lambda { |value|
303            Rake.verbose(true)
304            Rake.nowrite(true)
305            options.dryrun = true
306            options.trace = true
307          }
308        ],
309        ['--execute',  '-e CODE', "Execute some Ruby code and exit.",
310          lambda { |value|
311            eval(value)
312            exit
313          }
314        ],
315        ['--execute-print',  '-p CODE', "Execute some Ruby code, print the result, then exit.",
316          lambda { |value|
317            puts eval(value)
318            exit
319          }
320        ],
321        ['--execute-continue',  '-E CODE',
322          "Execute some Ruby code, then continue with normal task processing.",
323          lambda { |value| eval(value) }
324        ],
325        ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.",
326          lambda { |value| $:.push(value) }
327        ],
328        ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.",
329          lambda { |value| options.nosearch = true }
330        ],
331        ['--prereqs', '-P', "Display the tasks and dependencies, then exit.",
332          lambda { |value| options.show_prereqs = true }
333        ],
334        ['--quiet', '-q', "Do not log messages to standard output.",
335          lambda { |value| Rake.verbose(false) }
336        ],
337        ['--rakefile', '-f [FILE]', "Use FILE as the rakefile.",
338          lambda { |value|
339            value ||= ''
340            @rakefiles.clear
341            @rakefiles << value
342          }
343        ],
344        ['--rakelibdir', '--rakelib', '-R RAKELIBDIR',
345          "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')",
346          lambda { |value| options.rakelib = value.split(':') }
347        ],
348        ['--require', '-r MODULE', "Require MODULE before executing rakefile.",
349          lambda { |value|
350            begin
351              require value
352            rescue LoadError => ex
353              begin
354                rake_require value
355              rescue LoadError
356                raise ex
357              end
358            end
359          }
360        ],
361        ['--rules', "Trace the rules resolution.",
362          lambda { |value| options.trace_rules = true }
363        ],
364        ['--silent', '-s', "Like --quiet, but also suppresses the 'in directory' announcement.",
365          lambda { |value|
366            Rake.verbose(false)
367            options.silent = true
368          }
369        ],
370        ['--system',  '-g',
371          "Using system wide (global) rakefiles (usually '~/.rake/*.rake').",
372          lambda { |value| options.load_system = true }
373        ],
374        ['--no-system', '--nosystem', '-G',
375          "Use standard project Rakefile search paths, ignore system wide rakefiles.",
376          lambda { |value| options.ignore_system = true }
377        ],
378        ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.",
379          lambda { |value|
380            options.show_tasks = :tasks
381            options.show_task_pattern = Regexp.new(value || '')
382            Rake::TaskManager.record_task_metadata = true
383          }
384        ],
385        ['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.",
386          lambda { |value|
387            options.trace = true
388            Rake.verbose(true)
389          }
390        ],
391        ['--verbose', '-v', "Log message to standard output.",
392          lambda { |value| Rake.verbose(true) }
393        ],
394        ['--version', '-V', "Display the program version.",
395          lambda { |value|
396            puts "rake, version #{RAKEVERSION}"
397            exit
398          }
399        ],
400        ['--where', '-W [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
401          lambda { |value|
402            options.show_tasks = :lines
403            options.show_task_pattern = Regexp.new(value || '')
404            Rake::TaskManager.record_task_metadata = true
405          }
406        ],
407        ['--no-deprecation-warnings', '-X', "Disable the deprecation warnings.",
408          lambda { |value|
409            options.ignore_deprecate = true
410          }
411        ],
412      ]
413    end
414
415    # Read and handle the command line options.
416    def handle_options
417      options.rakelib = ['rakelib']
418
419      OptionParser.new do |opts|
420        opts.banner = "rake [-f rakefile] {options} targets..."
421        opts.separator ""
422        opts.separator "Options are ..."
423
424        opts.on_tail("-h", "--help", "-H", "Display this help message.") do
425          puts opts
426          exit
427        end
428
429        standard_rake_options.each { |args| opts.on(*args) }
430        opts.environment('RAKEOPT')
431      end.parse!
432
433      # If class namespaces are requested, set the global options
434      # according to the values in the options structure.
435      if options.classic_namespace
436        $show_tasks = options.show_tasks
437        $show_prereqs = options.show_prereqs
438        $trace = options.trace
439        $dryrun = options.dryrun
440        $silent = options.silent
441      end
442    end
443
444    # Similar to the regular Ruby +require+ command, but will check
445    # for *.rake files in addition to *.rb files.
446    def rake_require(file_name, paths=$LOAD_PATH, loaded=$")
447      fn = file_name + ".rake"
448      return false if loaded.include?(fn)
449      paths.each do |path|
450        full_path = File.join(path, fn)
451        if File.exist?(full_path)
452          Rake.load_rakefile(full_path)
453          loaded << fn
454          return true
455        end
456      end
457      fail LoadError, "Can't find #{file_name}"
458    end
459
460    def find_rakefile_location
461      here = Dir.pwd
462      while ! (fn = have_rakefile)
463        Dir.chdir("..")
464        if Dir.pwd == here || options.nosearch
465          return nil
466        end
467        here = Dir.pwd
468      end
469      [fn, here]
470    ensure
471      Dir.chdir(Rake.original_dir)
472    end
473
474    def print_rakefile_directory(location)
475      $stderr.puts "(in #{Dir.pwd})" unless
476        options.silent or original_dir == location
477    end
478
479    def raw_load_rakefile # :nodoc:
480      rakefile, location = find_rakefile_location
481      if (! options.ignore_system) &&
482          (options.load_system || rakefile.nil?) &&
483          system_dir && File.directory?(system_dir)
484        print_rakefile_directory(location)
485        glob("#{system_dir}/*.rake") do |name|
486          add_import name
487        end
488      else
489        fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if
490          rakefile.nil?
491        @rakefile = rakefile
492        Dir.chdir(location)
493        print_rakefile_directory(location)
494        $rakefile = @rakefile if options.classic_namespace
495        Rake.load_rakefile(File.expand_path(@rakefile)) if @rakefile && @rakefile != ''
496        options.rakelib.each do |rlib|
497          glob("#{rlib}/*.rake") do |name|
498            add_import name
499          end
500        end
501      end
502      load_imports
503    end
504
505    def glob(path, &block)
506      Dir[path.gsub("\\", '/')].each(&block)
507    end
508    private :glob
509
510    # The directory path containing the system wide rakefiles.
511    def system_dir
512      @system_dir ||=
513        begin
514          if ENV['RAKE_SYSTEM']
515            ENV['RAKE_SYSTEM']
516          else
517            standard_system_dir
518          end
519        end
520    end
521
522    # The standard directory containing system wide rake files.
523    if Win32.windows?
524      def standard_system_dir #:nodoc:
525        Win32.win32_system_dir
526      end
527    else
528      def standard_system_dir #:nodoc:
529        File.join(File.expand_path('~'), '.rake')
530      end
531    end
532    private :standard_system_dir
533
534    # Collect the list of tasks on the command line.  If no tasks are
535    # given, return a list containing only the default task.
536    # Environmental assignments are processed at this time as well.
537    def collect_tasks
538      @top_level_tasks = []
539      ARGV.each do |arg|
540        if arg =~ /^(\w+)=(.*)$/
541          ENV[$1] = $2
542        else
543          @top_level_tasks << arg unless arg =~ /^-/
544        end
545      end
546      @top_level_tasks.push("default") if @top_level_tasks.size == 0
547    end
548
549    # Add a file to the list of files to be imported.
550    def add_import(fn)
551      @pending_imports << fn
552    end
553
554    # Load the pending list of imported files.
555    def load_imports
556      while fn = @pending_imports.shift
557        next if @imported.member?(fn)
558        if fn_task = lookup(fn)
559          fn_task.invoke
560        end
561        ext = File.extname(fn)
562        loader = @loaders[ext] || @default_loader
563        loader.load(fn)
564        @imported << fn
565      end
566    end
567
568    # Warn about deprecated use of top level constant names.
569    def const_warning(const_name)
570      @const_warning ||= false
571      if ! @const_warning
572        $stderr.puts %{WARNING: Deprecated reference to top-level constant '#{const_name}' } +
573          %{found at: #{rakefile_location}} # '
574        $stderr.puts %{    Use --classic-namespace on rake command}
575        $stderr.puts %{    or 'require "rake/classic_namespace"' in Rakefile}
576      end
577      @const_warning = true
578    end
579
580    def rakefile_location backtrace = caller
581      backtrace.map { |t| t[/([^:]+):/,1] }
582
583      re = /^#{@rakefile}$/
584      re = /#{re.source}/i if windows?
585
586      backtrace.find { |str| str =~ re } || ''
587    end
588  end
589end