rake 13.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/macos.yml +22 -0
  3. data/.github/workflows/ubuntu-rvm.yml +28 -0
  4. data/.github/workflows/ubuntu.yml +20 -0
  5. data/.github/workflows/windows.yml +20 -0
  6. data/CONTRIBUTING.rdoc +43 -0
  7. data/Gemfile +10 -0
  8. data/History.rdoc +2359 -0
  9. data/MIT-LICENSE +21 -0
  10. data/README.rdoc +155 -0
  11. data/Rakefile +41 -0
  12. data/bin/bundle +105 -0
  13. data/bin/console +7 -0
  14. data/bin/rake +29 -0
  15. data/bin/rdoc +29 -0
  16. data/bin/rubocop +29 -0
  17. data/bin/setup +6 -0
  18. data/doc/command_line_usage.rdoc +158 -0
  19. data/doc/example/Rakefile1 +38 -0
  20. data/doc/example/Rakefile2 +35 -0
  21. data/doc/example/a.c +6 -0
  22. data/doc/example/b.c +6 -0
  23. data/doc/example/main.c +11 -0
  24. data/doc/glossary.rdoc +42 -0
  25. data/doc/jamis.rb +592 -0
  26. data/doc/proto_rake.rdoc +127 -0
  27. data/doc/rake.1 +156 -0
  28. data/doc/rakefile.rdoc +622 -0
  29. data/doc/rational.rdoc +151 -0
  30. data/exe/rake +27 -0
  31. data/lib/rake.rb +71 -0
  32. data/lib/rake/application.rb +824 -0
  33. data/lib/rake/backtrace.rb +24 -0
  34. data/lib/rake/clean.rb +78 -0
  35. data/lib/rake/cloneable.rb +17 -0
  36. data/lib/rake/cpu_counter.rb +107 -0
  37. data/lib/rake/default_loader.rb +15 -0
  38. data/lib/rake/dsl_definition.rb +195 -0
  39. data/lib/rake/early_time.rb +22 -0
  40. data/lib/rake/ext/core.rb +26 -0
  41. data/lib/rake/ext/string.rb +176 -0
  42. data/lib/rake/file_creation_task.rb +25 -0
  43. data/lib/rake/file_list.rb +435 -0
  44. data/lib/rake/file_task.rb +54 -0
  45. data/lib/rake/file_utils.rb +134 -0
  46. data/lib/rake/file_utils_ext.rb +134 -0
  47. data/lib/rake/invocation_chain.rb +57 -0
  48. data/lib/rake/invocation_exception_mixin.rb +17 -0
  49. data/lib/rake/late_time.rb +18 -0
  50. data/lib/rake/linked_list.rb +112 -0
  51. data/lib/rake/loaders/makefile.rb +54 -0
  52. data/lib/rake/multi_task.rb +14 -0
  53. data/lib/rake/name_space.rb +38 -0
  54. data/lib/rake/packagetask.rb +222 -0
  55. data/lib/rake/phony.rb +16 -0
  56. data/lib/rake/private_reader.rb +21 -0
  57. data/lib/rake/promise.rb +100 -0
  58. data/lib/rake/pseudo_status.rb +30 -0
  59. data/lib/rake/rake_module.rb +67 -0
  60. data/lib/rake/rake_test_loader.rb +27 -0
  61. data/lib/rake/rule_recursion_overflow_error.rb +20 -0
  62. data/lib/rake/scope.rb +43 -0
  63. data/lib/rake/task.rb +433 -0
  64. data/lib/rake/task_argument_error.rb +8 -0
  65. data/lib/rake/task_arguments.rb +109 -0
  66. data/lib/rake/task_manager.rb +328 -0
  67. data/lib/rake/tasklib.rb +12 -0
  68. data/lib/rake/testtask.rb +224 -0
  69. data/lib/rake/thread_history_display.rb +49 -0
  70. data/lib/rake/thread_pool.rb +163 -0
  71. data/lib/rake/trace_output.rb +23 -0
  72. data/lib/rake/version.rb +10 -0
  73. data/lib/rake/win32.rb +51 -0
  74. data/rake.gemspec +36 -0
  75. metadata +132 -0
@@ -0,0 +1,151 @@
1
+ = Why rake?
2
+
3
+ Ok, let me state from the beginning that I never intended to write this
4
+ code. I'm not convinced it is useful, and I'm not convinced anyone
5
+ would even be interested in it. All I can say is that Why's onion truck
6
+ must by been passing through the Ohio valley.
7
+
8
+ What am I talking about? ... A Ruby version of Make.
9
+
10
+ See, I can sense you cringing already, and I agree. The world certainly
11
+ doesn't need yet another reworking of the "make" program. I mean, we
12
+ already have "ant". Isn't that enough?
13
+
14
+ It started yesterday. I was helping a coworker fix a problem in one of
15
+ the Makefiles we use in our project. Not a particularly tough problem,
16
+ but during the course of the conversation I began lamenting some of the
17
+ shortcomings of make. In particular, in one of my makefiles I wanted to
18
+ determine the name of a file dynamically and had to resort to some
19
+ simple scripting (in Ruby) to make it work. "Wouldn't it be nice if you
20
+ could just use Ruby inside a Makefile" I said.
21
+
22
+ My coworker (a recent convert to Ruby) agreed, but wondered what it
23
+ would look like. So I sketched the following on the whiteboard...
24
+
25
+ "What if you could specify the make tasks in Ruby, like this ..."
26
+
27
+ task "build" do
28
+ java_compile(...args, etc ...)
29
+ end
30
+
31
+ "The task function would register "build" as a target to be made,
32
+ and the block would be the action executed whenever the build
33
+ system determined that it was time to do the build target."
34
+
35
+ We agreed that would be cool, but writing make from scratch would be WAY
36
+ too much work. And that was the end of that!
37
+
38
+ ... Except I couldn't get the thought out of my head. What exactly
39
+ would be needed to make the about syntax work as a make file? Hmmm, you
40
+ would need to register the tasks, you need some way of specifying
41
+ dependencies between tasks, and some way of kicking off the process.
42
+ Hey! What if we did ... and fifteen minutes later I had a working
43
+ prototype of Ruby make, complete with dependencies and actions.
44
+
45
+ I showed the code to my coworker and we had a good laugh. It was just
46
+ about a page worth of code that reproduced an amazing amount of the
47
+ functionality of make. We were both truly stunned with the power of
48
+ Ruby.
49
+
50
+ But it didn't do everything make did. In particular, it didn't have
51
+ timestamp based file dependencies (where a file is rebuilt if any of its
52
+ prerequisite files have a later timestamp). Obviously THAT would be a
53
+ pain to add and so Ruby Make would remain an interesting experiment.
54
+
55
+ ... Except as I walked back to my desk, I started thinking about what
56
+ file based dependencies would really need. Rats! I was hooked again,
57
+ and by adding a new class and two new methods, file/timestamp
58
+ dependencies were implemented.
59
+
60
+ Ok, now I was really hooked. Last night (during CSI!) I massaged the
61
+ code and cleaned it up a bit. The result is a bare-bones replacement
62
+ for make in exactly 100 lines of code.
63
+
64
+ For the curious, you can see it at ...
65
+ * doc/proto_rake.rdoc
66
+
67
+ Oh, about the name. When I wrote the example Ruby Make task on my
68
+ whiteboard, my coworker exclaimed "Oh! I have the perfect name: Rake ...
69
+ Get it? Ruby-Make. Rake!" He said he envisioned the tasks as leaves
70
+ and Rake would clean them up ... or something like that. Anyways, the
71
+ name stuck.
72
+
73
+ Some quick examples ...
74
+
75
+ A simple task to delete backup files ...
76
+
77
+ task :clean do
78
+ Dir['*~'].each {|fn| rm fn rescue nil}
79
+ end
80
+
81
+ Note that task names are symbols (they are slightly easier to type
82
+ than quoted strings ... but you may use quoted string if you would
83
+ rather). Rake makes the methods of the FileUtils module directly
84
+ available, so we take advantage of the <tt>rm</tt> command. Also note
85
+ the use of "rescue nil" to trap and ignore errors in the <tt>rm</tt>
86
+ command.
87
+
88
+ To run it, just type "rake clean". Rake will automatically find a
89
+ Rakefile in the current directory (or above!) and will invoke the
90
+ targets named on the command line. If there are no targets explicitly
91
+ named, rake will invoke the task "default".
92
+
93
+ Here's another task with dependencies ...
94
+
95
+ task :clobber => [:clean] do
96
+ rm_r "tempdir"
97
+ end
98
+
99
+ Task :clobber depends upon task :clean, so :clean will be run before
100
+ :clobber is executed.
101
+
102
+ Files are specified by using the "file" command. It is similar to the
103
+ task command, except that the task name represents a file, and the task
104
+ will be run only if the file doesn't exist, or if its modification time
105
+ is earlier than any of its prerequisites.
106
+
107
+ Here is a file based dependency that will compile "hello.cc" to
108
+ "hello.o".
109
+
110
+ file "hello.cc"
111
+ file "hello.o" => ["hello.cc"] do |t|
112
+ srcfile = t.name.sub(/\.o$/, ".cc")
113
+ sh %{g++ #{srcfile} -c -o #{t.name}}
114
+ end
115
+
116
+ I normally specify file tasks with string (rather than symbols). Some
117
+ file names can't be represented by symbols. Plus it makes the
118
+ distinction between them more clear to the casual reader.
119
+
120
+ Currently writing a task for each and every file in the project would be
121
+ tedious at best. I envision a set of libraries to make this job
122
+ easier. For instance, perhaps something like this ...
123
+
124
+ require 'rake/ctools'
125
+ Dir['*.c'].each do |fn|
126
+ c_source_file(fn)
127
+ end
128
+
129
+ where "c_source_file" will create all the tasks need to compile all the
130
+ C source files in a directory. Any number of useful libraries could be
131
+ created for rake.
132
+
133
+ That's it. There's no documentation (other than whats in this
134
+ message). Does this sound interesting to anyone? If so, I'll continue
135
+ to clean it up and write it up and publish it on RAA. Otherwise, I'll
136
+ leave it as an interesting exercise and a tribute to the power of Ruby.
137
+
138
+ Why /might/ rake be interesting to Ruby programmers. I don't know,
139
+ perhaps ...
140
+
141
+ * No weird make syntax (only weird Ruby syntax :-)
142
+ * No need to edit or read XML (a la ant)
143
+ * Platform independent build scripts.
144
+ * Will run anywhere Ruby exists, so no need to have "make" installed.
145
+ If you stay away from the "sys" command and use things like
146
+ 'ftools', you can have a perfectly platform independent
147
+ build script. Also rake is only 100 lines of code, so it can
148
+ easily be packaged along with the rest of your code.
149
+
150
+ So ... Sorry for the long rambling message. Like I said, I never
151
+ intended to write this code at all.
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # Copyright (c) 2003, 2004, 2005, 2006, 2007 Jim Weirich
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to
8
+ # deal in the Software without restriction, including without limitation the
9
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ # sell copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
+ # IN THE SOFTWARE.
23
+ #++
24
+
25
+ require "rake"
26
+
27
+ Rake.application.run
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+ #--
3
+ # Copyright 2003-2010 by Jim Weirich (jim.weirich@gmail.com)
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to
7
+ # deal in the Software without restriction, including without limitation the
8
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9
+ # sell copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Rake; end
25
+
26
+ require "rake/version"
27
+
28
+ require "rbconfig"
29
+ require "fileutils"
30
+ require "singleton"
31
+ require "monitor"
32
+ require "optparse"
33
+ require "ostruct"
34
+
35
+ require "rake/ext/string"
36
+
37
+ require "rake/win32"
38
+
39
+ require "rake/linked_list"
40
+ require "rake/cpu_counter"
41
+ require "rake/scope"
42
+ require "rake/task_argument_error"
43
+ require "rake/rule_recursion_overflow_error"
44
+ require "rake/rake_module"
45
+ require "rake/trace_output"
46
+ require "rake/pseudo_status"
47
+ require "rake/task_arguments"
48
+ require "rake/invocation_chain"
49
+ require "rake/task"
50
+ require "rake/file_task"
51
+ require "rake/file_creation_task"
52
+ require "rake/multi_task"
53
+ require "rake/dsl_definition"
54
+ require "rake/file_utils_ext"
55
+ require "rake/file_list"
56
+ require "rake/default_loader"
57
+ require "rake/early_time"
58
+ require "rake/late_time"
59
+ require "rake/name_space"
60
+ require "rake/task_manager"
61
+ require "rake/application"
62
+ require "rake/backtrace"
63
+
64
+ $trace = false
65
+
66
+ # :stopdoc:
67
+ #
68
+ # Some top level Constants.
69
+
70
+ FileList = Rake::FileList
71
+ RakeFileUtils = Rake::FileUtilsExt
@@ -0,0 +1,824 @@
1
+ # frozen_string_literal: true
2
+ require "optparse"
3
+
4
+ require "rake/task_manager"
5
+ require "rake/file_list"
6
+ require "rake/thread_pool"
7
+ require "rake/thread_history_display"
8
+ require "rake/trace_output"
9
+ require "rake/win32"
10
+
11
+ module Rake
12
+
13
+ CommandLineOptionError = Class.new(StandardError)
14
+
15
+ ##
16
+ # Rake main application object. When invoking +rake+ from the
17
+ # command line, a Rake::Application object is created and run.
18
+
19
+ class Application
20
+ include TaskManager
21
+ include TraceOutput
22
+
23
+ # The name of the application (typically 'rake')
24
+ attr_reader :name
25
+
26
+ # The original directory where rake was invoked.
27
+ attr_reader :original_dir
28
+
29
+ # Name of the actual rakefile used.
30
+ attr_reader :rakefile
31
+
32
+ # Number of columns on the terminal
33
+ attr_accessor :terminal_columns
34
+
35
+ # List of the top level task names (task names from the command line).
36
+ attr_reader :top_level_tasks
37
+
38
+ # Override the detected TTY output state (mostly for testing)
39
+ attr_writer :tty_output
40
+
41
+ DEFAULT_RAKEFILES = [
42
+ "rakefile",
43
+ "Rakefile",
44
+ "rakefile.rb",
45
+ "Rakefile.rb"
46
+ ].freeze
47
+
48
+ # Initialize a Rake::Application object.
49
+ def initialize
50
+ super
51
+ @name = "rake"
52
+ @rakefiles = DEFAULT_RAKEFILES.dup
53
+ @rakefile = nil
54
+ @pending_imports = []
55
+ @imported = []
56
+ @loaders = {}
57
+ @default_loader = Rake::DefaultLoader.new
58
+ @original_dir = Dir.pwd
59
+ @top_level_tasks = []
60
+ add_loader("rb", DefaultLoader.new)
61
+ add_loader("rf", DefaultLoader.new)
62
+ add_loader("rake", DefaultLoader.new)
63
+ @tty_output = STDOUT.tty?
64
+ @terminal_columns = ENV["RAKE_COLUMNS"].to_i
65
+
66
+ set_default_options
67
+ end
68
+
69
+ # Run the Rake application. The run method performs the following
70
+ # three steps:
71
+ #
72
+ # * Initialize the command line options (+init+).
73
+ # * Define the tasks (+load_rakefile+).
74
+ # * Run the top level tasks (+top_level+).
75
+ #
76
+ # If you wish to build a custom rake command, you should call
77
+ # +init+ on your application. Then define any tasks. Finally,
78
+ # call +top_level+ to run your top level tasks.
79
+ def run(argv = ARGV)
80
+ standard_exception_handling do
81
+ init "rake", argv
82
+ load_rakefile
83
+ top_level
84
+ end
85
+ end
86
+
87
+ # Initialize the command line parameters and app name.
88
+ def init(app_name="rake", argv = ARGV)
89
+ standard_exception_handling do
90
+ @name = app_name
91
+ begin
92
+ args = handle_options argv
93
+ rescue ArgumentError
94
+ # Backward compatibility for capistrano
95
+ args = handle_options
96
+ end
97
+ collect_command_line_tasks(args)
98
+ end
99
+ end
100
+
101
+ # Find the rakefile and then load it and any pending imports.
102
+ def load_rakefile
103
+ standard_exception_handling do
104
+ raw_load_rakefile
105
+ end
106
+ end
107
+
108
+ # Run the top level tasks of a Rake application.
109
+ def top_level
110
+ run_with_threads do
111
+ if options.show_tasks
112
+ display_tasks_and_comments
113
+ elsif options.show_prereqs
114
+ display_prerequisites
115
+ else
116
+ top_level_tasks.each { |task_name| invoke_task(task_name) }
117
+ end
118
+ end
119
+ end
120
+
121
+ # Run the given block with the thread startup and shutdown.
122
+ def run_with_threads
123
+ thread_pool.gather_history if options.job_stats == :history
124
+
125
+ yield
126
+
127
+ thread_pool.join
128
+ if options.job_stats
129
+ stats = thread_pool.statistics
130
+ puts "Maximum active threads: #{stats[:max_active_threads]} + main"
131
+ puts "Total threads in play: #{stats[:total_threads_in_play]} + main"
132
+ end
133
+ ThreadHistoryDisplay.new(thread_pool.history).show if
134
+ options.job_stats == :history
135
+ end
136
+
137
+ # Add a loader to handle imported files ending in the extension
138
+ # +ext+.
139
+ def add_loader(ext, loader)
140
+ ext = ".#{ext}" unless ext =~ /^\./
141
+ @loaders[ext] = loader
142
+ end
143
+
144
+ # Application options from the command line
145
+ def options
146
+ @options ||= OpenStruct.new
147
+ end
148
+
149
+ # Return the thread pool used for multithreaded processing.
150
+ def thread_pool # :nodoc:
151
+ @thread_pool ||= ThreadPool.new(options.thread_pool_size || Rake.suggested_thread_count-1)
152
+ end
153
+
154
+ # internal ----------------------------------------------------------------
155
+
156
+ # Invokes a task with arguments that are extracted from +task_string+
157
+ def invoke_task(task_string) # :nodoc:
158
+ name, args = parse_task_string(task_string)
159
+ t = self[name]
160
+ t.invoke(*args)
161
+ end
162
+
163
+ def parse_task_string(string) # :nodoc:
164
+ /^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s
165
+
166
+ name = $1
167
+ remaining_args = $2
168
+
169
+ return string, [] unless name
170
+ return name, [] if remaining_args.empty?
171
+
172
+ args = []
173
+
174
+ begin
175
+ /\s*((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args
176
+
177
+ remaining_args = $2
178
+ args << $1.gsub(/\\(.)/, '\1')
179
+ end while remaining_args
180
+
181
+ return name, args
182
+ end
183
+
184
+ # Provide standard exception handling for the given block.
185
+ def standard_exception_handling # :nodoc:
186
+ yield
187
+ rescue SystemExit
188
+ # Exit silently with current status
189
+ raise
190
+ rescue OptionParser::InvalidOption => ex
191
+ $stderr.puts ex.message
192
+ exit(false)
193
+ rescue Exception => ex
194
+ # Exit with error message
195
+ display_error_message(ex)
196
+ exit_because_of_exception(ex)
197
+ end
198
+
199
+ # Exit the program because of an unhandled exception.
200
+ # (may be overridden by subclasses)
201
+ def exit_because_of_exception(ex) # :nodoc:
202
+ exit(false)
203
+ end
204
+
205
+ # Display the error message that caused the exception.
206
+ def display_error_message(ex) # :nodoc:
207
+ trace "#{name} aborted!"
208
+ display_exception_details(ex)
209
+ trace "Tasks: #{ex.chain}" if has_chain?(ex)
210
+ trace "(See full trace by running task with --trace)" unless
211
+ options.backtrace
212
+ end
213
+
214
+ def display_exception_details(ex) # :nodoc:
215
+ display_exception_details_seen << ex
216
+
217
+ display_exception_message_details(ex)
218
+ display_exception_backtrace(ex)
219
+ display_cause_details(ex.cause) if has_cause?(ex)
220
+ end
221
+
222
+ def display_cause_details(ex) # :nodoc:
223
+ return if display_exception_details_seen.include? ex
224
+
225
+ trace "\nCaused by:"
226
+ display_exception_details(ex)
227
+ end
228
+
229
+ def display_exception_details_seen # :nodoc:
230
+ Thread.current[:rake_display_exception_details_seen] ||= []
231
+ end
232
+
233
+ def has_cause?(ex) # :nodoc:
234
+ ex.respond_to?(:cause) && ex.cause
235
+ end
236
+
237
+ def display_exception_message_details(ex) # :nodoc:
238
+ if ex.instance_of?(RuntimeError)
239
+ trace ex.message
240
+ else
241
+ trace "#{ex.class.name}: #{ex.message}"
242
+ end
243
+ end
244
+
245
+ def display_exception_backtrace(ex) # :nodoc:
246
+ if options.backtrace
247
+ trace ex.backtrace.join("\n")
248
+ else
249
+ trace Backtrace.collapse(ex.backtrace).join("\n")
250
+ end
251
+ end
252
+
253
+ # Warn about deprecated usage.
254
+ #
255
+ # Example:
256
+ # Rake.application.deprecate("import", "Rake.import", caller.first)
257
+ #
258
+ def deprecate(old_usage, new_usage, call_site) # :nodoc:
259
+ unless options.ignore_deprecate
260
+ $stderr.puts "WARNING: '#{old_usage}' is deprecated. " +
261
+ "Please use '#{new_usage}' instead.\n" +
262
+ " at #{call_site}"
263
+ end
264
+ end
265
+
266
+ # Does the exception have a task invocation chain?
267
+ def has_chain?(exception) # :nodoc:
268
+ exception.respond_to?(:chain) && exception.chain
269
+ end
270
+ private :has_chain?
271
+
272
+ # True if one of the files in RAKEFILES is in the current directory.
273
+ # If a match is found, it is copied into @rakefile.
274
+ def have_rakefile # :nodoc:
275
+ @rakefiles.each do |fn|
276
+ if File.exist?(fn)
277
+ others = FileList.glob(fn, File::FNM_CASEFOLD)
278
+ return others.size == 1 ? others.first : fn
279
+ elsif fn == ""
280
+ return fn
281
+ end
282
+ end
283
+ return nil
284
+ end
285
+
286
+ # True if we are outputting to TTY, false otherwise
287
+ def tty_output? # :nodoc:
288
+ @tty_output
289
+ end
290
+
291
+ # We will truncate output if we are outputting to a TTY or if we've been
292
+ # given an explicit column width to honor
293
+ def truncate_output? # :nodoc:
294
+ tty_output? || @terminal_columns.nonzero?
295
+ end
296
+
297
+ # Display the tasks and comments.
298
+ def display_tasks_and_comments # :nodoc:
299
+ displayable_tasks = tasks.select { |t|
300
+ (options.show_all_tasks || t.comment) &&
301
+ t.name =~ options.show_task_pattern
302
+ }
303
+ case options.show_tasks
304
+ when :tasks
305
+ width = displayable_tasks.map { |t| t.name_with_args.length }.max || 10
306
+ if truncate_output?
307
+ max_column = terminal_width - name.size - width - 7
308
+ else
309
+ max_column = nil
310
+ end
311
+
312
+ displayable_tasks.each do |t|
313
+ printf("#{name} %-#{width}s # %s\n",
314
+ t.name_with_args,
315
+ max_column ? truncate(t.comment, max_column) : t.comment)
316
+ end
317
+ when :describe
318
+ displayable_tasks.each do |t|
319
+ puts "#{name} #{t.name_with_args}"
320
+ comment = t.full_comment || ""
321
+ comment.split("\n").each do |line|
322
+ puts " #{line}"
323
+ end
324
+ puts
325
+ end
326
+ when :lines
327
+ displayable_tasks.each do |t|
328
+ t.locations.each do |loc|
329
+ printf "#{name} %-30s %s\n", t.name_with_args, loc
330
+ end
331
+ end
332
+ else
333
+ fail "Unknown show task mode: '#{options.show_tasks}'"
334
+ end
335
+ end
336
+
337
+ def terminal_width # :nodoc:
338
+ if @terminal_columns.nonzero?
339
+ result = @terminal_columns
340
+ else
341
+ result = unix? ? dynamic_width : 80
342
+ end
343
+ (result < 10) ? 80 : result
344
+ rescue
345
+ 80
346
+ end
347
+
348
+ # Calculate the dynamic width of the
349
+ def dynamic_width # :nodoc:
350
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
351
+ end
352
+
353
+ def dynamic_width_stty # :nodoc:
354
+ %x{stty size 2>/dev/null}.split[1].to_i
355
+ end
356
+
357
+ def dynamic_width_tput # :nodoc:
358
+ %x{tput cols 2>/dev/null}.to_i
359
+ end
360
+
361
+ def unix? # :nodoc:
362
+ RbConfig::CONFIG["host_os"] =~
363
+ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
364
+ end
365
+
366
+ def windows? # :nodoc:
367
+ Win32.windows?
368
+ end
369
+
370
+ def truncate(string, width) # :nodoc:
371
+ if string.nil?
372
+ ""
373
+ elsif string.length <= width
374
+ string
375
+ else
376
+ (string[0, width - 3] || "") + "..."
377
+ end
378
+ end
379
+
380
+ # Display the tasks and prerequisites
381
+ def display_prerequisites # :nodoc:
382
+ tasks.each do |t|
383
+ puts "#{name} #{t.name}"
384
+ t.prerequisites.each { |pre| puts " #{pre}" }
385
+ end
386
+ end
387
+
388
+ def trace(*strings) # :nodoc:
389
+ options.trace_output ||= $stderr
390
+ trace_on(options.trace_output, *strings)
391
+ end
392
+
393
+ def sort_options(options) # :nodoc:
394
+ options.sort_by { |opt|
395
+ opt.select { |o| o.is_a?(String) && o =~ /^-/ }.map(&:downcase).sort.reverse
396
+ }
397
+ end
398
+ private :sort_options
399
+
400
+ # A list of all the standard options used in rake, suitable for
401
+ # passing to OptionParser.
402
+ def standard_rake_options # :nodoc:
403
+ sort_options(
404
+ [
405
+ ["--all", "-A",
406
+ "Show all tasks, even uncommented ones (in combination with -T or -D)",
407
+ lambda { |value|
408
+ options.show_all_tasks = value
409
+ }
410
+ ],
411
+ ["--backtrace=[OUT]",
412
+ "Enable full backtrace. OUT can be stderr (default) or stdout.",
413
+ lambda { |value|
414
+ options.backtrace = true
415
+ select_trace_output(options, "backtrace", value)
416
+ }
417
+ ],
418
+ ["--build-all", "-B",
419
+ "Build all prerequisites, including those which are up-to-date.",
420
+ lambda { |value|
421
+ options.build_all = true
422
+ }
423
+ ],
424
+ ["--comments",
425
+ "Show commented tasks only",
426
+ lambda { |value|
427
+ options.show_all_tasks = !value
428
+ }
429
+ ],
430
+ ["--describe", "-D [PATTERN]",
431
+ "Describe the tasks (matching optional PATTERN), then exit.",
432
+ lambda { |value|
433
+ select_tasks_to_show(options, :describe, value)
434
+ }
435
+ ],
436
+ ["--dry-run", "-n",
437
+ "Do a dry run without executing actions.",
438
+ lambda { |value|
439
+ Rake.verbose(true)
440
+ Rake.nowrite(true)
441
+ options.dryrun = true
442
+ options.trace = true
443
+ }
444
+ ],
445
+ ["--execute", "-e CODE",
446
+ "Execute some Ruby code and exit.",
447
+ lambda { |value|
448
+ eval(value)
449
+ exit
450
+ }
451
+ ],
452
+ ["--execute-print", "-p CODE",
453
+ "Execute some Ruby code, print the result, then exit.",
454
+ lambda { |value|
455
+ puts eval(value)
456
+ exit
457
+ }
458
+ ],
459
+ ["--execute-continue", "-E CODE",
460
+ "Execute some Ruby code, " +
461
+ "then continue with normal task processing.",
462
+ lambda { |value| eval(value) }
463
+ ],
464
+ ["--jobs", "-j [NUMBER]",
465
+ "Specifies the maximum number of tasks to execute in parallel. " +
466
+ "(default is number of CPU cores + 4)",
467
+ lambda { |value|
468
+ if value.nil? || value == ""
469
+ value = Float::INFINITY
470
+ elsif value =~ /^\d+$/
471
+ value = value.to_i
472
+ else
473
+ value = Rake.suggested_thread_count
474
+ end
475
+ value = 1 if value < 1
476
+ options.thread_pool_size = value - 1
477
+ }
478
+ ],
479
+ ["--job-stats [LEVEL]",
480
+ "Display job statistics. " +
481
+ "LEVEL=history displays a complete job list",
482
+ lambda { |value|
483
+ if value =~ /^history/i
484
+ options.job_stats = :history
485
+ else
486
+ options.job_stats = true
487
+ end
488
+ }
489
+ ],
490
+ ["--libdir", "-I LIBDIR",
491
+ "Include LIBDIR in the search path for required modules.",
492
+ lambda { |value| $:.push(value) }
493
+ ],
494
+ ["--multitask", "-m",
495
+ "Treat all tasks as multitasks.",
496
+ lambda { |value| options.always_multitask = true }
497
+ ],
498
+ ["--no-search", "--nosearch",
499
+ "-N", "Do not search parent directories for the Rakefile.",
500
+ lambda { |value| options.nosearch = true }
501
+ ],
502
+ ["--prereqs", "-P",
503
+ "Display the tasks and dependencies, then exit.",
504
+ lambda { |value| options.show_prereqs = true }
505
+ ],
506
+ ["--quiet", "-q",
507
+ "Do not log messages to standard output.",
508
+ lambda { |value| Rake.verbose(false) }
509
+ ],
510
+ ["--rakefile", "-f [FILENAME]",
511
+ "Use FILENAME as the rakefile to search for.",
512
+ lambda { |value|
513
+ value ||= ""
514
+ @rakefiles.clear
515
+ @rakefiles << value
516
+ }
517
+ ],
518
+ ["--rakelibdir", "--rakelib", "-R RAKELIBDIR",
519
+ "Auto-import any .rake files in RAKELIBDIR. " +
520
+ "(default is 'rakelib')",
521
+ lambda { |value|
522
+ options.rakelib = value.split(File::PATH_SEPARATOR)
523
+ }
524
+ ],
525
+ ["--require", "-r MODULE",
526
+ "Require MODULE before executing rakefile.",
527
+ lambda { |value|
528
+ begin
529
+ require value
530
+ rescue LoadError => ex
531
+ begin
532
+ rake_require value
533
+ rescue LoadError
534
+ raise ex
535
+ end
536
+ end
537
+ }
538
+ ],
539
+ ["--rules",
540
+ "Trace the rules resolution.",
541
+ lambda { |value| options.trace_rules = true }
542
+ ],
543
+ ["--silent", "-s",
544
+ "Like --quiet, but also suppresses the " +
545
+ "'in directory' announcement.",
546
+ lambda { |value|
547
+ Rake.verbose(false)
548
+ options.silent = true
549
+ }
550
+ ],
551
+ ["--suppress-backtrace PATTERN",
552
+ "Suppress backtrace lines matching regexp PATTERN. " +
553
+ "Ignored if --trace is on.",
554
+ lambda { |value|
555
+ options.suppress_backtrace_pattern = Regexp.new(value)
556
+ }
557
+ ],
558
+ ["--system", "-g",
559
+ "Using system wide (global) rakefiles " +
560
+ "(usually '~/.rake/*.rake').",
561
+ lambda { |value| options.load_system = true }
562
+ ],
563
+ ["--no-system", "--nosystem", "-G",
564
+ "Use standard project Rakefile search paths, " +
565
+ "ignore system wide rakefiles.",
566
+ lambda { |value| options.ignore_system = true }
567
+ ],
568
+ ["--tasks", "-T [PATTERN]",
569
+ "Display the tasks (matching optional PATTERN) " +
570
+ "with descriptions, then exit. " +
571
+ "-AT combination displays all of tasks contained no description.",
572
+ lambda { |value|
573
+ select_tasks_to_show(options, :tasks, value)
574
+ }
575
+ ],
576
+ ["--trace=[OUT]", "-t",
577
+ "Turn on invoke/execute tracing, enable full backtrace. " +
578
+ "OUT can be stderr (default) or stdout.",
579
+ lambda { |value|
580
+ options.trace = true
581
+ options.backtrace = true
582
+ select_trace_output(options, "trace", value)
583
+ Rake.verbose(true)
584
+ }
585
+ ],
586
+ ["--verbose", "-v",
587
+ "Log message to standard output.",
588
+ lambda { |value| Rake.verbose(true) }
589
+ ],
590
+ ["--version", "-V",
591
+ "Display the program version.",
592
+ lambda { |value|
593
+ puts "rake, version #{Rake::VERSION}"
594
+ exit
595
+ }
596
+ ],
597
+ ["--where", "-W [PATTERN]",
598
+ "Describe the tasks (matching optional PATTERN), then exit.",
599
+ lambda { |value|
600
+ select_tasks_to_show(options, :lines, value)
601
+ options.show_all_tasks = true
602
+ }
603
+ ],
604
+ ["--no-deprecation-warnings", "-X",
605
+ "Disable the deprecation warnings.",
606
+ lambda { |value|
607
+ options.ignore_deprecate = true
608
+ }
609
+ ],
610
+ ])
611
+ end
612
+
613
+ def select_tasks_to_show(options, show_tasks, value) # :nodoc:
614
+ options.show_tasks = show_tasks
615
+ options.show_task_pattern = Regexp.new(value || "")
616
+ Rake::TaskManager.record_task_metadata = true
617
+ end
618
+ private :select_tasks_to_show
619
+
620
+ def select_trace_output(options, trace_option, value) # :nodoc:
621
+ value = value.strip unless value.nil?
622
+ case value
623
+ when "stdout"
624
+ options.trace_output = $stdout
625
+ when "stderr", nil
626
+ options.trace_output = $stderr
627
+ else
628
+ fail CommandLineOptionError,
629
+ "Unrecognized --#{trace_option} option '#{value}'"
630
+ end
631
+ end
632
+ private :select_trace_output
633
+
634
+ # Read and handle the command line options. Returns the command line
635
+ # arguments that we didn't understand, which should (in theory) be just
636
+ # task names and env vars.
637
+ def handle_options(argv) # :nodoc:
638
+ set_default_options
639
+
640
+ OptionParser.new do |opts|
641
+ opts.banner = "#{Rake.application.name} [-f rakefile] {options} targets..."
642
+ opts.separator ""
643
+ opts.separator "Options are ..."
644
+
645
+ opts.on_tail("-h", "--help", "-H", "Display this help message.") do
646
+ puts opts
647
+ exit
648
+ end
649
+
650
+ standard_rake_options.each { |args| opts.on(*args) }
651
+ opts.environment("RAKEOPT")
652
+ end.parse(argv)
653
+ end
654
+
655
+ # Similar to the regular Ruby +require+ command, but will check
656
+ # for *.rake files in addition to *.rb files.
657
+ def rake_require(file_name, paths=$LOAD_PATH, loaded=$") # :nodoc:
658
+ fn = file_name + ".rake"
659
+ return false if loaded.include?(fn)
660
+ paths.each do |path|
661
+ full_path = File.join(path, fn)
662
+ if File.exist?(full_path)
663
+ Rake.load_rakefile(full_path)
664
+ loaded << fn
665
+ return true
666
+ end
667
+ end
668
+ fail LoadError, "Can't find #{file_name}"
669
+ end
670
+
671
+ def find_rakefile_location # :nodoc:
672
+ here = Dir.pwd
673
+ until (fn = have_rakefile)
674
+ Dir.chdir("..")
675
+ return nil if Dir.pwd == here || options.nosearch
676
+ here = Dir.pwd
677
+ end
678
+ [fn, here]
679
+ ensure
680
+ Dir.chdir(Rake.original_dir)
681
+ end
682
+
683
+ def print_rakefile_directory(location) # :nodoc:
684
+ $stderr.puts "(in #{Dir.pwd})" unless
685
+ options.silent or original_dir == location
686
+ end
687
+
688
+ def raw_load_rakefile # :nodoc:
689
+ rakefile, location = find_rakefile_location
690
+ if (!options.ignore_system) &&
691
+ (options.load_system || rakefile.nil?) &&
692
+ system_dir && File.directory?(system_dir)
693
+ print_rakefile_directory(location)
694
+ glob("#{system_dir}/*.rake") do |name|
695
+ add_import name
696
+ end
697
+ else
698
+ fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if
699
+ rakefile.nil?
700
+ @rakefile = rakefile
701
+ Dir.chdir(location)
702
+ print_rakefile_directory(location)
703
+ Rake.load_rakefile(File.expand_path(@rakefile)) if
704
+ @rakefile && @rakefile != ""
705
+ options.rakelib.each do |rlib|
706
+ glob("#{rlib}/*.rake") do |name|
707
+ add_import name
708
+ end
709
+ end
710
+ end
711
+ load_imports
712
+ end
713
+
714
+ def glob(path, &block) # :nodoc:
715
+ FileList.glob(path.tr("\\", "/")).each(&block)
716
+ end
717
+ private :glob
718
+
719
+ # The directory path containing the system wide rakefiles.
720
+ def system_dir # :nodoc:
721
+ @system_dir ||=
722
+ begin
723
+ if ENV["RAKE_SYSTEM"]
724
+ ENV["RAKE_SYSTEM"]
725
+ else
726
+ standard_system_dir
727
+ end
728
+ end
729
+ end
730
+
731
+ # The standard directory containing system wide rake files.
732
+ if Win32.windows?
733
+ def standard_system_dir #:nodoc:
734
+ Win32.win32_system_dir
735
+ end
736
+ else
737
+ def standard_system_dir #:nodoc:
738
+ File.join(File.expand_path("~"), ".rake")
739
+ end
740
+ end
741
+ private :standard_system_dir
742
+
743
+ # Collect the list of tasks on the command line. If no tasks are
744
+ # given, return a list containing only the default task.
745
+ # Environmental assignments are processed at this time as well.
746
+ #
747
+ # `args` is the list of arguments to peruse to get the list of tasks.
748
+ # It should be the command line that was given to rake, less any
749
+ # recognised command-line options, which OptionParser.parse will
750
+ # have taken care of already.
751
+ def collect_command_line_tasks(args) # :nodoc:
752
+ @top_level_tasks = []
753
+ args.each do |arg|
754
+ if arg =~ /^(\w+)=(.*)$/m
755
+ ENV[$1] = $2
756
+ else
757
+ @top_level_tasks << arg unless arg =~ /^-/
758
+ end
759
+ end
760
+ @top_level_tasks.push(default_task_name) if @top_level_tasks.empty?
761
+ end
762
+
763
+ # Default task name ("default").
764
+ # (May be overridden by subclasses)
765
+ def default_task_name # :nodoc:
766
+ "default"
767
+ end
768
+
769
+ # Add a file to the list of files to be imported.
770
+ def add_import(fn) # :nodoc:
771
+ @pending_imports << fn
772
+ end
773
+
774
+ # Load the pending list of imported files.
775
+ def load_imports # :nodoc:
776
+ while fn = @pending_imports.shift
777
+ next if @imported.member?(fn)
778
+ fn_task = lookup(fn) and fn_task.invoke
779
+ ext = File.extname(fn)
780
+ loader = @loaders[ext] || @default_loader
781
+ loader.load(fn)
782
+ if fn_task = lookup(fn) and fn_task.needed?
783
+ fn_task.reenable
784
+ fn_task.invoke
785
+ loader.load(fn)
786
+ end
787
+ @imported << fn
788
+ end
789
+ end
790
+
791
+ def rakefile_location(backtrace=caller) # :nodoc:
792
+ backtrace.map { |t| t[/([^:]+):/, 1] }
793
+
794
+ re = /^#{@rakefile}$/
795
+ re = /#{re.source}/i if windows?
796
+
797
+ backtrace.find { |str| str =~ re } || ""
798
+ end
799
+
800
+ def set_default_options # :nodoc:
801
+ options.always_multitask = false
802
+ options.backtrace = false
803
+ options.build_all = false
804
+ options.dryrun = false
805
+ options.ignore_deprecate = false
806
+ options.ignore_system = false
807
+ options.job_stats = false
808
+ options.load_system = false
809
+ options.nosearch = false
810
+ options.rakelib = %w[rakelib]
811
+ options.show_all_tasks = false
812
+ options.show_prereqs = false
813
+ options.show_task_pattern = nil
814
+ options.show_tasks = nil
815
+ options.silent = false
816
+ options.suppress_backtrace_pattern = nil
817
+ options.thread_pool_size = Rake.suggested_thread_count
818
+ options.trace = false
819
+ options.trace_output = $stderr
820
+ options.trace_rules = false
821
+ end
822
+
823
+ end
824
+ end