application.rb 17 KB

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