rake 0.4.11 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. data/CHANGES +288 -1
  2. data/README +101 -114
  3. data/Rakefile +269 -54
  4. data/TODO +1 -0
  5. data/bin/rake +26 -3
  6. data/doc/command_line_usage.rdoc +102 -0
  7. data/doc/jamis.rb +133 -106
  8. data/doc/rake.1.gz +0 -0
  9. data/doc/rakefile.rdoc +304 -4
  10. data/doc/release_notes/rake-0.4.14.rdoc +23 -0
  11. data/doc/release_notes/rake-0.4.15.rdoc +35 -0
  12. data/doc/release_notes/rake-0.5.0.rdoc +53 -0
  13. data/doc/release_notes/rake-0.5.3.rdoc +78 -0
  14. data/doc/release_notes/rake-0.5.4.rdoc +46 -0
  15. data/doc/release_notes/rake-0.6.0.rdoc +141 -0
  16. data/doc/release_notes/rake-0.7.0.rdoc +119 -0
  17. data/doc/release_notes/rake-0.7.1.rdoc +59 -0
  18. data/doc/release_notes/rake-0.7.2.rdoc +121 -0
  19. data/doc/release_notes/rake-0.7.3.rdoc +47 -0
  20. data/doc/release_notes/rake-0.8.0.rdoc +114 -0
  21. data/doc/release_notes/rake-0.8.2.rdoc +165 -0
  22. data/doc/release_notes/rake-0.8.3.rdoc +112 -0
  23. data/doc/release_notes/rake-0.8.4.rdoc +147 -0
  24. data/doc/release_notes/rake-0.8.5.rdoc +53 -0
  25. data/doc/release_notes/rake-0.8.6.rdoc +55 -0
  26. data/doc/release_notes/rake-0.8.7.rdoc +55 -0
  27. data/lib/rake/alt_system.rb +108 -0
  28. data/lib/rake/classic_namespace.rb +8 -0
  29. data/lib/rake/clean.rb +3 -1
  30. data/lib/rake/contrib/ftptools.rb +40 -26
  31. data/lib/rake/contrib/publisher.rb +1 -1
  32. data/lib/rake/contrib/rubyforgepublisher.rb +3 -3
  33. data/lib/rake/contrib/sshpublisher.rb +1 -1
  34. data/lib/rake/contrib/sys.rb +25 -23
  35. data/lib/rake/gempackagetask.rb +14 -15
  36. data/lib/rake/loaders/makefile.rb +42 -0
  37. data/lib/rake/packagetask.rb +65 -33
  38. data/lib/rake/rake_test_loader.rb +5 -0
  39. data/lib/rake/rdoctask.rb +112 -31
  40. data/lib/rake/ruby182_test_unit_fix.rb +23 -0
  41. data/lib/rake/runtest.rb +4 -4
  42. data/lib/rake/tasklib.rb +11 -12
  43. data/lib/rake/testtask.rb +67 -24
  44. data/lib/rake/win32.rb +55 -0
  45. data/lib/rake.rb +2087 -578
  46. data/test/capture_stdout.rb +26 -0
  47. data/test/check_expansion.rb +5 -0
  48. data/test/check_no_expansion.rb +5 -0
  49. data/test/data/chains/Rakefile +15 -0
  50. data/test/data/default/Rakefile +19 -0
  51. data/test/data/dryrun/Rakefile +22 -0
  52. data/test/data/file_creation_task/Rakefile +33 -0
  53. data/test/data/imports/Rakefile +19 -0
  54. data/test/data/imports/deps.mf +1 -0
  55. data/test/data/multidesc/Rakefile +17 -0
  56. data/test/data/namespace/Rakefile +57 -0
  57. data/test/data/rakelib/test1.rb +3 -0
  58. data/test/data/sample.mf +14 -0
  59. data/test/data/statusreturn/Rakefile +8 -0
  60. data/test/data/unittest/Rakefile +1 -0
  61. data/test/filecreation.rb +18 -12
  62. data/test/functional.rb +6 -73
  63. data/test/in_environment.rb +30 -0
  64. data/test/rake_test_setup.rb +24 -0
  65. data/test/reqfile.rb +3 -0
  66. data/test/reqfile2.rb +3 -0
  67. data/test/session_functional.rb +339 -0
  68. data/test/shellcommand.rb +0 -0
  69. data/test/test_application.rb +675 -0
  70. data/test/{testclean.rb → test_clean.rb} +1 -0
  71. data/test/test_definitions.rb +85 -0
  72. data/test/test_earlytime.rb +35 -0
  73. data/test/test_extension.rb +63 -0
  74. data/test/test_file_creation_task.rb +62 -0
  75. data/test/test_file_task.rb +143 -0
  76. data/test/test_filelist.rb +623 -0
  77. data/test/test_fileutils.rb +251 -0
  78. data/test/{testftp.rb → test_ftp.rb} +5 -1
  79. data/test/test_invocation_chain.rb +81 -0
  80. data/test/test_makefile_loader.rb +26 -0
  81. data/test/test_multitask.rb +45 -0
  82. data/test/test_namespace.rb +55 -0
  83. data/test/{testpackagetask.rb → test_package_task.rb} +49 -12
  84. data/test/test_pathmap.rb +210 -0
  85. data/test/test_pseudo_status.rb +26 -0
  86. data/test/test_rake.rb +41 -0
  87. data/test/test_rdoc_task.rb +88 -0
  88. data/test/test_require.rb +35 -0
  89. data/test/test_rules.rb +349 -0
  90. data/test/test_task_arguments.rb +89 -0
  91. data/test/test_task_manager.rb +173 -0
  92. data/test/test_tasklib.rb +12 -0
  93. data/test/test_tasks.rb +374 -0
  94. data/test/{testtesttask.rb → test_test_task.rb} +8 -2
  95. data/test/test_top_level_functions.rb +86 -0
  96. data/test/test_win32.rb +72 -0
  97. metadata +184 -84
  98. data/test/testfilelist.rb +0 -255
  99. data/test/testfileutils.rb +0 -83
  100. data/test/testtasks.rb +0 -371
  101. /data/test/contrib/{testsys.rb → test_sys.rb} +0 -0
data/lib/rake.rb CHANGED
@@ -1,284 +1,839 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  #--
4
- # Copyright (c) 2003, 2004 Jim Weirich
4
+
5
+ # Copyright 2003, 2004, 2005, 2006, 2007, 2008 by Jim Weirich (jim@weirichhouse.org)
5
6
  #
6
- # Permission is hereby granted, free of charge, to any person obtaining
7
- # a copy of this software and associated documentation files (the
8
- # "Software"), to deal in the Software without restriction, including
9
- # without limitation the rights to use, copy, modify, merge, publish,
10
- # distribute, sublicense, and/or sell copies of the Software, and to
11
- # permit persons to whom the Software is furnished to do so, subject to
12
- # the following conditions:
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to
9
+ # deal in the Software without restriction, including without limitation the
10
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
+ # sell copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
13
  #
14
- # The above copyright notice and this permission notice shall be
15
- # included in all copies or substantial portions of the Software.
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
16
  #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
+ # IN THE SOFTWARE.
24
24
  #++
25
25
  #
26
26
  # = Rake -- Ruby Make
27
- #
28
- # This is the main file for the Rake application. Normally it is
29
- # referenced as a library via a require statement, but it can be
30
- # distributed independently as an application.
27
+ #
28
+ # This is the main file for the Rake application. Normally it is referenced
29
+ # as a library via a require statement, but it can be distributed
30
+ # independently as an application.
31
31
 
32
- RAKEVERSION = '0.4.11'
32
+ RAKEVERSION = '0.8.7'
33
33
 
34
34
  require 'rbconfig'
35
- require 'ftools'
36
- require 'getoptlong'
37
35
  require 'fileutils'
36
+ require 'singleton'
37
+ require 'monitor'
38
+ require 'optparse'
39
+ require 'ostruct'
38
40
 
39
- $last_comment = nil
40
- $show_tasks = nil
41
- $show_prereqs = nil
42
- $trace = nil
43
- $dryrun = nil
41
+ require 'rake/win32'
42
+
43
+ $trace = false
44
44
 
45
45
  ######################################################################
46
- # A Task is the basic unit of work in a Rakefile. Tasks have
47
- # associated actions (possibly more than one) and a list of
48
- # prerequisites. When invoked, a task will first ensure that all of
49
- # its prerequisites have an opportunity to run and then it will
50
- # execute its own actions.
51
- #
52
- # Tasks are not usually created directly using the new method, but
53
- # rather use the +file+ and +task+ convenience methods.
46
+ # Rake extensions to Module.
54
47
  #
55
- class Task
56
- TASKS = Hash.new
57
- RULES = Array.new
48
+ class Module
49
+ # Check for an existing method in the current class before extending. IF
50
+ # the method already exists, then a warning is printed and the extension is
51
+ # not added. Otherwise the block is yielded and any definitions in the
52
+ # block will take effect.
53
+ #
54
+ # Usage:
55
+ #
56
+ # class String
57
+ # rake_extension("xyz") do
58
+ # def xyz
59
+ # ...
60
+ # end
61
+ # end
62
+ # end
63
+ #
64
+ def rake_extension(method)
65
+ if method_defined?(method)
66
+ $stderr.puts "WARNING: Possible conflict with Rake extension: #{self}##{method} already exists"
67
+ else
68
+ yield
69
+ end
70
+ end
71
+ end # module Module
58
72
 
59
- # List of prerequisites for a task.
60
- attr_reader :prerequisites
61
73
 
62
- # Comment for this task.
63
- attr_accessor :comment
74
+ ######################################################################
75
+ # User defined methods to be added to String.
76
+ #
77
+ class String
78
+ rake_extension("ext") do
79
+ # Replace the file extension with +newext+. If there is no extension on
80
+ # the string, append the new extension to the end. If the new extension
81
+ # is not given, or is the empty string, remove any existing extension.
82
+ #
83
+ # +ext+ is a user added method for the String class.
84
+ def ext(newext='')
85
+ return self.dup if ['.', '..'].include? self
86
+ if newext != ''
87
+ newext = (newext =~ /^\./) ? newext : ("." + newext)
88
+ end
89
+ self.chomp(File.extname(self)) << newext
90
+ end
91
+ end
64
92
 
65
- # Source dependency for rule synthesized tasks. Nil if task was not
66
- # sythesized from a rule.
67
- attr_accessor :source
93
+ rake_extension("pathmap") do
94
+ # Explode a path into individual components. Used by +pathmap+.
95
+ def pathmap_explode
96
+ head, tail = File.split(self)
97
+ return [self] if head == self
98
+ return [tail] if head == '.' || tail == '/'
99
+ return [head, tail] if head == '/'
100
+ return head.pathmap_explode + [tail]
101
+ end
102
+ protected :pathmap_explode
103
+
104
+ # Extract a partial path from the path. Include +n+ directories from the
105
+ # front end (left hand side) if +n+ is positive. Include |+n+|
106
+ # directories from the back end (right hand side) if +n+ is negative.
107
+ def pathmap_partial(n)
108
+ dirs = File.dirname(self).pathmap_explode
109
+ partial_dirs =
110
+ if n > 0
111
+ dirs[0...n]
112
+ elsif n < 0
113
+ dirs.reverse[0...-n].reverse
114
+ else
115
+ "."
116
+ end
117
+ File.join(partial_dirs)
118
+ end
119
+ protected :pathmap_partial
120
+
121
+ # Preform the pathmap replacement operations on the given path. The
122
+ # patterns take the form 'pat1,rep1;pat2,rep2...'.
123
+ def pathmap_replace(patterns, &block)
124
+ result = self
125
+ patterns.split(';').each do |pair|
126
+ pattern, replacement = pair.split(',')
127
+ pattern = Regexp.new(pattern)
128
+ if replacement == '*' && block_given?
129
+ result = result.sub(pattern, &block)
130
+ elsif replacement
131
+ result = result.sub(pattern, replacement)
132
+ else
133
+ result = result.sub(pattern, '')
134
+ end
135
+ end
136
+ result
137
+ end
138
+ protected :pathmap_replace
68
139
 
69
- # Create a task named +task_name+ with no actions or prerequisites..
70
- # use +enhance+ to add actions and prerequisites.
71
- def initialize(task_name)
72
- @name = task_name
73
- @prerequisites = []
74
- @actions = []
75
- @already_invoked = false
76
- @comment = nil
140
+ # Map the path according to the given specification. The specification
141
+ # controls the details of the mapping. The following special patterns are
142
+ # recognized:
143
+ #
144
+ # * <b>%p</b> -- The complete path.
145
+ # * <b>%f</b> -- The base file name of the path, with its file extension,
146
+ # but without any directories.
147
+ # * <b>%n</b> -- The file name of the path without its file extension.
148
+ # * <b>%d</b> -- The directory list of the path.
149
+ # * <b>%x</b> -- The file extension of the path. An empty string if there
150
+ # is no extension.
151
+ # * <b>%X</b> -- Everything *but* the file extension.
152
+ # * <b>%s</b> -- The alternate file separater if defined, otherwise use
153
+ # the standard file separator.
154
+ # * <b>%%</b> -- A percent sign.
155
+ #
156
+ # The %d specifier can also have a numeric prefix (e.g. '%2d'). If the
157
+ # number is positive, only return (up to) +n+ directories in the path,
158
+ # starting from the left hand side. If +n+ is negative, return (up to)
159
+ # |+n+| directories from the right hand side of the path.
160
+ #
161
+ # Examples:
162
+ #
163
+ # 'a/b/c/d/file.txt'.pathmap("%2d") => 'a/b'
164
+ # 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d'
165
+ #
166
+ # Also the %d, %p, %f, %n, %x, and %X operators can take a
167
+ # pattern/replacement argument to perform simple string substititions on a
168
+ # particular part of the path. The pattern and replacement are speparated
169
+ # by a comma and are enclosed by curly braces. The replacement spec comes
170
+ # after the % character but before the operator letter. (e.g.
171
+ # "%{old,new}d"). Muliple replacement specs should be separated by
172
+ # semi-colons (e.g. "%{old,new;src,bin}d").
173
+ #
174
+ # Regular expressions may be used for the pattern, and back refs may be
175
+ # used in the replacement text. Curly braces, commas and semi-colons are
176
+ # excluded from both the pattern and replacement text (let's keep parsing
177
+ # reasonable).
178
+ #
179
+ # For example:
180
+ #
181
+ # "src/org/onestepback/proj/A.java".pathmap("%{^src,bin}X.class")
182
+ #
183
+ # returns:
184
+ #
185
+ # "bin/org/onestepback/proj/A.class"
186
+ #
187
+ # If the replacement text is '*', then a block may be provided to perform
188
+ # some arbitrary calculation for the replacement.
189
+ #
190
+ # For example:
191
+ #
192
+ # "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext|
193
+ # ext.downcase
194
+ # }
195
+ #
196
+ # Returns:
197
+ #
198
+ # "/path/to/file.txt"
199
+ #
200
+ def pathmap(spec=nil, &block)
201
+ return self if spec.nil?
202
+ result = ''
203
+ spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag|
204
+ case frag
205
+ when '%f'
206
+ result << File.basename(self)
207
+ when '%n'
208
+ result << File.basename(self).ext
209
+ when '%d'
210
+ result << File.dirname(self)
211
+ when '%x'
212
+ result << File.extname(self)
213
+ when '%X'
214
+ result << self.ext
215
+ when '%p'
216
+ result << self
217
+ when '%s'
218
+ result << (File::ALT_SEPARATOR || File::SEPARATOR)
219
+ when '%-'
220
+ # do nothing
221
+ when '%%'
222
+ result << "%"
223
+ when /%(-?\d+)d/
224
+ result << pathmap_partial($1.to_i)
225
+ when /^%\{([^}]*)\}(\d*[dpfnxX])/
226
+ patterns, operator = $1, $2
227
+ result << pathmap('%' + operator).pathmap_replace(patterns, &block)
228
+ when /^%/
229
+ fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'"
230
+ else
231
+ result << frag
232
+ end
233
+ end
234
+ result
235
+ end
77
236
  end
237
+ end # class String
78
238
 
79
- # Enhance a task with prerequisites or actions. Returns self.
80
- def enhance(deps=nil, &block)
81
- @prerequisites |= deps if deps
82
- @actions << block if block_given?
83
- self
84
- end
239
+ ##############################################################################
240
+ module Rake
85
241
 
86
- # Name of the task.
87
- def name
88
- @name.to_s
242
+ # Errors -----------------------------------------------------------
243
+
244
+ # Error indicating an ill-formed task declaration.
245
+ class TaskArgumentError < ArgumentError
89
246
  end
90
247
 
91
- # Invoke the task if it is needed. Prerequites are invoked first.
92
- def invoke
93
- if $trace
94
- puts "** Invoke #{name} #{format_trace_flags}"
248
+ # Error indicating a recursion overflow error in task selection.
249
+ class RuleRecursionOverflowError < StandardError
250
+ def initialize(*args)
251
+ super
252
+ @targets = []
253
+ end
254
+
255
+ def add_target(target)
256
+ @targets << target
95
257
  end
96
- return if @already_invoked
97
- @already_invoked = true
98
- @prerequisites.each { |n| Task[n].invoke }
99
- execute if needed?
100
- end
101
258
 
102
- # Format the trace flags for display.
103
- def format_trace_flags
104
- flags = []
105
- flags << "first_time" unless @already_invoked
106
- flags << "not_needed" unless needed?
107
- flags.empty? ? "" : "(" + flags.join(", ") + ")"
259
+ def message
260
+ super + ": [" + @targets.reverse.join(' => ') + "]"
261
+ end
108
262
  end
109
- private :format_trace_flags
110
263
 
111
- # Execute the actions associated with this task.
112
- def execute
113
- puts "** Execute #{name}" if $trace
114
- self.class.enhance_with_matching_rule(name) if @actions.empty?
115
- @actions.each { |act| result = act.call(self) }
264
+ # --------------------------------------------------------------------------
265
+ # Rake module singleton methods.
266
+ #
267
+ class << self
268
+ # Current Rake Application
269
+ def application
270
+ @application ||= Rake::Application.new
271
+ end
272
+
273
+ # Set the current Rake application object.
274
+ def application=(app)
275
+ @application = app
276
+ end
277
+
278
+ # Return the original directory where the Rake application was started.
279
+ def original_dir
280
+ application.original_dir
281
+ end
282
+
116
283
  end
117
284
 
118
- # Is this task needed?
119
- def needed?
120
- true
285
+ ####################################################################
286
+ # Mixin for creating easily cloned objects.
287
+ #
288
+ module Cloneable
289
+ # Clone an object by making a new object and setting all the instance
290
+ # variables to the same values.
291
+ def dup
292
+ sibling = self.class.new
293
+ instance_variables.each do |ivar|
294
+ value = self.instance_variable_get(ivar)
295
+ new_value = value.clone rescue value
296
+ sibling.instance_variable_set(ivar, new_value)
297
+ end
298
+ sibling.taint if tainted?
299
+ sibling
300
+ end
301
+
302
+ def clone
303
+ sibling = dup
304
+ sibling.freeze if frozen?
305
+ sibling
306
+ end
121
307
  end
122
308
 
123
- # Timestamp for this task. Basic tasks return the current time for
124
- # their time stamp. Other tasks can be more sophisticated.
125
- def timestamp
126
- @prerequisites.collect { |p| Task[p].timestamp }.max || Time.now
309
+ ####################################################################
310
+ # Exit status class for times the system just gives us a nil.
311
+ class PseudoStatus
312
+ attr_reader :exitstatus
313
+ def initialize(code=0)
314
+ @exitstatus = code
315
+ end
316
+ def to_i
317
+ @exitstatus << 8
318
+ end
319
+ def >>(n)
320
+ to_i >> n
321
+ end
322
+ def stopped?
323
+ false
324
+ end
325
+ def exited?
326
+ true
327
+ end
127
328
  end
128
329
 
129
- # Add a comment to the task. If a comment alread exists, separate
130
- # the new comment with " / ".
131
- def add_comment(comment)
132
- return if ! $last_comment
133
- if @comment
134
- @comment << " / "
135
- else
136
- @comment = ''
330
+ ####################################################################
331
+ # TaskAguments manage the arguments passed to a task.
332
+ #
333
+ class TaskArguments
334
+ include Enumerable
335
+
336
+ attr_reader :names
337
+
338
+ # Create a TaskArgument object with a list of named arguments
339
+ # (given by :names) and a set of associated values (given by
340
+ # :values). :parent is the parent argument object.
341
+ def initialize(names, values, parent=nil)
342
+ @names = names
343
+ @parent = parent
344
+ @hash = {}
345
+ names.each_with_index { |name, i|
346
+ @hash[name.to_sym] = values[i] unless values[i].nil?
347
+ }
348
+ end
349
+
350
+ # Create a new argument scope using the prerequisite argument
351
+ # names.
352
+ def new_scope(names)
353
+ values = names.collect { |n| self[n] }
354
+ self.class.new(names, values, self)
355
+ end
356
+
357
+ # Find an argument value by name or index.
358
+ def [](index)
359
+ lookup(index.to_sym)
360
+ end
361
+
362
+ # Specify a hash of default values for task arguments. Use the
363
+ # defaults only if there is no specific value for the given
364
+ # argument.
365
+ def with_defaults(defaults)
366
+ @hash = defaults.merge(@hash)
367
+ end
368
+
369
+ def each(&block)
370
+ @hash.each(&block)
371
+ end
372
+
373
+ def method_missing(sym, *args, &block)
374
+ lookup(sym.to_sym)
375
+ end
376
+
377
+ def to_hash
378
+ @hash
379
+ end
380
+
381
+ def to_s
382
+ @hash.inspect
383
+ end
384
+
385
+ def inspect
386
+ to_s
387
+ end
388
+
389
+ protected
390
+
391
+ def lookup(name)
392
+ if @hash.has_key?(name)
393
+ @hash[name]
394
+ elsif ENV.has_key?(name.to_s)
395
+ ENV[name.to_s]
396
+ elsif ENV.has_key?(name.to_s.upcase)
397
+ ENV[name.to_s.upcase]
398
+ elsif @parent
399
+ @parent.lookup(name)
400
+ end
137
401
  end
138
- @comment << $last_comment
139
- $last_comment = nil
140
402
  end
141
403
 
142
- # Class Methods ----------------------------------------------------
404
+ EMPTY_TASK_ARGS = TaskArguments.new([], [])
143
405
 
144
- class << self
406
+ ####################################################################
407
+ # InvocationChain tracks the chain of task invocations to detect
408
+ # circular dependencies.
409
+ class InvocationChain
410
+ def initialize(value, tail)
411
+ @value = value
412
+ @tail = tail
413
+ end
414
+
415
+ def member?(obj)
416
+ @value == obj || @tail.member?(obj)
417
+ end
418
+
419
+ def append(value)
420
+ if member?(value)
421
+ fail RuntimeError, "Circular dependency detected: #{to_s} => #{value}"
422
+ end
423
+ self.class.new(value, self)
424
+ end
425
+
426
+ def to_s
427
+ "#{prefix}#{@value}"
428
+ end
429
+
430
+ def self.append(value, chain)
431
+ chain.append(value)
432
+ end
433
+
434
+ private
435
+
436
+ def prefix
437
+ "#{@tail.to_s} => "
438
+ end
439
+
440
+ class EmptyInvocationChain
441
+ def member?(obj)
442
+ false
443
+ end
444
+ def append(value)
445
+ InvocationChain.new(value, self)
446
+ end
447
+ def to_s
448
+ "TOP"
449
+ end
450
+ end
451
+
452
+ EMPTY = EmptyInvocationChain.new
453
+
454
+ end # class InvocationChain
455
+
456
+ end # module Rake
457
+
458
+ module Rake
459
+
460
+ ###########################################################################
461
+ # A Task is the basic unit of work in a Rakefile. Tasks have associated
462
+ # actions (possibly more than one) and a list of prerequisites. When
463
+ # invoked, a task will first ensure that all of its prerequisites have an
464
+ # opportunity to run and then it will execute its own actions.
465
+ #
466
+ # Tasks are not usually created directly using the new method, but rather
467
+ # use the +file+ and +task+ convenience methods.
468
+ #
469
+ class Task
470
+ # List of prerequisites for a task.
471
+ attr_reader :prerequisites
472
+
473
+ # List of actions attached to a task.
474
+ attr_reader :actions
475
+
476
+ # Application owning this task.
477
+ attr_accessor :application
478
+
479
+ # Comment for this task. Restricted to a single line of no more than 50
480
+ # characters.
481
+ attr_reader :comment
482
+
483
+ # Full text of the (possibly multi-line) comment.
484
+ attr_reader :full_comment
485
+
486
+ # Array of nested namespaces names used for task lookup by this task.
487
+ attr_reader :scope
145
488
 
146
- # Clear the task list. This cause rake to immediately forget all
147
- # the tasks that have been assigned. (Normally used in the unit
148
- # tests.)
489
+ # Return task name
490
+ def to_s
491
+ name
492
+ end
493
+
494
+ def inspect
495
+ "<#{self.class} #{name} => [#{prerequisites.join(', ')}]>"
496
+ end
497
+
498
+ # List of sources for task.
499
+ attr_writer :sources
500
+ def sources
501
+ @sources ||= []
502
+ end
503
+
504
+ # First source from a rule (nil if no sources)
505
+ def source
506
+ @sources.first if defined?(@sources)
507
+ end
508
+
509
+ # Create a task named +task_name+ with no actions or prerequisites. Use
510
+ # +enhance+ to add actions and prerequisites.
511
+ def initialize(task_name, app)
512
+ @name = task_name.to_s
513
+ @prerequisites = []
514
+ @actions = []
515
+ @already_invoked = false
516
+ @full_comment = nil
517
+ @comment = nil
518
+ @lock = Monitor.new
519
+ @application = app
520
+ @scope = app.current_scope
521
+ @arg_names = nil
522
+ end
523
+
524
+ # Enhance a task with prerequisites or actions. Returns self.
525
+ def enhance(deps=nil, &block)
526
+ @prerequisites |= deps if deps
527
+ @actions << block if block_given?
528
+ self
529
+ end
530
+
531
+ # Name of the task, including any namespace qualifiers.
532
+ def name
533
+ @name.to_s
534
+ end
535
+
536
+ # Name of task with argument list description.
537
+ def name_with_args # :nodoc:
538
+ if arg_description
539
+ "#{name}#{arg_description}"
540
+ else
541
+ name
542
+ end
543
+ end
544
+
545
+ # Argument description (nil if none).
546
+ def arg_description # :nodoc:
547
+ @arg_names ? "[#{(arg_names || []).join(',')}]" : nil
548
+ end
549
+
550
+ # Name of arguments for this task.
551
+ def arg_names
552
+ @arg_names || []
553
+ end
554
+
555
+ # Reenable the task, allowing its tasks to be executed if the task
556
+ # is invoked again.
557
+ def reenable
558
+ @already_invoked = false
559
+ end
560
+
561
+ # Clear the existing prerequisites and actions of a rake task.
149
562
  def clear
150
- TASKS.clear
151
- RULES.clear
563
+ clear_prerequisites
564
+ clear_actions
565
+ self
152
566
  end
153
567
 
154
- # List of all defined tasks.
155
- def tasks
156
- TASKS.keys.sort.collect { |tn| Task[tn] }
568
+ # Clear the existing prerequisites of a rake task.
569
+ def clear_prerequisites
570
+ prerequisites.clear
571
+ self
157
572
  end
158
573
 
159
- # Return a task with the given name. If the task is not currently
160
- # known, try to synthesize one from the defined rules. If no
161
- # rules are found, but an existing file matches the task name,
162
- # assume it is a file task with no dependencies or actions.
163
- def [](task_name)
164
- task_name = task_name.to_s
165
- if task = TASKS[task_name]
166
- return task
574
+ # Clear the existing actions on a rake task.
575
+ def clear_actions
576
+ actions.clear
577
+ self
578
+ end
579
+
580
+ # Invoke the task if it is needed. Prerequites are invoked first.
581
+ def invoke(*args)
582
+ task_args = TaskArguments.new(arg_names, args)
583
+ invoke_with_call_chain(task_args, InvocationChain::EMPTY)
584
+ end
585
+
586
+ # Same as invoke, but explicitly pass a call chain to detect
587
+ # circular dependencies.
588
+ def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
589
+ new_chain = InvocationChain.append(self, invocation_chain)
590
+ @lock.synchronize do
591
+ if application.options.trace
592
+ puts "** Invoke #{name} #{format_trace_flags}"
593
+ end
594
+ return if @already_invoked
595
+ @already_invoked = true
596
+ invoke_prerequisites(task_args, new_chain)
597
+ execute(task_args) if needed?
598
+ end
599
+ end
600
+ protected :invoke_with_call_chain
601
+
602
+ # Invoke all the prerequisites of a task.
603
+ def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
604
+ @prerequisites.each { |n|
605
+ prereq = application[n, @scope]
606
+ prereq_args = task_args.new_scope(prereq.arg_names)
607
+ prereq.invoke_with_call_chain(prereq_args, invocation_chain)
608
+ }
609
+ end
610
+
611
+ # Format the trace flags for display.
612
+ def format_trace_flags
613
+ flags = []
614
+ flags << "first_time" unless @already_invoked
615
+ flags << "not_needed" unless needed?
616
+ flags.empty? ? "" : "(" + flags.join(", ") + ")"
617
+ end
618
+ private :format_trace_flags
619
+
620
+ # Execute the actions associated with this task.
621
+ def execute(args=nil)
622
+ args ||= EMPTY_TASK_ARGS
623
+ if application.options.dryrun
624
+ puts "** Execute (dry run) #{name}"
625
+ return
167
626
  end
168
- if task = enhance_with_matching_rule(task_name)
169
- return task
627
+ if application.options.trace
628
+ puts "** Execute #{name}"
170
629
  end
171
- if File.exist?(task_name)
172
- return FileTask.define_task(task_name)
630
+ application.enhance_with_matching_rule(name) if @actions.empty?
631
+ @actions.each do |act|
632
+ case act.arity
633
+ when 1
634
+ act.call(self)
635
+ else
636
+ act.call(self, args)
637
+ end
173
638
  end
174
- fail "Don't know how to build task '#{task_name}'"
175
639
  end
176
640
 
177
- # TRUE if the task name is already defined.
178
- def task_defined?(task_name)
179
- task_name = task_name.to_s
180
- TASKS[task_name]
641
+ # Is this task needed?
642
+ def needed?
643
+ true
181
644
  end
182
645
 
183
- # Define a task given +args+ and an option block. If a rule with
184
- # the given name already exists, the prerequisites and actions are
185
- # added to the existing task. Returns the defined task.
186
- def define_task(args, &block)
187
- task_name, deps = resolve_args(args)
188
- deps = [deps] if (Symbol === deps) || (String === deps)
189
- deps = deps.collect {|d| d.to_s }
190
- t = lookup(task_name)
191
- t.add_comment($last_comment)
192
- t.enhance(deps, &block)
646
+ # Timestamp for this task. Basic tasks return the current time for their
647
+ # time stamp. Other tasks can be more sophisticated.
648
+ def timestamp
649
+ @prerequisites.collect { |p| application[p].timestamp }.max || Time.now
193
650
  end
194
651
 
195
- # Define a rule for synthesizing tasks.
196
- def create_rule(args, &block)
197
- pattern, deps = resolve_args(args)
198
- fail "Too many dependents specified in rule #{pattern}: #{deps.inspect}" if deps.size > 1
199
- pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern
200
- RULES << [pattern, deps, block]
652
+ # Add a description to the task. The description can consist of an option
653
+ # argument list (enclosed brackets) and an optional comment.
654
+ def add_description(description)
655
+ return if ! description
656
+ comment = description.strip
657
+ add_comment(comment) if comment && ! comment.empty?
201
658
  end
202
659
 
203
-
204
- # Lookup a task. Return an existing task if found, otherwise
205
- # create a task of the current type.
206
- def lookup(task_name)
207
- name = task_name.to_s
208
- TASKS[name] ||= self.new(task_name)
660
+ # Writing to the comment attribute is the same as adding a description.
661
+ def comment=(description)
662
+ add_description(description)
209
663
  end
210
664
 
211
- # If a rule can be found that matches the task name, enhance the
212
- # task with the prerequisites and actions from the rule. Set the
213
- # source attribute of the task appropriately for the rule. Return
214
- # the enhanced task or nil of no rule was found.
215
- def enhance_with_matching_rule(task_name)
216
- RULES.each do |pattern, extensions, block|
217
- if md = pattern.match(task_name)
218
- ext = extensions.first
219
- case ext
220
- when String
221
- source = task_name.sub(/\.[^.]*$/, ext)
222
- when Proc
223
- source = ext.call(task_name)
224
- else
225
- fail "Don't know how to handle rule dependent: #{ext.inspect}"
226
- end
227
- if File.exist?(source)
228
- task = FileTask.define_task({task_name => [source]}, &block)
229
- task.source = source
230
- return task
231
- end
232
- end
665
+ # Add a comment to the task. If a comment alread exists, separate
666
+ # the new comment with " / ".
667
+ def add_comment(comment)
668
+ if @full_comment
669
+ @full_comment << " / "
670
+ else
671
+ @full_comment = ''
672
+ end
673
+ @full_comment << comment
674
+ if @full_comment =~ /\A([^.]+?\.)( |$)/
675
+ @comment = $1
676
+ else
677
+ @comment = @full_comment
233
678
  end
234
- nil
235
679
  end
236
-
237
- private
680
+ private :add_comment
238
681
 
239
- # Resolve the arguments for a task/rule.
240
- def resolve_args(args)
241
- case args
242
- when Hash
243
- fail "Too Many Task Names: #{args.keys.join(' ')}" if args.size > 1
244
- fail "No Task Name Given" if args.size < 1
245
- task_name = args.keys[0]
246
- deps = args[task_name]
247
- deps = [deps] if (String===deps) || (Regexp===deps) || (Proc===deps)
682
+ # Set the names of the arguments for this task. +args+ should be
683
+ # an array of symbols, one for each argument name.
684
+ def set_arg_names(args)
685
+ @arg_names = args.map { |a| a.to_sym }
686
+ end
687
+
688
+ # Return a string describing the internal state of a task. Useful for
689
+ # debugging.
690
+ def investigation
691
+ result = "------------------------------\n"
692
+ result << "Investigating #{name}\n"
693
+ result << "class: #{self.class}\n"
694
+ result << "task needed: #{needed?}\n"
695
+ result << "timestamp: #{timestamp}\n"
696
+ result << "pre-requisites: \n"
697
+ prereqs = @prerequisites.collect {|name| application[name]}
698
+ prereqs.sort! {|a,b| a.timestamp <=> b.timestamp}
699
+ prereqs.each do |p|
700
+ result << "--#{p.name} (#{p.timestamp})\n"
701
+ end
702
+ latest_prereq = @prerequisites.collect{|n| application[n].timestamp}.max
703
+ result << "latest-prerequisite time: #{latest_prereq}\n"
704
+ result << "................................\n\n"
705
+ return result
706
+ end
707
+
708
+ # ----------------------------------------------------------------
709
+ # Rake Module Methods
710
+ #
711
+ class << self
712
+
713
+ # Clear the task list. This cause rake to immediately forget all the
714
+ # tasks that have been assigned. (Normally used in the unit tests.)
715
+ def clear
716
+ Rake.application.clear
717
+ end
718
+
719
+ # List of all defined tasks.
720
+ def tasks
721
+ Rake.application.tasks
722
+ end
723
+
724
+ # Return a task with the given name. If the task is not currently
725
+ # known, try to synthesize one from the defined rules. If no rules are
726
+ # found, but an existing file matches the task name, assume it is a file
727
+ # task with no dependencies or actions.
728
+ def [](task_name)
729
+ Rake.application[task_name]
730
+ end
731
+
732
+ # TRUE if the task name is already defined.
733
+ def task_defined?(task_name)
734
+ Rake.application.lookup(task_name) != nil
735
+ end
736
+
737
+ # Define a task given +args+ and an option block. If a rule with the
738
+ # given name already exists, the prerequisites and actions are added to
739
+ # the existing task. Returns the defined task.
740
+ def define_task(*args, &block)
741
+ Rake.application.define_task(self, *args, &block)
742
+ end
743
+
744
+ # Define a rule for synthesizing tasks.
745
+ def create_rule(*args, &block)
746
+ Rake.application.create_rule(*args, &block)
747
+ end
748
+
749
+ # Apply the scope to the task name according to the rules for
750
+ # this kind of task. Generic tasks will accept the scope as
751
+ # part of the name.
752
+ def scope_name(scope, task_name)
753
+ (scope + [task_name]).join(':')
754
+ end
755
+
756
+ end # class << Rake::Task
757
+ end # class Rake::Task
758
+
759
+
760
+ ###########################################################################
761
+ # A FileTask is a task that includes time based dependencies. If any of a
762
+ # FileTask's prerequisites have a timestamp that is later than the file
763
+ # represented by this task, then the file must be rebuilt (using the
764
+ # supplied actions).
765
+ #
766
+ class FileTask < Task
767
+
768
+ # Is this file task needed? Yes if it doesn't exist, or if its time stamp
769
+ # is out of date.
770
+ def needed?
771
+ ! File.exist?(name) || out_of_date?(timestamp)
772
+ end
773
+
774
+ # Time stamp for file task.
775
+ def timestamp
776
+ if File.exist?(name)
777
+ File.mtime(name.to_s)
248
778
  else
249
- task_name = args
250
- deps = []
779
+ Rake::EARLY
251
780
  end
252
- [task_name, deps]
253
781
  end
254
- end
255
- end
256
782
 
783
+ private
257
784
 
258
- ######################################################################
259
- # A FileTask is a task that includes time based dependencies. If any
260
- # of a FileTask's prerequisites have a timestamp that is later than
261
- # the file represented by this task, then the file must be rebuilt
262
- # (using the supplied actions).
263
- #
264
- class FileTask < Task
785
+ # Are there any prerequisites with a later time than the given time stamp?
786
+ def out_of_date?(stamp)
787
+ @prerequisites.any? { |n| application[n].timestamp > stamp}
788
+ end
789
+
790
+ # ----------------------------------------------------------------
791
+ # Task class methods.
792
+ #
793
+ class << self
794
+ # Apply the scope to the task name according to the rules for this kind
795
+ # of task. File based tasks ignore the scope when creating the name.
796
+ def scope_name(scope, task_name)
797
+ task_name
798
+ end
799
+ end
800
+ end # class Rake::FileTask
801
+
802
+ ###########################################################################
803
+ # A FileCreationTask is a file task that when used as a dependency will be
804
+ # needed if and only if the file has not been created. Once created, it is
805
+ # not re-triggered if any of its dependencies are newer, nor does trigger
806
+ # any rebuilds of tasks that depend on it whenever it is updated.
807
+ #
808
+ class FileCreationTask < FileTask
809
+ # Is this file task needed? Yes if it doesn't exist.
810
+ def needed?
811
+ ! File.exist?(name)
812
+ end
265
813
 
266
- # Is this file task needed? Yes if it doesn't exist, or if its time
267
- # stamp is out of date.
268
- def needed?
269
- return true unless File.exist?(name)
270
- latest_prereq = @prerequisites.collect{|n| Task[n].timestamp}.max
271
- return false if latest_prereq.nil?
272
- timestamp < latest_prereq
814
+ # Time stamp for file creation task. This time stamp is earlier
815
+ # than any other time stamp.
816
+ def timestamp
817
+ Rake::EARLY
818
+ end
273
819
  end
274
820
 
275
- # Time stamp for file task.
276
- def timestamp
277
- File.mtime(name.to_s)
821
+ ###########################################################################
822
+ # Same as a regular task, but the immediate prerequisites are done in
823
+ # parallel using Ruby threads.
824
+ #
825
+ class MultiTask < Task
826
+ private
827
+ def invoke_prerequisites(args, invocation_chain)
828
+ threads = @prerequisites.collect { |p|
829
+ Thread.new(p) { |r| application[r].invoke_with_call_chain(args, invocation_chain) }
830
+ }
831
+ threads.each { |t| t.join }
832
+ end
278
833
  end
279
- end
834
+ end # module Rake
280
835
 
281
- ######################################################################
836
+ ## ###########################################################################
282
837
  # Task Definition Functions ...
283
838
 
284
839
  # Declare a basic task.
@@ -288,8 +843,8 @@ end
288
843
  # rm_rf "html"
289
844
  # end
290
845
  #
291
- def task(args, &block)
292
- Task.define_task(args, &block)
846
+ def task(*args, &block)
847
+ Rake::Task.define_task(*args, &block)
293
848
  end
294
849
 
295
850
 
@@ -306,25 +861,55 @@ end
306
861
  # end
307
862
  # end
308
863
  #
309
- def file(args, &block)
310
- FileTask.define_task(args, &block)
864
+ def file(*args, &block)
865
+ Rake::FileTask.define_task(*args, &block)
311
866
  end
312
867
 
313
- # Declare a set of files tasks to create the given directories on
314
- # demand.
868
+ # Declare a file creation task.
869
+ # (Mainly used for the directory command).
870
+ def file_create(args, &block)
871
+ Rake::FileCreationTask.define_task(args, &block)
872
+ end
873
+
874
+ # Declare a set of files tasks to create the given directories on demand.
315
875
  #
316
876
  # Example:
317
877
  # directory "testdata/doc"
318
878
  #
319
879
  def directory(dir)
320
- while dir != '.' && dir != '/'
321
- file dir do |t|
880
+ Rake.each_dir_parent(dir) do |d|
881
+ file_create d do |t|
322
882
  mkdir_p t.name if ! File.exist?(t.name)
323
883
  end
324
- dir = File.dirname(dir)
325
884
  end
326
885
  end
327
886
 
887
+ # Declare a task that performs its prerequisites in parallel. Multitasks does
888
+ # *not* guarantee that its prerequisites will execute in any given order
889
+ # (which is obvious when you think about it)
890
+ #
891
+ # Example:
892
+ # multitask :deploy => [:deploy_gem, :deploy_rdoc]
893
+ #
894
+ def multitask(args, &block)
895
+ Rake::MultiTask.define_task(args, &block)
896
+ end
897
+
898
+ # Create a new rake namespace and use it for evaluating the given block.
899
+ # Returns a NameSpace object that can be used to lookup tasks defined in the
900
+ # namespace.
901
+ #
902
+ # E.g.
903
+ #
904
+ # ns = namespace "nested" do
905
+ # task :run
906
+ # end
907
+ # task_run = ns[:run] # find :run in the given namespace.
908
+ #
909
+ def namespace(name=nil, &block)
910
+ Rake.application.in_namespace(name, &block)
911
+ end
912
+
328
913
  # Declare a rule for auto-tasks.
329
914
  #
330
915
  # Example:
@@ -332,8 +917,8 @@ end
332
917
  # sh %{cc -o #{t.name} #{t.source}}
333
918
  # end
334
919
  #
335
- def rule(args, &block)
336
- Task.create_rule(args, &block)
920
+ def rule(*args, &block)
921
+ Rake::Task.create_rule(*args, &block)
337
922
  end
338
923
 
339
924
  # Describe the next rake task.
@@ -344,26 +929,55 @@ end
344
929
  # runtests
345
930
  # end
346
931
  #
347
- def desc(comment)
348
- $last_comment = comment
932
+ def desc(description)
933
+ Rake.application.last_description = description
349
934
  end
350
935
 
936
+ # Import the partial Rakefiles +fn+. Imported files are loaded _after_ the
937
+ # current file is completely loaded. This allows the import statement to
938
+ # appear anywhere in the importing file, and yet allowing the imported files
939
+ # to depend on objects defined in the importing file.
940
+ #
941
+ # A common use of the import statement is to include files containing
942
+ # dependency declarations.
943
+ #
944
+ # See also the --rakelibdir command line option.
945
+ #
946
+ # Example:
947
+ # import ".depend", "my_rules"
948
+ #
949
+ def import(*fns)
950
+ fns.each do |fn|
951
+ Rake.application.add_import(fn)
952
+ end
953
+ end
351
954
 
352
- ######################################################################
353
- # This a FileUtils extension that defines several additional commands
354
- # to be added to the FileUtils utility functions.
955
+ #############################################################################
956
+ # This a FileUtils extension that defines several additional commands to be
957
+ # added to the FileUtils utility functions.
355
958
  #
356
959
  module FileUtils
357
- RUBY = Config::CONFIG['ruby_install_name']
960
+ RUBY_EXT = ((Config::CONFIG['ruby_install_name'] =~ /\.(com|cmd|exe|bat|rb|sh)$/) ?
961
+ "" :
962
+ Config::CONFIG['EXEEXT'])
963
+
964
+ RUBY = File.join(
965
+ Config::CONFIG['bindir'],
966
+ Config::CONFIG['ruby_install_name'] + RUBY_EXT).
967
+ sub(/.*\s.*/m, '"\&"')
358
968
 
359
969
  OPT_TABLE['sh'] = %w(noop verbose)
360
970
  OPT_TABLE['ruby'] = %w(noop verbose)
361
971
 
362
- # Run the system command +cmd+.
972
+ # Run the system command +cmd+. If multiple arguments are given the command
973
+ # is not run with the shell (same semantics as Kernel::exec and
974
+ # Kernel::system).
363
975
  #
364
976
  # Example:
365
977
  # sh %{ls -ltr}
366
978
  #
979
+ # sh 'ls', 'file with spaces'
980
+ #
367
981
  # # check exit status after command runs
368
982
  # sh %{grep pattern file} do |ok, res|
369
983
  # if ! ok
@@ -371,47 +985,64 @@ module FileUtils
371
985
  # end
372
986
  # end
373
987
  #
374
- def sh(cmd, options={}, &block)
988
+ def sh(*cmd, &block)
989
+ options = (Hash === cmd.last) ? cmd.pop : {}
375
990
  unless block_given?
991
+ show_command = cmd.join(" ")
992
+ show_command = show_command[0,42] + "..." unless $trace
993
+ # TODO code application logic heref show_command.length > 45
376
994
  block = lambda { |ok, status|
377
- ok or fail "Command failed with status (#{status.exitstatus}): [#{cmd}]"
995
+ ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]"
378
996
  }
379
997
  end
380
- fu_check_options options, :noop, :verbose
381
- fu_output_message cmd if options[:verbose]
382
- unless options[:noop]
383
- res = system(cmd)
384
- block.call(res, $?)
998
+ if RakeFileUtils.verbose_flag == :default
999
+ options[:verbose] = true
1000
+ else
1001
+ options[:verbose] ||= RakeFileUtils.verbose_flag
1002
+ end
1003
+ options[:noop] ||= RakeFileUtils.nowrite_flag
1004
+ rake_check_options options, :noop, :verbose
1005
+ rake_output_message cmd.join(" ") if options[:verbose]
1006
+ unless options[:noop]
1007
+ res = rake_system(*cmd)
1008
+ status = $?
1009
+ status = PseudoStatus.new(1) if !res && status.nil?
1010
+ block.call(res, status)
385
1011
  end
386
1012
  end
387
1013
 
1014
+ def rake_system(*cmd)
1015
+ Rake::AltSystem.system(*cmd)
1016
+ end
1017
+ private :rake_system
1018
+
388
1019
  # Run a Ruby interpreter with the given arguments.
389
1020
  #
390
1021
  # Example:
391
1022
  # ruby %{-pe '$_.upcase!' <README}
392
1023
  #
393
- def ruby(*args)
394
- if Hash === args.last
395
- options = args.pop
1024
+ def ruby(*args,&block)
1025
+ options = (Hash === args.last) ? args.pop : {}
1026
+ if args.length > 1 then
1027
+ sh(*([RUBY] + args + [options]), &block)
396
1028
  else
397
- options = {}
1029
+ sh("#{RUBY} #{args.first}", options, &block)
398
1030
  end
399
- sh "#{RUBY} #{args.join(' ')}", options
400
1031
  end
401
-
1032
+
402
1033
  LN_SUPPORTED = [true]
403
1034
 
404
- # Attempt to do a normal file link, but fall back to a copy if the
405
- # link fails.
1035
+ # Attempt to do a normal file link, but fall back to a copy if the link
1036
+ # fails.
406
1037
  def safe_ln(*args)
407
1038
  unless LN_SUPPORTED[0]
408
1039
  cp(*args)
409
1040
  else
410
1041
  begin
411
- ln(*args)
412
- rescue Errno::EOPNOTSUPP
413
- LN_SUPPORTED[0] = false
414
- cp(*args)
1042
+ ln(*args)
1043
+ rescue StandardError, NotImplementedError => ex
1044
+ LN_SUPPORTED[0] = false
1045
+ cp(*args)
415
1046
  end
416
1047
  end
417
1048
  end
@@ -429,33 +1060,44 @@ module FileUtils
429
1060
  end
430
1061
  end
431
1062
 
432
- ######################################################################
433
- # RakeFileUtils provides a custom version of the FileUtils methods
434
- # that respond to the <tt>verbose</tt> and <tt>nowrite</tt> commands.
1063
+ #############################################################################
1064
+ # RakeFileUtils provides a custom version of the FileUtils methods that
1065
+ # respond to the <tt>verbose</tt> and <tt>nowrite</tt> commands.
435
1066
  #
436
1067
  module RakeFileUtils
437
1068
  include FileUtils
438
-
439
- $fileutils_output = $stderr
440
- $fileutils_label = ''
1069
+
1070
+ class << self
1071
+ attr_accessor :verbose_flag, :nowrite_flag
1072
+ end
1073
+ RakeFileUtils.verbose_flag = :default
1074
+ RakeFileUtils.nowrite_flag = false
1075
+
441
1076
  $fileutils_verbose = true
442
1077
  $fileutils_nowrite = false
443
-
1078
+
444
1079
  FileUtils::OPT_TABLE.each do |name, opts|
445
- next unless opts.include?('verbose')
1080
+ default_options = []
1081
+ if opts.include?(:verbose) || opts.include?("verbose")
1082
+ default_options << ':verbose => RakeFileUtils.verbose_flag'
1083
+ end
1084
+ if opts.include?(:noop) || opts.include?("noop")
1085
+ default_options << ':noop => RakeFileUtils.nowrite_flag'
1086
+ end
1087
+
1088
+ next if default_options.empty?
446
1089
  module_eval(<<-EOS, __FILE__, __LINE__ + 1)
447
1090
  def #{name}( *args, &block )
448
- super(*fu_merge_option(args,
449
- :verbose => $fileutils_verbose,
450
- :noop => $fileutils_nowrite),
451
- &block)
1091
+ super(
1092
+ *rake_merge_option(args,
1093
+ #{default_options.join(', ')}
1094
+ ), &block)
452
1095
  end
453
1096
  EOS
454
1097
  end
455
1098
 
456
- # Get/set the verbose flag controlling output from the FileUtils
457
- # utilities. If verbose is true, then the utility method is echoed
458
- # to standard output.
1099
+ # Get/set the verbose flag controlling output from the FileUtils utilities.
1100
+ # If verbose is true, then the utility method is echoed to standard output.
459
1101
  #
460
1102
  # Examples:
461
1103
  # verbose # return the current value of the verbose flag
@@ -463,21 +1105,20 @@ module RakeFileUtils
463
1105
  # verbose(v) { code } # Execute code with the verbose flag set temporarily to _v_.
464
1106
  # # Return to the original value when code is done.
465
1107
  def verbose(value=nil)
466
- oldvalue = $fileutils_verbose
467
- $fileutils_verbose = value unless value.nil?
1108
+ oldvalue = RakeFileUtils.verbose_flag
1109
+ RakeFileUtils.verbose_flag = value unless value.nil?
468
1110
  if block_given?
469
1111
  begin
470
- yield
1112
+ yield
471
1113
  ensure
472
- $fileutils_verbose = oldvalue
1114
+ RakeFileUtils.verbose_flag = oldvalue
473
1115
  end
474
1116
  end
475
- $fileutils_verbose
1117
+ RakeFileUtils.verbose_flag
476
1118
  end
477
1119
 
478
- # Get/set the nowrite flag controlling output from the FileUtils
479
- # utilities. If verbose is true, then the utility method is echoed
480
- # to standard output.
1120
+ # Get/set the nowrite flag controlling output from the FileUtils utilities.
1121
+ # If verbose is true, then the utility method is echoed to standard output.
481
1122
  #
482
1123
  # Examples:
483
1124
  # nowrite # return the current value of the nowrite flag
@@ -485,34 +1126,34 @@ module RakeFileUtils
485
1126
  # nowrite(v) { code } # Execute code with the nowrite flag set temporarily to _v_.
486
1127
  # # Return to the original value when code is done.
487
1128
  def nowrite(value=nil)
488
- oldvalue = $fileutils_nowrite
489
- $fileutils_nowrite = value unless value.nil?
1129
+ oldvalue = RakeFileUtils.nowrite_flag
1130
+ RakeFileUtils.nowrite_flag = value unless value.nil?
490
1131
  if block_given?
491
1132
  begin
492
- yield
1133
+ yield
493
1134
  ensure
494
- $fileutils_nowrite = oldvalue
1135
+ RakeFileUtils.nowrite_flag = oldvalue
495
1136
  end
496
1137
  end
497
1138
  oldvalue
498
1139
  end
499
1140
 
500
- # Use this function to prevent protentially destructive ruby code
501
- # from running when the :nowrite flag is set.
1141
+ # Use this function to prevent protentially destructive ruby code from
1142
+ # running when the :nowrite flag is set.
502
1143
  #
503
- # Example:
1144
+ # Example:
504
1145
  #
505
1146
  # when_writing("Building Project") do
506
1147
  # project.build
507
1148
  # end
508
1149
  #
509
- # The following code will build the project under normal conditions.
510
- # If the nowrite(true) flag is set, then the example will print:
1150
+ # The following code will build the project under normal conditions. If the
1151
+ # nowrite(true) flag is set, then the example will print:
511
1152
  # DRYRUN: Building Project
512
1153
  # instead of actually building the project.
513
1154
  #
514
1155
  def when_writing(msg=nil)
515
- if $fileutils_nowrite
1156
+ if RakeFileUtils.nowrite_flag
516
1157
  puts "DRYRUN: #{msg}" if msg
517
1158
  else
518
1159
  yield
@@ -520,7 +1161,7 @@ module RakeFileUtils
520
1161
  end
521
1162
 
522
1163
  # Merge the given options with the default values.
523
- def fu_merge_option(args, defaults)
1164
+ def rake_merge_option(args, defaults)
524
1165
  if Hash === args.last
525
1166
  defaults.update(args.last)
526
1167
  args.pop
@@ -528,51 +1169,126 @@ module RakeFileUtils
528
1169
  args.push defaults
529
1170
  args
530
1171
  end
531
- private :fu_merge_option
1172
+ private :rake_merge_option
1173
+
1174
+ # Send the message to the default rake output (which is $stderr).
1175
+ def rake_output_message(message)
1176
+ $stderr.puts(message)
1177
+ end
1178
+ private :rake_output_message
1179
+
1180
+ # Check that the options do not contain options not listed in +optdecl+. An
1181
+ # ArgumentError exception is thrown if non-declared options are found.
1182
+ def rake_check_options(options, *optdecl)
1183
+ h = options.dup
1184
+ optdecl.each do |name|
1185
+ h.delete name
1186
+ end
1187
+ raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
1188
+ end
1189
+ private :rake_check_options
532
1190
 
533
1191
  extend self
534
-
535
1192
  end
536
1193
 
1194
+ #############################################################################
1195
+ # Include the FileUtils file manipulation functions in the top level module,
1196
+ # but mark them private so that they don't unintentionally define methods on
1197
+ # other objects.
1198
+
537
1199
  include RakeFileUtils
1200
+ private(*FileUtils.instance_methods(false))
1201
+ private(*RakeFileUtils.instance_methods(false))
538
1202
 
1203
+ ######################################################################
539
1204
  module Rake
540
1205
 
541
- ####################################################################
542
- # A FileList is essentially an array with a few helper methods
543
- # defined to make file manipulation a bit easier.
1206
+ ###########################################################################
1207
+ # A FileList is essentially an array with a few helper methods defined to
1208
+ # make file manipulation a bit easier.
1209
+ #
1210
+ # FileLists are lazy. When given a list of glob patterns for possible files
1211
+ # to be included in the file list, instead of searching the file structures
1212
+ # to find the files, a FileList holds the pattern for latter use.
544
1213
  #
545
- class FileList < Array
1214
+ # This allows us to define a number of FileList to match any number of
1215
+ # files, but only search out the actual files when then FileList itself is
1216
+ # actually used. The key is that the first time an element of the
1217
+ # FileList/Array is requested, the pending patterns are resolved into a real
1218
+ # list of file names.
1219
+ #
1220
+ class FileList
546
1221
 
547
- # Rewrite all array methods (and to_s/inspect) to resolve the list
548
- # before running.
549
- method_list = Array.instance_methods - Object.instance_methods
550
- %w[to_a to_s inspect].each do |meth|
551
- method_list << meth unless method_list.include? meth
552
- end
553
- method_list.each_with_index do |sym, i|
554
- if sym =~ /^[A-Za-z_]+$/
555
- name = sym
1222
+ include Cloneable
1223
+
1224
+ # == Method Delegation
1225
+ #
1226
+ # The lazy evaluation magic of FileLists happens by implementing all the
1227
+ # array specific methods to call +resolve+ before delegating the heavy
1228
+ # lifting to an embedded array object (@items).
1229
+ #
1230
+ # In addition, there are two kinds of delegation calls. The regular kind
1231
+ # delegates to the @items array and returns the result directly. Well,
1232
+ # almost directly. It checks if the returned value is the @items object
1233
+ # itself, and if so will return the FileList object instead.
1234
+ #
1235
+ # The second kind of delegation call is used in methods that normally
1236
+ # return a new Array object. We want to capture the return value of these
1237
+ # methods and wrap them in a new FileList object. We enumerate these
1238
+ # methods in the +SPECIAL_RETURN+ list below.
1239
+
1240
+ # List of array methods (that are not in +Object+) that need to be
1241
+ # delegated.
1242
+ ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
1243
+
1244
+ # List of additional methods that must be delegated.
1245
+ MUST_DEFINE = %w[to_a inspect]
1246
+
1247
+ # List of methods that should not be delegated here (we define special
1248
+ # versions of them explicitly below).
1249
+ MUST_NOT_DEFINE = %w[to_a to_ary partition *]
1250
+
1251
+ # List of delegated methods that return new array values which need
1252
+ # wrapping.
1253
+ SPECIAL_RETURN = %w[
1254
+ map collect sort sort_by select find_all reject grep
1255
+ compact flatten uniq values_at
1256
+ + - & |
1257
+ ]
1258
+
1259
+ DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).collect{ |s| s.to_s }.sort.uniq
1260
+
1261
+ # Now do the delegation.
1262
+ DELEGATING_METHODS.each_with_index do |sym, i|
1263
+ if SPECIAL_RETURN.include?(sym)
1264
+ ln = __LINE__+1
1265
+ class_eval %{
1266
+ def #{sym}(*args, &block)
1267
+ resolve
1268
+ result = @items.send(:#{sym}, *args, &block)
1269
+ FileList.new.import(result)
1270
+ end
1271
+ }, __FILE__, ln
556
1272
  else
557
- name = i
558
- end
559
- alias_method "array_#{name}", sym
560
- class_eval %{
561
- def #{sym}(*args, &block)
562
- resolve if @pending
563
- array_#{name}(*args, &block)
564
- end
565
- }
1273
+ ln = __LINE__+1
1274
+ class_eval %{
1275
+ def #{sym}(*args, &block)
1276
+ resolve
1277
+ result = @items.send(:#{sym}, *args, &block)
1278
+ result.object_id == @items.object_id ? self : result
1279
+ end
1280
+ }, __FILE__, ln
1281
+ end
566
1282
  end
567
1283
 
568
- # Create a file list from the globbable patterns given. If you
569
- # wish to perform multiple includes or excludes at object build
570
- # time, use the "yield self" pattern.
1284
+ # Create a file list from the globbable patterns given. If you wish to
1285
+ # perform multiple includes or excludes at object build time, use the
1286
+ # "yield self" pattern.
571
1287
  #
572
1288
  # Example:
573
- # file_list = FileList.new['lib/**/*.rb', 'test/test*.rb']
1289
+ # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb')
574
1290
  #
575
- # pkg_files = FileList.new['lib/**/*'] do |fl|
1291
+ # pkg_files = FileList.new('lib/**/*') do |fl|
576
1292
  # fl.exclude(/\bCVS\b/)
577
1293
  # end
578
1294
  #
@@ -580,13 +1296,15 @@ module Rake
580
1296
  @pending_add = []
581
1297
  @pending = false
582
1298
  @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
1299
+ @exclude_procs = DEFAULT_IGNORE_PROCS.dup
583
1300
  @exclude_re = nil
1301
+ @items = []
584
1302
  patterns.each { |pattern| include(pattern) }
585
1303
  yield self if block_given?
586
1304
  end
587
1305
 
588
- # Add file names defined by glob patterns to the file list. If an
589
- # array is given, add each element of the array.
1306
+ # Add file names defined by glob patterns to the file list. If an array
1307
+ # is given, add each element of the array.
590
1308
  #
591
1309
  # Example:
592
1310
  # file_list.include("*.java", "*.cfg")
@@ -594,20 +1312,27 @@ module Rake
594
1312
  #
595
1313
  def include(*filenames)
596
1314
  # TODO: check for pending
597
- filenames.each do |fn| @pending_add << fn end
1315
+ filenames.each do |fn|
1316
+ if fn.respond_to? :to_ary
1317
+ include(*fn.to_ary)
1318
+ else
1319
+ @pending_add << fn
1320
+ end
1321
+ end
598
1322
  @pending = true
599
1323
  self
600
1324
  end
601
- alias :add :include
602
-
603
- # Register a list of file name patterns that should be excluded
604
- # from the list. Patterns may be regular expressions, glob
605
- # patterns or regular strings.
1325
+ alias :add :include
1326
+
1327
+ # Register a list of file name patterns that should be excluded from the
1328
+ # list. Patterns may be regular expressions, glob patterns or regular
1329
+ # strings. In addition, a block given to exclude will remove entries that
1330
+ # return true when given to the block.
606
1331
  #
607
- # Note that glob patterns are expanded against the file system.
608
- # If a file is explicitly added to a file list, but does not exist
609
- # in the file system, then an glob pattern in the exclude list
610
- # will not exclude the file.
1332
+ # Note that glob patterns are expanded against the file system. If a file
1333
+ # is explicitly added to a file list, but does not exist in the file
1334
+ # system, then an glob pattern in the exclude list will not exclude the
1335
+ # file.
611
1336
  #
612
1337
  # Examples:
613
1338
  # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
@@ -619,79 +1344,109 @@ module Rake
619
1344
  # If "a.c" is not a file, then ...
620
1345
  # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
621
1346
  #
622
- def exclude(*patterns)
623
- # TODO: check for pending
624
- patterns.each do |pat| @exclude_patterns << pat end
625
- if ! @pending
626
- calculate_exclude_regexp
627
- reject! { |fn| fn =~ @exclude_re }
1347
+ def exclude(*patterns, &block)
1348
+ patterns.each do |pat|
1349
+ @exclude_patterns << pat
628
1350
  end
1351
+ if block_given?
1352
+ @exclude_procs << block
1353
+ end
1354
+ resolve_exclude if ! @pending
629
1355
  self
630
1356
  end
631
1357
 
632
-
1358
+
1359
+ # Clear all the exclude patterns so that we exclude nothing.
633
1360
  def clear_exclude
634
1361
  @exclude_patterns = []
1362
+ @exclude_procs = []
635
1363
  calculate_exclude_regexp if ! @pending
1364
+ self
1365
+ end
1366
+
1367
+ # Define equality.
1368
+ def ==(array)
1369
+ to_ary == array
1370
+ end
1371
+
1372
+ # Return the internal array object.
1373
+ def to_a
1374
+ resolve
1375
+ @items
1376
+ end
1377
+
1378
+ # Return the internal array object.
1379
+ def to_ary
1380
+ to_a
1381
+ end
1382
+
1383
+ # Lie about our class.
1384
+ def is_a?(klass)
1385
+ klass == Array || super(klass)
1386
+ end
1387
+ alias kind_of? is_a?
1388
+
1389
+ # Redefine * to return either a string or a new file list.
1390
+ def *(other)
1391
+ result = @items * other
1392
+ case result
1393
+ when Array
1394
+ FileList.new.import(result)
1395
+ else
1396
+ result
1397
+ end
636
1398
  end
637
1399
 
638
1400
  # Resolve all the pending adds now.
639
1401
  def resolve
640
- @pending = false
641
- @pending_add.each do |fn| resolve_add(fn) end
642
- @pending_add = []
643
- resolve_exclude
1402
+ if @pending
1403
+ @pending = false
1404
+ @pending_add.each do |fn| resolve_add(fn) end
1405
+ @pending_add = []
1406
+ resolve_exclude
1407
+ end
644
1408
  self
645
1409
  end
646
1410
 
647
1411
  def calculate_exclude_regexp
648
1412
  ignores = []
649
1413
  @exclude_patterns.each do |pat|
650
- case pat
651
- when Regexp
652
- ignores << pat
653
- when /[*.]/
654
- Dir[pat].each do |p| ignores << p end
655
- else
656
- ignores << Regexp.quote(pat)
657
- end
1414
+ case pat
1415
+ when Regexp
1416
+ ignores << pat
1417
+ when /[*?]/
1418
+ Dir[pat].each do |p| ignores << p end
1419
+ else
1420
+ ignores << Regexp.quote(pat)
1421
+ end
658
1422
  end
659
1423
  if ignores.empty?
660
- @exclude_re = /^$/
1424
+ @exclude_re = /^$/
661
1425
  else
662
- re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
663
- @exclude_re = Regexp.new(re_str)
1426
+ re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
1427
+ @exclude_re = Regexp.new(re_str)
664
1428
  end
665
1429
  end
666
1430
 
667
1431
  def resolve_add(fn)
668
1432
  case fn
669
- when Array
670
- fn.each { |f| self.resolve_add(f) }
671
- when %r{[*?]}
672
- add_matching(fn)
1433
+ when %r{[*?\[\{]}
1434
+ add_matching(fn)
673
1435
  else
674
- self << fn
1436
+ self << fn
675
1437
  end
676
1438
  end
1439
+ private :resolve_add
677
1440
 
678
1441
  def resolve_exclude
679
- @exclude_patterns.each do |pat|
680
- case pat
681
- when Regexp
682
- reject! { |fn| fn =~ pat }
683
- when /[*.]/
684
- reject_list = Dir[pat]
685
- reject! { |fn| reject_list.include?(fn) }
686
- else
687
- reject! { |fn| fn == pat }
688
- end
689
- end
1442
+ calculate_exclude_regexp
1443
+ reject! { |fn| exclude?(fn) }
690
1444
  self
691
1445
  end
1446
+ private :resolve_exclude
692
1447
 
693
- # Return a new FileList with the results of running +sub+ against
694
- # each element of the oringal list.
1448
+ # Return a new FileList with the results of running +sub+ against each
1449
+ # element of the oringal list.
695
1450
  #
696
1451
  # Example:
697
1452
  # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
@@ -700,8 +1455,8 @@ module Rake
700
1455
  inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
701
1456
  end
702
1457
 
703
- # Return a new FileList with the results of running +gsub+ against
704
- # each element of the original list.
1458
+ # Return a new FileList with the results of running +gsub+ against each
1459
+ # element of the original list.
705
1460
  #
706
1461
  # Example:
707
1462
  # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
@@ -723,16 +1478,84 @@ module Rake
723
1478
  self
724
1479
  end
725
1480
 
1481
+ # Apply the pathmap spec to each of the included file names, returning a
1482
+ # new file list with the modified paths. (See String#pathmap for
1483
+ # details.)
1484
+ def pathmap(spec=nil)
1485
+ collect { |fn| fn.pathmap(spec) }
1486
+ end
1487
+
1488
+ # Return a new FileList with <tt>String#ext</tt> method applied
1489
+ # to each member of the array.
1490
+ #
1491
+ # This method is a shortcut for:
1492
+ #
1493
+ # array.collect { |item| item.ext(newext) }
1494
+ #
1495
+ # +ext+ is a user added method for the Array class.
1496
+ def ext(newext='')
1497
+ collect { |fn| fn.ext(newext) }
1498
+ end
1499
+
1500
+
1501
+ # Grep each of the files in the filelist using the given pattern. If a
1502
+ # block is given, call the block on each matching line, passing the file
1503
+ # name, line number, and the matching line of text. If no block is given,
1504
+ # a standard emac style file:linenumber:line message will be printed to
1505
+ # standard out.
1506
+ def egrep(pattern, *options)
1507
+ each do |fn|
1508
+ open(fn, "rb", *options) do |inf|
1509
+ count = 0
1510
+ inf.each do |line|
1511
+ count += 1
1512
+ if pattern.match(line)
1513
+ if block_given?
1514
+ yield fn, count, line
1515
+ else
1516
+ puts "#{fn}:#{count}:#{line}"
1517
+ end
1518
+ end
1519
+ end
1520
+ end
1521
+ end
1522
+ end
1523
+
1524
+ # Return a new file list that only contains file names from the current
1525
+ # file list that exist on the file system.
1526
+ def existing
1527
+ select { |fn| File.exist?(fn) }
1528
+ end
1529
+
1530
+ # Modify the current file list so that it contains only file name that
1531
+ # exist on the file system.
1532
+ def existing!
1533
+ resolve
1534
+ @items = @items.select { |fn| File.exist?(fn) }
1535
+ self
1536
+ end
1537
+
1538
+ # FileList version of partition. Needed because the nested arrays should
1539
+ # be FileLists in this version.
1540
+ def partition(&block) # :nodoc:
1541
+ resolve
1542
+ result = @items.partition(&block)
1543
+ [
1544
+ FileList.new.import(result[0]),
1545
+ FileList.new.import(result[1]),
1546
+ ]
1547
+ end
1548
+
726
1549
  # Convert a FileList to a string by joining all elements with a space.
727
1550
  def to_s
728
- resolve if @pending
1551
+ resolve
729
1552
  self.join(' ')
730
1553
  end
731
1554
 
732
1555
  # Add matching glob patterns.
733
1556
  def add_matching(pattern)
734
1557
  Dir[pattern].each do |fn|
735
- self << fn unless exclude?(fn)
1558
+ self << fn unless exclude?(fn)
736
1559
  end
737
1560
  end
738
1561
  private :add_matching
@@ -740,258 +1563,944 @@ module Rake
740
1563
  # Should the given file name be excluded?
741
1564
  def exclude?(fn)
742
1565
  calculate_exclude_regexp unless @exclude_re
743
- fn =~ @exclude_re
1566
+ fn =~ @exclude_re || @exclude_procs.any? { |p| p.call(fn) }
744
1567
  end
745
1568
 
746
1569
  DEFAULT_IGNORE_PATTERNS = [
747
1570
  /(^|[\/\\])CVS([\/\\]|$)/,
1571
+ /(^|[\/\\])\.svn([\/\\]|$)/,
748
1572
  /\.bak$/,
749
- /~$/,
750
- /(^|[\/\\])core$/
1573
+ /~$/
751
1574
  ]
752
- @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
1575
+ DEFAULT_IGNORE_PROCS = [
1576
+ proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) }
1577
+ ]
1578
+ # @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
1579
+
1580
+ def import(array)
1581
+ @items = array
1582
+ self
1583
+ end
753
1584
 
754
1585
  class << self
755
1586
  # Create a new file list including the files listed. Similar to:
756
1587
  #
757
1588
  # FileList.new(*args)
758
1589
  def [](*args)
759
- new(*args)
1590
+ new(*args)
760
1591
  end
1592
+ end
1593
+ end # FileList
1594
+ end
761
1595
 
762
- # Set the ignore patterns back to the default value. The
763
- # default patterns will ignore files
764
- # * containing "CVS" in the file path
765
- # * ending with ".bak"
766
- # * ending with "~"
767
- # * named "core"
768
- #
769
- # Note that file names beginning with "." are automatically
770
- # ignored by Ruby's glob patterns and are not specifically
771
- # listed in the ignore patterns.
772
- def select_default_ignore_patterns
773
- @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
774
- end
1596
+ module Rake
1597
+ class << self
775
1598
 
776
- # Clear the ignore patterns.
777
- def clear_ignore_patterns
778
- @exclude_patterns = [ /^$/ ]
1599
+ # Yield each file or directory component.
1600
+ def each_dir_parent(dir) # :nodoc:
1601
+ old_length = nil
1602
+ while dir != '.' && dir.length != old_length
1603
+ yield(dir)
1604
+ old_length = dir.length
1605
+ dir = File.dirname(dir)
779
1606
  end
780
1607
  end
781
1608
  end
782
- end
1609
+ end # module Rake
783
1610
 
784
1611
  # Alias FileList to be available at the top level.
785
1612
  FileList = Rake::FileList
786
1613
 
787
- ######################################################################
788
- # Rake main application object. When invoking +rake+ from the command
789
- # line, a RakeApp object is created and run.
1614
+ #############################################################################
1615
+ module Rake
1616
+
1617
+ # Default Rakefile loader used by +import+.
1618
+ class DefaultLoader
1619
+ def load(fn)
1620
+ Kernel.load(File.expand_path(fn))
1621
+ end
1622
+ end
1623
+
1624
+ # EarlyTime is a fake timestamp that occurs _before_ any other time value.
1625
+ class EarlyTime
1626
+ include Comparable
1627
+ include Singleton
1628
+
1629
+ def <=>(other)
1630
+ -1
1631
+ end
1632
+
1633
+ def to_s
1634
+ "<EARLY TIME>"
1635
+ end
1636
+ end
1637
+
1638
+ EARLY = EarlyTime.instance
1639
+ end # module Rake
1640
+
1641
+ #############################################################################
1642
+ # Extensions to time to allow comparisons with an early time class.
790
1643
  #
791
- class RakeApp
792
- RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb']
793
-
794
- OPTIONS = [
795
- ['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
796
- "Do a dry run without executing actions."],
797
- ['--help', '-H', GetoptLong::NO_ARGUMENT,
798
- "Display this help message."],
799
- ['--libdir', '-I', GetoptLong::REQUIRED_ARGUMENT,
800
- "Include LIBDIR in the search path for required modules."],
801
- ['--nosearch', '-N', GetoptLong::NO_ARGUMENT,
802
- "Do not search parent directories for the Rakefile."],
803
- ['--prereqs', '-P', GetoptLong::NO_ARGUMENT,
804
- "Display the tasks and dependencies, then exit."],
805
- ['--quiet', '-q', GetoptLong::NO_ARGUMENT,
806
- "Do not log messages to standard output."],
807
- ['--rakefile', '-f', GetoptLong::REQUIRED_ARGUMENT,
808
- "Use FILE as the rakefile."],
809
- ['--require', '-r', GetoptLong::REQUIRED_ARGUMENT,
810
- "Require MODULE before executing rakefile."],
811
- ['--tasks', '-T', GetoptLong::NO_ARGUMENT,
812
- "Display the tasks and dependencies, then exit."],
813
- ['--trace', '-t', GetoptLong::NO_ARGUMENT,
814
- "Turn on invoke/execute tracing."],
815
- ['--usage', '-h', GetoptLong::NO_ARGUMENT,
816
- "Display usage."],
817
- ['--verbose', '-v', GetoptLong::NO_ARGUMENT,
818
- "Log message to standard output (default)."],
819
- ['--version', '-V', GetoptLong::NO_ARGUMENT,
820
- "Display the program version."],
821
- ]
822
-
823
- # Create a RakeApp object.
824
- def initialize
825
- @rakefile = nil
826
- @nosearch = false
827
- end
828
-
829
- # True if one of the files in RAKEFILES is in the current directory.
830
- # If a match is found, it is copied into @rakefile.
831
- def have_rakefile
832
- RAKEFILES.each do |fn|
833
- if File.exist?(fn)
834
- @rakefile = fn
835
- return true
836
- end
837
- end
838
- return false
839
- end
840
-
841
- # Display the program usage line.
842
- def usage
843
- puts "rake [-f rakefile] {options} targets..."
844
- end
845
-
846
- # Display the rake command line help.
847
- def help
848
- usage
849
- puts
850
- puts "Options are ..."
851
- puts
852
- OPTIONS.sort.each do |long, short, mode, desc|
853
- if mode == GetoptLong::REQUIRED_ARGUMENT
854
- if desc =~ /\b([A-Z]{2,})\b/
855
- long = long + "=#{$1}"
856
- end
857
- end
858
- printf " %-20s (%s)\n", long, short
859
- printf " %s\n", desc
860
- end
861
- end
862
-
863
- # Display the tasks and dependencies.
864
- def display_tasks_and_comments
865
- width = Task.tasks.select { |t|
866
- t.comment
867
- }.collect { |t|
868
- t.name.length
869
- }.max
870
- Task.tasks.each do |t|
871
- if t.comment
872
- printf "rake %-#{width}s # %s\n", t.name, t.comment
873
- end
874
- end
875
- end
876
-
877
- # Display the tasks and prerequisites
878
- def display_prerequisites
879
- Task.tasks.each do |t|
880
- puts "rake #{t.name}"
881
- t.prerequisites.each { |pre| puts " #{pre}" }
882
- end
883
- end
884
-
885
- # Return a list of the command line options supported by the
886
- # program.
887
- def command_line_options
888
- OPTIONS.collect { |lst| lst[0..-2] }
889
- end
890
-
891
- # Do the option defined by +opt+ and +value+.
892
- def do_option(opt, value)
893
- case opt
894
- when '--dry-run'
895
- verbose(true)
896
- nowrite(true)
897
- $dryrun = true
898
- $trace = true
899
- when '--help'
900
- help
901
- exit
902
- when '--libdir'
903
- $:.push(value)
904
- when '--nosearch'
905
- @nosearch = true
906
- when '--prereqs'
907
- $show_prereqs = true
908
- when '--quiet'
909
- verbose(false)
910
- when '--rakefile'
911
- RAKEFILES.clear
912
- RAKEFILES << value
913
- when '--require'
914
- require value
915
- when '--tasks'
916
- $show_tasks = true
917
- when '--trace'
918
- $trace = true
919
- verbose(true)
920
- when '--usage'
921
- usage
922
- exit
923
- when '--verbose'
924
- verbose(true)
925
- when '--version'
926
- puts "rake, version #{RAKEVERSION}"
927
- exit
1644
+ class Time
1645
+ alias rake_original_time_compare :<=>
1646
+ def <=>(other)
1647
+ if Rake::EarlyTime === other
1648
+ - other.<=>(self)
928
1649
  else
929
- fail "Unknown option: #{opt}"
1650
+ rake_original_time_compare(other)
930
1651
  end
931
1652
  end
932
-
933
- # Read and handle the command line options.
934
- def handle_options
935
- opts = GetoptLong.new(*command_line_options)
936
- opts.each { |opt, value| do_option(opt, value) }
937
- end
1653
+ end # class Time
1654
+
1655
+ module Rake
938
1656
 
939
- def load_rakefile
940
- here = Dir.pwd
941
- while ! have_rakefile
942
- Dir.chdir("..")
943
- if Dir.pwd == here || @nosearch
944
- fail "No Rakefile found (looking for: #{RAKEFILES.join(', ')})"
1657
+ ####################################################################
1658
+ # The NameSpace class will lookup task names in the the scope
1659
+ # defined by a +namespace+ command.
1660
+ #
1661
+ class NameSpace
1662
+
1663
+ # Create a namespace lookup object using the given task manager
1664
+ # and the list of scopes.
1665
+ def initialize(task_manager, scope_list)
1666
+ @task_manager = task_manager
1667
+ @scope = scope_list.dup
1668
+ end
1669
+
1670
+ # Lookup a task named +name+ in the namespace.
1671
+ def [](name)
1672
+ @task_manager.lookup(name, @scope)
1673
+ end
1674
+
1675
+ # Return the list of tasks defined in this and nested namespaces.
1676
+ def tasks
1677
+ @task_manager.tasks_in_scope(@scope)
1678
+ end
1679
+ end # NameSpace
1680
+
1681
+
1682
+ ####################################################################
1683
+ # The TaskManager module is a mixin for managing tasks.
1684
+ module TaskManager
1685
+ # Track the last comment made in the Rakefile.
1686
+ attr_accessor :last_description
1687
+ alias :last_comment :last_description # Backwards compatibility
1688
+
1689
+ def initialize
1690
+ super
1691
+ @tasks = Hash.new
1692
+ @rules = Array.new
1693
+ @scope = Array.new
1694
+ @last_description = nil
1695
+ end
1696
+
1697
+ def create_rule(*args, &block)
1698
+ pattern, arg_names, deps = resolve_args(args)
1699
+ pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern
1700
+ @rules << [pattern, deps, block]
1701
+ end
1702
+
1703
+ def define_task(task_class, *args, &block)
1704
+ task_name, arg_names, deps = resolve_args(args)
1705
+ task_name = task_class.scope_name(@scope, task_name)
1706
+ deps = [deps] unless deps.respond_to?(:to_ary)
1707
+ deps = deps.collect {|d| d.to_s }
1708
+ task = intern(task_class, task_name)
1709
+ task.set_arg_names(arg_names) unless arg_names.empty?
1710
+ task.add_description(@last_description)
1711
+ @last_description = nil
1712
+ task.enhance(deps, &block)
1713
+ task
1714
+ end
1715
+
1716
+ # Lookup a task. Return an existing task if found, otherwise
1717
+ # create a task of the current type.
1718
+ def intern(task_class, task_name)
1719
+ @tasks[task_name.to_s] ||= task_class.new(task_name, self)
1720
+ end
1721
+
1722
+ # Find a matching task for +task_name+.
1723
+ def [](task_name, scopes=nil)
1724
+ task_name = task_name.to_s
1725
+ self.lookup(task_name, scopes) or
1726
+ enhance_with_matching_rule(task_name) or
1727
+ synthesize_file_task(task_name) or
1728
+ fail "Don't know how to build task '#{task_name}'"
1729
+ end
1730
+
1731
+ def synthesize_file_task(task_name)
1732
+ return nil unless File.exist?(task_name)
1733
+ define_task(Rake::FileTask, task_name)
1734
+ end
1735
+
1736
+ # Resolve the arguments for a task/rule. Returns a triplet of
1737
+ # [task_name, arg_name_list, prerequisites].
1738
+ def resolve_args(args)
1739
+ if args.last.is_a?(Hash)
1740
+ deps = args.pop
1741
+ resolve_args_with_dependencies(args, deps)
1742
+ else
1743
+ resolve_args_without_dependencies(args)
945
1744
  end
946
- here = Dir.pwd
947
1745
  end
948
- puts "(in #{Dir.pwd})"
949
- $rakefile = @rakefile
950
- load @rakefile
951
- end
952
1746
 
953
- # Collect the list of tasks on the command line. If no tasks are
954
- # give, return a list containing only the default task.
955
- # Environmental assignments are processed at this time as well.
956
- def collect_tasks
957
- tasks = []
958
- ARGV.each do |arg|
959
- if arg =~ /^(\w+)=(.*)$/
960
- ENV[$1] = $2
1747
+ # Resolve task arguments for a task or rule when there are no
1748
+ # dependencies declared.
1749
+ #
1750
+ # The patterns recognized by this argument resolving function are:
1751
+ #
1752
+ # task :t
1753
+ # task :t, [:a]
1754
+ # task :t, :a (deprecated)
1755
+ #
1756
+ def resolve_args_without_dependencies(args)
1757
+ task_name = args.shift
1758
+ if args.size == 1 && args.first.respond_to?(:to_ary)
1759
+ arg_names = args.first.to_ary
961
1760
  else
962
- tasks << arg
1761
+ arg_names = args
963
1762
  end
1763
+ [task_name, arg_names, []]
1764
+ end
1765
+ private :resolve_args_without_dependencies
1766
+
1767
+ # Resolve task arguments for a task or rule when there are
1768
+ # dependencies declared.
1769
+ #
1770
+ # The patterns recognized by this argument resolving function are:
1771
+ #
1772
+ # task :t => [:d]
1773
+ # task :t, [a] => [:d]
1774
+ # task :t, :needs => [:d] (deprecated)
1775
+ # task :t, :a, :needs => [:d] (deprecated)
1776
+ #
1777
+ def resolve_args_with_dependencies(args, hash) # :nodoc:
1778
+ fail "Task Argument Error" if hash.size != 1
1779
+ key, value = hash.map { |k, v| [k,v] }.first
1780
+ if args.empty?
1781
+ task_name = key
1782
+ arg_names = []
1783
+ deps = value
1784
+ elsif key == :needs
1785
+ task_name = args.shift
1786
+ arg_names = args
1787
+ deps = value
1788
+ else
1789
+ task_name = args.shift
1790
+ arg_names = key
1791
+ deps = value
1792
+ end
1793
+ deps = [deps] unless deps.respond_to?(:to_ary)
1794
+ [task_name, arg_names, deps]
1795
+ end
1796
+ private :resolve_args_with_dependencies
1797
+
1798
+ # If a rule can be found that matches the task name, enhance the
1799
+ # task with the prerequisites and actions from the rule. Set the
1800
+ # source attribute of the task appropriately for the rule. Return
1801
+ # the enhanced task or nil of no rule was found.
1802
+ def enhance_with_matching_rule(task_name, level=0)
1803
+ fail Rake::RuleRecursionOverflowError,
1804
+ "Rule Recursion Too Deep" if level >= 16
1805
+ @rules.each do |pattern, extensions, block|
1806
+ if md = pattern.match(task_name)
1807
+ task = attempt_rule(task_name, extensions, block, level)
1808
+ return task if task
1809
+ end
1810
+ end
1811
+ nil
1812
+ rescue Rake::RuleRecursionOverflowError => ex
1813
+ ex.add_target(task_name)
1814
+ fail ex
1815
+ end
1816
+
1817
+ # List of all defined tasks in this application.
1818
+ def tasks
1819
+ @tasks.values.sort_by { |t| t.name }
1820
+ end
1821
+
1822
+ # List of all the tasks defined in the given scope (and its
1823
+ # sub-scopes).
1824
+ def tasks_in_scope(scope)
1825
+ prefix = scope.join(":")
1826
+ tasks.select { |t|
1827
+ /^#{prefix}:/ =~ t.name
1828
+ }
964
1829
  end
965
- tasks.push("default") if tasks.size == 0
966
- tasks
967
- end
968
1830
 
969
- # Run the +rake+ application.
970
- def run
971
- handle_options
972
- begin
973
- tasks = collect_tasks
974
- load_rakefile
975
- if $show_tasks
976
- display_tasks_and_comments
977
- elsif $show_prereqs
978
- display_prerequisites
1831
+ # Clear all tasks in this application.
1832
+ def clear
1833
+ @tasks.clear
1834
+ @rules.clear
1835
+ end
1836
+
1837
+ # Lookup a task, using scope and the scope hints in the task name.
1838
+ # This method performs straight lookups without trying to
1839
+ # synthesize file tasks or rules. Special scope names (e.g. '^')
1840
+ # are recognized. If no scope argument is supplied, use the
1841
+ # current scope. Return nil if the task cannot be found.
1842
+ def lookup(task_name, initial_scope=nil)
1843
+ initial_scope ||= @scope
1844
+ task_name = task_name.to_s
1845
+ if task_name =~ /^rake:/
1846
+ scopes = []
1847
+ task_name = task_name.sub(/^rake:/, '')
1848
+ elsif task_name =~ /^(\^+)/
1849
+ scopes = initial_scope[0, initial_scope.size - $1.size]
1850
+ task_name = task_name.sub(/^(\^+)/, '')
979
1851
  else
980
- tasks.each { |task_name| Task[task_name].invoke }
1852
+ scopes = initial_scope
1853
+ end
1854
+ lookup_in_scope(task_name, scopes)
1855
+ end
1856
+
1857
+ # Lookup the task name
1858
+ def lookup_in_scope(name, scope)
1859
+ n = scope.size
1860
+ while n >= 0
1861
+ tn = (scope[0,n] + [name]).join(':')
1862
+ task = @tasks[tn]
1863
+ return task if task
1864
+ n -= 1
1865
+ end
1866
+ nil
1867
+ end
1868
+ private :lookup_in_scope
1869
+
1870
+ # Return the list of scope names currently active in the task
1871
+ # manager.
1872
+ def current_scope
1873
+ @scope.dup
1874
+ end
1875
+
1876
+ # Evaluate the block in a nested namespace named +name+. Create
1877
+ # an anonymous namespace if +name+ is nil.
1878
+ def in_namespace(name)
1879
+ name ||= generate_name
1880
+ @scope.push(name)
1881
+ ns = NameSpace.new(self, @scope)
1882
+ yield(ns)
1883
+ ns
1884
+ ensure
1885
+ @scope.pop
1886
+ end
1887
+
1888
+ private
1889
+
1890
+ # Generate an anonymous namespace name.
1891
+ def generate_name
1892
+ @seed ||= 0
1893
+ @seed += 1
1894
+ "_anon_#{@seed}"
1895
+ end
1896
+
1897
+ def trace_rule(level, message)
1898
+ puts "#{" "*level}#{message}" if Rake.application.options.trace_rules
1899
+ end
1900
+
1901
+ # Attempt to create a rule given the list of prerequisites.
1902
+ def attempt_rule(task_name, extensions, block, level)
1903
+ sources = make_sources(task_name, extensions)
1904
+ prereqs = sources.collect { |source|
1905
+ trace_rule level, "Attempting Rule #{task_name} => #{source}"
1906
+ if File.exist?(source) || Rake::Task.task_defined?(source)
1907
+ trace_rule level, "(#{task_name} => #{source} ... EXIST)"
1908
+ source
1909
+ elsif parent = enhance_with_matching_rule(source, level+1)
1910
+ trace_rule level, "(#{task_name} => #{source} ... ENHANCE)"
1911
+ parent.name
1912
+ else
1913
+ trace_rule level, "(#{task_name} => #{source} ... FAIL)"
1914
+ return nil
1915
+ end
1916
+ }
1917
+ task = FileTask.define_task({task_name => prereqs}, &block)
1918
+ task.sources = prereqs
1919
+ task
1920
+ end
1921
+
1922
+ # Make a list of sources from the list of file name extensions /
1923
+ # translation procs.
1924
+ def make_sources(task_name, extensions)
1925
+ extensions.collect { |ext|
1926
+ case ext
1927
+ when /%/
1928
+ task_name.pathmap(ext)
1929
+ when %r{/}
1930
+ ext
1931
+ when /^\./
1932
+ task_name.ext(ext)
1933
+ when String
1934
+ ext
1935
+ when Proc
1936
+ if ext.arity == 1
1937
+ ext.call(task_name)
1938
+ else
1939
+ ext.call
1940
+ end
1941
+ else
1942
+ fail "Don't know how to handle rule dependent: #{ext.inspect}"
1943
+ end
1944
+ }.flatten
1945
+ end
1946
+
1947
+ end # TaskManager
1948
+
1949
+ ######################################################################
1950
+ # Rake main application object. When invoking +rake+ from the
1951
+ # command line, a Rake::Application object is created and run.
1952
+ #
1953
+ class Application
1954
+ include TaskManager
1955
+
1956
+ # The name of the application (typically 'rake')
1957
+ attr_reader :name
1958
+
1959
+ # The original directory where rake was invoked.
1960
+ attr_reader :original_dir
1961
+
1962
+ # Name of the actual rakefile used.
1963
+ attr_reader :rakefile
1964
+
1965
+ # List of the top level task names (task names from the command line).
1966
+ attr_reader :top_level_tasks
1967
+
1968
+ DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze
1969
+
1970
+ # Initialize a Rake::Application object.
1971
+ def initialize
1972
+ super
1973
+ @name = 'rake'
1974
+ @rakefiles = DEFAULT_RAKEFILES.dup
1975
+ @rakefile = nil
1976
+ @pending_imports = []
1977
+ @imported = []
1978
+ @loaders = {}
1979
+ @default_loader = Rake::DefaultLoader.new
1980
+ @original_dir = Dir.pwd
1981
+ @top_level_tasks = []
1982
+ add_loader('rb', DefaultLoader.new)
1983
+ add_loader('rf', DefaultLoader.new)
1984
+ add_loader('rake', DefaultLoader.new)
1985
+ @tty_output = STDOUT.tty?
1986
+ end
1987
+
1988
+ # Run the Rake application. The run method performs the following three steps:
1989
+ #
1990
+ # * Initialize the command line options (+init+).
1991
+ # * Define the tasks (+load_rakefile+).
1992
+ # * Run the top level tasks (+run_tasks+).
1993
+ #
1994
+ # If you wish to build a custom rake command, you should call +init+ on your
1995
+ # application. The define any tasks. Finally, call +top_level+ to run your top
1996
+ # level tasks.
1997
+ def run
1998
+ standard_exception_handling do
1999
+ init
2000
+ load_rakefile
2001
+ top_level
2002
+ end
2003
+ end
2004
+
2005
+ # Initialize the command line parameters and app name.
2006
+ def init(app_name='rake')
2007
+ standard_exception_handling do
2008
+ @name = app_name
2009
+ handle_options
2010
+ collect_tasks
2011
+ end
2012
+ end
2013
+
2014
+ # Find the rakefile and then load it and any pending imports.
2015
+ def load_rakefile
2016
+ standard_exception_handling do
2017
+ raw_load_rakefile
981
2018
  end
982
- rescue Exception => ex
983
- puts "rake aborted!"
984
- puts ex.message
985
- if $trace
986
- puts ex.backtrace.join("\n")
2019
+ end
2020
+
2021
+ # Run the top level tasks of a Rake application.
2022
+ def top_level
2023
+ standard_exception_handling do
2024
+ if options.show_tasks
2025
+ display_tasks_and_comments
2026
+ elsif options.show_prereqs
2027
+ display_prerequisites
2028
+ else
2029
+ top_level_tasks.each { |task_name| invoke_task(task_name) }
2030
+ end
2031
+ end
2032
+ end
2033
+
2034
+ # Add a loader to handle imported files ending in the extension
2035
+ # +ext+.
2036
+ def add_loader(ext, loader)
2037
+ ext = ".#{ext}" unless ext =~ /^\./
2038
+ @loaders[ext] = loader
2039
+ end
2040
+
2041
+ # Application options from the command line
2042
+ def options
2043
+ @options ||= OpenStruct.new
2044
+ end
2045
+
2046
+ # private ----------------------------------------------------------------
2047
+
2048
+ def invoke_task(task_string)
2049
+ name, args = parse_task_string(task_string)
2050
+ t = self[name]
2051
+ t.invoke(*args)
2052
+ end
2053
+
2054
+ def parse_task_string(string)
2055
+ if string =~ /^([^\[]+)(\[(.*)\])$/
2056
+ name = $1
2057
+ args = $3.split(/\s*,\s*/)
987
2058
  else
988
- puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
2059
+ name = string
2060
+ args = []
2061
+ end
2062
+ [name, args]
2063
+ end
2064
+
2065
+ # Provide standard execption handling for the given block.
2066
+ def standard_exception_handling
2067
+ begin
2068
+ yield
2069
+ rescue SystemExit => ex
2070
+ # Exit silently with current status
2071
+ raise
2072
+ rescue OptionParser::InvalidOption => ex
2073
+ # Exit silently
2074
+ exit(false)
2075
+ rescue Exception => ex
2076
+ # Exit with error message
2077
+ $stderr.puts "#{name} aborted!"
2078
+ $stderr.puts ex.message
2079
+ if options.trace
2080
+ $stderr.puts ex.backtrace.join("\n")
2081
+ else
2082
+ $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
2083
+ $stderr.puts "(See full trace by running task with --trace)"
2084
+ end
2085
+ exit(false)
2086
+ end
2087
+ end
2088
+
2089
+ # True if one of the files in RAKEFILES is in the current directory.
2090
+ # If a match is found, it is copied into @rakefile.
2091
+ def have_rakefile
2092
+ @rakefiles.each do |fn|
2093
+ if File.exist?(fn)
2094
+ others = Dir.glob(fn, File::FNM_CASEFOLD)
2095
+ return others.size == 1 ? others.first : fn
2096
+ elsif fn == ''
2097
+ return fn
2098
+ end
989
2099
  end
990
- exit(1)
991
- end
2100
+ return nil
2101
+ end
2102
+
2103
+ # True if we are outputting to TTY, false otherwise
2104
+ def tty_output?
2105
+ @tty_output
2106
+ end
2107
+
2108
+ # Override the detected TTY output state (mostly for testing)
2109
+ def tty_output=( tty_output_state )
2110
+ @tty_output = tty_output_state
2111
+ end
2112
+
2113
+ # We will truncate output if we are outputting to a TTY or if we've been
2114
+ # given an explicit column width to honor
2115
+ def truncate_output?
2116
+ tty_output? || ENV['RAKE_COLUMNS']
2117
+ end
2118
+
2119
+ # Display the tasks and comments.
2120
+ def display_tasks_and_comments
2121
+ displayable_tasks = tasks.select { |t|
2122
+ t.comment && t.name =~ options.show_task_pattern
2123
+ }
2124
+ if options.full_description
2125
+ displayable_tasks.each do |t|
2126
+ puts "#{name} #{t.name_with_args}"
2127
+ t.full_comment.split("\n").each do |line|
2128
+ puts " #{line}"
2129
+ end
2130
+ puts
2131
+ end
2132
+ else
2133
+ width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10
2134
+ max_column = truncate_output? ? terminal_width - name.size - width - 7 : nil
2135
+ displayable_tasks.each do |t|
2136
+ printf "#{name} %-#{width}s # %s\n",
2137
+ t.name_with_args, max_column ? truncate(t.comment, max_column) : t.comment
2138
+ end
2139
+ end
2140
+ end
2141
+
2142
+ def terminal_width
2143
+ if ENV['RAKE_COLUMNS']
2144
+ result = ENV['RAKE_COLUMNS'].to_i
2145
+ else
2146
+ result = unix? ? dynamic_width : 80
2147
+ end
2148
+ (result < 10) ? 80 : result
2149
+ rescue
2150
+ 80
2151
+ end
2152
+
2153
+ # Calculate the dynamic width of the
2154
+ def dynamic_width
2155
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
2156
+ end
2157
+
2158
+ def dynamic_width_stty
2159
+ %x{stty size 2>/dev/null}.split[1].to_i
2160
+ end
2161
+
2162
+ def dynamic_width_tput
2163
+ %x{tput cols 2>/dev/null}.to_i
2164
+ end
2165
+
2166
+ def unix?
2167
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
2168
+ end
2169
+
2170
+ def windows?
2171
+ Win32.windows?
2172
+ end
2173
+
2174
+ def truncate(string, width)
2175
+ if string.length <= width
2176
+ string
2177
+ else
2178
+ ( string[0, width-3] || "" ) + "..."
2179
+ end
2180
+ end
2181
+
2182
+ # Display the tasks and prerequisites
2183
+ def display_prerequisites
2184
+ tasks.each do |t|
2185
+ puts "#{name} #{t.name}"
2186
+ t.prerequisites.each { |pre| puts " #{pre}" }
2187
+ end
2188
+ end
2189
+
2190
+ # A list of all the standard options used in rake, suitable for
2191
+ # passing to OptionParser.
2192
+ def standard_rake_options
2193
+ [
2194
+ ['--classic-namespace', '-C', "Put Task and FileTask in the top level namespace",
2195
+ lambda { |value|
2196
+ require 'rake/classic_namespace'
2197
+ options.classic_namespace = true
2198
+ }
2199
+ ],
2200
+ ['--describe', '-D [PATTERN]', "Describe the tasks (matching optional PATTERN), then exit.",
2201
+ lambda { |value|
2202
+ options.show_tasks = true
2203
+ options.full_description = true
2204
+ options.show_task_pattern = Regexp.new(value || '')
2205
+ }
2206
+ ],
2207
+ ['--dry-run', '-n', "Do a dry run without executing actions.",
2208
+ lambda { |value|
2209
+ verbose(true)
2210
+ nowrite(true)
2211
+ options.dryrun = true
2212
+ options.trace = true
2213
+ }
2214
+ ],
2215
+ ['--execute', '-e CODE', "Execute some Ruby code and exit.",
2216
+ lambda { |value|
2217
+ eval(value)
2218
+ exit
2219
+ }
2220
+ ],
2221
+ ['--execute-print', '-p CODE', "Execute some Ruby code, print the result, then exit.",
2222
+ lambda { |value|
2223
+ puts eval(value)
2224
+ exit
2225
+ }
2226
+ ],
2227
+ ['--execute-continue', '-E CODE',
2228
+ "Execute some Ruby code, then continue with normal task processing.",
2229
+ lambda { |value| eval(value) }
2230
+ ],
2231
+ ['--libdir', '-I LIBDIR', "Include LIBDIR in the search path for required modules.",
2232
+ lambda { |value| $:.push(value) }
2233
+ ],
2234
+ ['--prereqs', '-P', "Display the tasks and dependencies, then exit.",
2235
+ lambda { |value| options.show_prereqs = true }
2236
+ ],
2237
+ ['--quiet', '-q', "Do not log messages to standard output.",
2238
+ lambda { |value| verbose(false) }
2239
+ ],
2240
+ ['--rakefile', '-f [FILE]', "Use FILE as the rakefile.",
2241
+ lambda { |value|
2242
+ value ||= ''
2243
+ @rakefiles.clear
2244
+ @rakefiles << value
2245
+ }
2246
+ ],
2247
+ ['--rakelibdir', '--rakelib', '-R RAKELIBDIR',
2248
+ "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')",
2249
+ lambda { |value| options.rakelib = value.split(':') }
2250
+ ],
2251
+ ['--require', '-r MODULE', "Require MODULE before executing rakefile.",
2252
+ lambda { |value|
2253
+ begin
2254
+ require value
2255
+ rescue LoadError => ex
2256
+ begin
2257
+ rake_require value
2258
+ rescue LoadError => ex2
2259
+ raise ex
2260
+ end
2261
+ end
2262
+ }
2263
+ ],
2264
+ ['--rules', "Trace the rules resolution.",
2265
+ lambda { |value| options.trace_rules = true }
2266
+ ],
2267
+ ['--no-search', '--nosearch', '-N', "Do not search parent directories for the Rakefile.",
2268
+ lambda { |value| options.nosearch = true }
2269
+ ],
2270
+ ['--silent', '-s', "Like --quiet, but also suppresses the 'in directory' announcement.",
2271
+ lambda { |value|
2272
+ verbose(false)
2273
+ options.silent = true
2274
+ }
2275
+ ],
2276
+ ['--system', '-g',
2277
+ "Using system wide (global) rakefiles (usually '~/.rake/*.rake').",
2278
+ lambda { |value| options.load_system = true }
2279
+ ],
2280
+ ['--no-system', '--nosystem', '-G',
2281
+ "Use standard project Rakefile search paths, ignore system wide rakefiles.",
2282
+ lambda { |value| options.ignore_system = true }
2283
+ ],
2284
+ ['--tasks', '-T [PATTERN]', "Display the tasks (matching optional PATTERN) with descriptions, then exit.",
2285
+ lambda { |value|
2286
+ options.show_tasks = true
2287
+ options.show_task_pattern = Regexp.new(value || '')
2288
+ options.full_description = false
2289
+ }
2290
+ ],
2291
+ ['--trace', '-t', "Turn on invoke/execute tracing, enable full backtrace.",
2292
+ lambda { |value|
2293
+ options.trace = true
2294
+ verbose(true)
2295
+ }
2296
+ ],
2297
+ ['--verbose', '-v', "Log message to standard output.",
2298
+ lambda { |value| verbose(true) }
2299
+ ],
2300
+ ['--version', '-V', "Display the program version.",
2301
+ lambda { |value|
2302
+ puts "rake, version #{RAKEVERSION}"
2303
+ exit
2304
+ }
2305
+ ]
2306
+ ]
2307
+ end
2308
+
2309
+ # Read and handle the command line options.
2310
+ def handle_options
2311
+ options.rakelib = ['rakelib']
2312
+
2313
+ OptionParser.new do |opts|
2314
+ opts.banner = "rake [-f rakefile] {options} targets..."
2315
+ opts.separator ""
2316
+ opts.separator "Options are ..."
2317
+
2318
+ opts.on_tail("-h", "--help", "-H", "Display this help message.") do
2319
+ puts opts
2320
+ exit
2321
+ end
2322
+
2323
+ standard_rake_options.each { |args| opts.on(*args) }
2324
+ end.parse!
2325
+
2326
+ # If class namespaces are requested, set the global options
2327
+ # according to the values in the options structure.
2328
+ if options.classic_namespace
2329
+ $show_tasks = options.show_tasks
2330
+ $show_prereqs = options.show_prereqs
2331
+ $trace = options.trace
2332
+ $dryrun = options.dryrun
2333
+ $silent = options.silent
2334
+ end
2335
+ end
2336
+
2337
+ # Similar to the regular Ruby +require+ command, but will check
2338
+ # for *.rake files in addition to *.rb files.
2339
+ def rake_require(file_name, paths=$LOAD_PATH, loaded=$")
2340
+ return false if loaded.include?(file_name)
2341
+ paths.each do |path|
2342
+ fn = file_name + ".rake"
2343
+ full_path = File.join(path, fn)
2344
+ if File.exist?(full_path)
2345
+ load full_path
2346
+ loaded << fn
2347
+ return true
2348
+ end
2349
+ end
2350
+ fail LoadError, "Can't find #{file_name}"
2351
+ end
2352
+
2353
+ def find_rakefile_location
2354
+ here = Dir.pwd
2355
+ while ! (fn = have_rakefile)
2356
+ Dir.chdir("..")
2357
+ if Dir.pwd == here || options.nosearch
2358
+ return nil
2359
+ end
2360
+ here = Dir.pwd
2361
+ end
2362
+ [fn, here]
2363
+ ensure
2364
+ Dir.chdir(Rake.original_dir)
2365
+ end
2366
+
2367
+ def raw_load_rakefile # :nodoc:
2368
+ rakefile, location = find_rakefile_location
2369
+ if (! options.ignore_system) &&
2370
+ (options.load_system || rakefile.nil?) &&
2371
+ system_dir && File.directory?(system_dir)
2372
+ puts "(in #{Dir.pwd})" unless options.silent
2373
+ glob("#{system_dir}/*.rake") do |name|
2374
+ add_import name
2375
+ end
2376
+ else
2377
+ fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if
2378
+ rakefile.nil?
2379
+ @rakefile = rakefile
2380
+ Dir.chdir(location)
2381
+ puts "(in #{Dir.pwd})" unless options.silent
2382
+ $rakefile = @rakefile if options.classic_namespace
2383
+ load File.expand_path(@rakefile) if @rakefile && @rakefile != ''
2384
+ options.rakelib.each do |rlib|
2385
+ glob("#{rlib}/*.rake") do |name|
2386
+ add_import name
2387
+ end
2388
+ end
2389
+ end
2390
+ load_imports
2391
+ end
2392
+
2393
+ def glob(path, &block)
2394
+ Dir[path.gsub("\\", '/')].each(&block)
2395
+ end
2396
+ private :glob
2397
+
2398
+ # The directory path containing the system wide rakefiles.
2399
+ def system_dir
2400
+ @system_dir ||=
2401
+ begin
2402
+ if ENV['RAKE_SYSTEM']
2403
+ ENV['RAKE_SYSTEM']
2404
+ else
2405
+ standard_system_dir
2406
+ end
2407
+ end
2408
+ end
2409
+
2410
+ # The standard directory containing system wide rake files.
2411
+ if Win32.windows?
2412
+ def standard_system_dir #:nodoc:
2413
+ Win32.win32_system_dir
2414
+ end
2415
+ else
2416
+ def standard_system_dir #:nodoc:
2417
+ File.join(File.expand_path('~'), '.rake')
2418
+ end
2419
+ end
2420
+ private :standard_system_dir
2421
+
2422
+ # Collect the list of tasks on the command line. If no tasks are
2423
+ # given, return a list containing only the default task.
2424
+ # Environmental assignments are processed at this time as well.
2425
+ def collect_tasks
2426
+ @top_level_tasks = []
2427
+ ARGV.each do |arg|
2428
+ if arg =~ /^(\w+)=(.*)$/
2429
+ ENV[$1] = $2
2430
+ else
2431
+ @top_level_tasks << arg unless arg =~ /^-/
2432
+ end
2433
+ end
2434
+ @top_level_tasks.push("default") if @top_level_tasks.size == 0
2435
+ end
2436
+
2437
+ # Add a file to the list of files to be imported.
2438
+ def add_import(fn)
2439
+ @pending_imports << fn
2440
+ end
2441
+
2442
+ # Load the pending list of imported files.
2443
+ def load_imports
2444
+ while fn = @pending_imports.shift
2445
+ next if @imported.member?(fn)
2446
+ if fn_task = lookup(fn)
2447
+ fn_task.invoke
2448
+ end
2449
+ ext = File.extname(fn)
2450
+ loader = @loaders[ext] || @default_loader
2451
+ loader.load(fn)
2452
+ @imported << fn
2453
+ end
2454
+ end
2455
+
2456
+ # Warn about deprecated use of top level constant names.
2457
+ def const_warning(const_name)
2458
+ @const_warning ||= false
2459
+ if ! @const_warning
2460
+ $stderr.puts %{WARNING: Deprecated reference to top-level constant '#{const_name}' } +
2461
+ %{found at: #{rakefile_location}} # '
2462
+ $stderr.puts %{ Use --classic-namespace on rake command}
2463
+ $stderr.puts %{ or 'require "rake/classic_namespace"' in Rakefile}
2464
+ end
2465
+ @const_warning = true
2466
+ end
2467
+
2468
+ def rakefile_location
2469
+ begin
2470
+ fail
2471
+ rescue RuntimeError => ex
2472
+ ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
2473
+ end
2474
+ end
992
2475
  end
993
2476
  end
994
2477
 
995
- if __FILE__ == $0 then
996
- RakeApp.new.run
2478
+
2479
+ class Module
2480
+ # Rename the original handler to make it available.
2481
+ alias :rake_original_const_missing :const_missing
2482
+
2483
+ # Check for deprecated uses of top level (i.e. in Object) uses of
2484
+ # Rake class names. If someone tries to reference the constant
2485
+ # name, display a warning and return the proper object. Using the
2486
+ # --classic-namespace command line option will define these
2487
+ # constants in Object and avoid this handler.
2488
+ def const_missing(const_name)
2489
+ case const_name
2490
+ when :Task
2491
+ Rake.application.const_warning(const_name)
2492
+ Rake::Task
2493
+ when :FileTask
2494
+ Rake.application.const_warning(const_name)
2495
+ Rake::FileTask
2496
+ when :FileCreationTask
2497
+ Rake.application.const_warning(const_name)
2498
+ Rake::FileCreationTask
2499
+ when :RakeApp
2500
+ Rake.application.const_warning(const_name)
2501
+ Rake::Application
2502
+ else
2503
+ rake_original_const_missing(const_name)
2504
+ end
2505
+ end
997
2506
  end