rake 0.4.8 → 0.7.3

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 (77) hide show
  1. data/CHANGES +183 -0
  2. data/README +47 -14
  3. data/Rakefile +212 -44
  4. data/bin/rake +2 -3
  5. data/doc/jamis.rb +591 -0
  6. data/doc/rake.1.gz +0 -0
  7. data/doc/rakefile.rdoc +180 -3
  8. data/doc/release_notes/rake-0.4.14.rdoc +23 -0
  9. data/doc/release_notes/rake-0.4.15.rdoc +35 -0
  10. data/doc/release_notes/rake-0.5.0.rdoc +53 -0
  11. data/doc/release_notes/rake-0.5.3.rdoc +78 -0
  12. data/doc/release_notes/rake-0.5.4.rdoc +46 -0
  13. data/doc/release_notes/rake-0.6.0.rdoc +141 -0
  14. data/doc/release_notes/rake-0.7.0.rdoc +119 -0
  15. data/doc/release_notes/rake-0.7.1.rdoc +59 -0
  16. data/doc/release_notes/rake-0.7.2.rdoc +121 -0
  17. data/doc/release_notes/rake-0.7.3.rdoc +47 -0
  18. data/lib/rake/classic_namespace.rb +8 -0
  19. data/lib/rake/clean.rb +3 -1
  20. data/lib/rake/contrib/ftptools.rb +21 -21
  21. data/lib/rake/contrib/rubyforgepublisher.rb +3 -3
  22. data/lib/rake/contrib/sshpublisher.rb +1 -1
  23. data/lib/rake/contrib/sys.rb +20 -21
  24. data/lib/rake/gempackagetask.rb +12 -9
  25. data/lib/rake/loaders/makefile.rb +40 -0
  26. data/lib/rake/packagetask.rb +65 -33
  27. data/lib/rake/rake_test_loader.rb +5 -0
  28. data/lib/rake/rdoctask.rb +34 -15
  29. data/lib/rake/ruby182_test_unit_fix.rb +23 -0
  30. data/lib/rake/runtest.rb +4 -4
  31. data/lib/rake/tasklib.rb +3 -9
  32. data/lib/rake/testtask.rb +67 -24
  33. data/lib/rake.rb +1600 -552
  34. data/test/capture_stdout.rb +26 -0
  35. data/test/data/chains/Rakefile +15 -0
  36. data/test/data/default/Rakefile +19 -0
  37. data/test/data/dryrun/Rakefile +22 -0
  38. data/test/data/file_creation_task/Rakefile +30 -0
  39. data/test/data/imports/Rakefile +19 -0
  40. data/test/data/imports/deps.mf +1 -0
  41. data/test/data/multidesc/Rakefile +14 -0
  42. data/test/data/namespace/Rakefile +57 -0
  43. data/test/data/rakelib/test1.rb +3 -0
  44. data/test/data/sample.mf +9 -0
  45. data/test/data/unittest/Rakefile +1 -0
  46. data/test/filecreation.rb +18 -10
  47. data/test/functional.rb +3 -72
  48. data/test/reqfile.rb +3 -0
  49. data/test/reqfile2.rb +3 -0
  50. data/test/session_functional.rb +218 -0
  51. data/test/shellcommand.rb +3 -0
  52. data/test/test_application.rb +425 -0
  53. data/test/{testclean.rb → test_clean.rb} +1 -0
  54. data/test/test_definitions.rb +82 -0
  55. data/test/test_earlytime.rb +35 -0
  56. data/test/test_file_creation_task.rb +62 -0
  57. data/test/test_file_task.rb +139 -0
  58. data/test/test_filelist.rb +574 -0
  59. data/test/test_fileutils.rb +230 -0
  60. data/test/test_makefile_loader.rb +23 -0
  61. data/test/test_multitask.rb +45 -0
  62. data/test/test_namespace.rb +32 -0
  63. data/test/test_package_task.rb +130 -0
  64. data/test/test_pathmap.rb +188 -0
  65. data/test/test_rake.rb +34 -0
  66. data/test/test_require.rb +33 -0
  67. data/test/test_rules.rb +305 -0
  68. data/test/test_task_manager.rb +148 -0
  69. data/test/test_tasks.rb +146 -0
  70. data/test/{testtesttask.rb → test_test_task.rb} +5 -0
  71. data/test/test_top_level_functions.rb +79 -0
  72. metadata +134 -68
  73. data/test/testfilelist.rb +0 -255
  74. data/test/testfileutils.rb +0 -55
  75. data/test/testpackagetask.rb +0 -81
  76. data/test/testtasks.rb +0 -371
  77. /data/test/{testftp.rb → test_ftp.rb} +0 -0
data/lib/rake.rb CHANGED
@@ -1,284 +1,570 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  #--
4
- # Copyright (c) 2003, 2004 Jim Weirich
4
+
5
+ # Copyright (c) 2003, 2004, 2005, 2006 Jim Weirich
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.8'
32
+ RAKEVERSION = '0.7.3'
33
33
 
34
34
  require 'rbconfig'
35
35
  require 'ftools'
36
36
  require 'getoptlong'
37
37
  require 'fileutils'
38
-
39
- $last_comment = nil
40
- $show_tasks = nil
41
- $show_prereqs = nil
42
- $trace = nil
43
- $dryrun = nil
38
+ require 'singleton'
39
+ require 'thread'
40
+ require 'ostruct'
44
41
 
45
42
  ######################################################################
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.
43
+ # Rake extensions to Module.
51
44
  #
52
- # Tasks are not usually created directly using the new method, but
53
- # rather use the +file+ and +task+ convenience methods.
54
- #
55
- class Task
56
- TASKS = Hash.new
57
- RULES = Array.new
58
-
59
- # List of prerequisites for a task.
60
- attr_reader :prerequisites
61
-
62
- # Comment for this task.
63
- attr_accessor :comment
64
-
65
- # Source dependency for rule synthesized tasks. Nil if task was not
66
- # sythesized from a rule.
67
- attr_accessor :source
68
-
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
77
- end
45
+ class Module
78
46
 
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
47
+ # Check for an existing method in the current class before extending. IF
48
+ # the method already exists, then a warning is printed and the extension is
49
+ # not added. Otherwise the block is yielded and any definitions in the
50
+ # block will take effect.
51
+ #
52
+ # Usage:
53
+ #
54
+ # class String
55
+ # rake_extension("xyz") do
56
+ # def xyz
57
+ # ...
58
+ # end
59
+ # end
60
+ # end
61
+ #
62
+ def rake_extension(method)
63
+ if instance_methods.include?(method)
64
+ $stderr.puts "WARNING: Possible conflict with Rake extension: #{self}##{method} already exists"
65
+ else
66
+ yield
67
+ end
84
68
  end
69
+ end
85
70
 
86
- # Name of the task.
87
- def name
88
- @name.to_s
89
- end
90
71
 
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}"
72
+ ######################################################################
73
+ # User defined methods to be added to String.
74
+ #
75
+ class String
76
+ rake_extension("ext") do
77
+ # Replace the file extension with +newext+. If there is no extenson on
78
+ # the string, append the new extension to the end. If the new extension
79
+ # is not given, or is the empty string, remove any existing extension.
80
+ #
81
+ # +ext+ is a user added method for the String class.
82
+ def ext(newext='')
83
+ return self.dup if ['.', '..'].include? self
84
+ if newext != ''
85
+ newext = (newext =~ /^\./) ? newext : ("." + newext)
86
+ end
87
+ dup.sub!(%r(([^/\\])\.[^./\\]*$)) { $1 + newext } || self + newext
95
88
  end
96
- return if @already_invoked
97
- @already_invoked = true
98
- @prerequisites.each { |n| Task[n].invoke }
99
- execute if needed?
100
89
  end
101
90
 
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(", ") + ")"
108
- end
109
- private :format_trace_flags
91
+ rake_extension("pathmap") do
92
+ # Explode a path into individual components. Used by +pathmap+.
93
+ def pathmap_explode
94
+ head, tail = File.split(self)
95
+ return [self] if head == self
96
+ return [tail] if head == '.' || tail == '/'
97
+ return [head, tail] if head == '/'
98
+ return head.pathmap_explode + [tail]
99
+ end
100
+ protected :pathmap_explode
101
+
102
+ # Extract a partial path from the path. Include +n+ directories from the
103
+ # front end (left hand side) if +n+ is positive. Include |+n+|
104
+ # directories from the back end (right hand side) if +n+ is negative.
105
+ def pathmap_partial(n)
106
+ target = File.dirname(self)
107
+ dirs = target.pathmap_explode
108
+ if n > 0
109
+ File.join(dirs[0...n])
110
+ elsif n < 0
111
+ partial = dirs[n..-1]
112
+ if partial.nil? || partial.empty?
113
+ target
114
+ else
115
+ File.join(partial)
116
+ end
117
+ else
118
+ "."
119
+ end
120
+ end
121
+ protected :pathmap_partial
122
+
123
+ # Preform the pathmap replacement operations on the given path. The
124
+ # patterns take the form 'pat1,rep1;pat2,rep2...'.
125
+ def pathmap_replace(patterns, &block)
126
+ result = self
127
+ patterns.split(';').each do |pair|
128
+ pattern, replacement = pair.split(',')
129
+ pattern = Regexp.new(pattern)
130
+ if replacement == '*' && block_given?
131
+ result = result.sub(pattern, &block)
132
+ elsif replacement
133
+ result = result.sub(pattern, replacement)
134
+ else
135
+ result = result.sub(pattern, '')
136
+ end
137
+ end
138
+ result
139
+ end
140
+ protected :pathmap_replace
110
141
 
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) }
142
+ # Map the path according to the given specification. The specification
143
+ # controls the details of the mapping. The following special patterns are
144
+ # recognized:
145
+ #
146
+ # * <b>%p</b> -- The complete path.
147
+ # * <b>%f</b> -- The base file name of the path, with its file extension,
148
+ # but without any directories.
149
+ # * <b>%n</b> -- The file name of the path without its file extension.
150
+ # * <b>%d</b> -- The directory list of the path.
151
+ # * <b>%x</b> -- The file extension of the path. An empty string if there
152
+ # is no extension.
153
+ # * <b>%X</b> -- Everything *but* the file extension.
154
+ # * <b>%s</b> -- The alternate file separater if defined, otherwise use
155
+ # the standard file separator.
156
+ # * <b>%%</b> -- A percent sign.
157
+ #
158
+ # The %d specifier can also have a numeric prefix (e.g. '%2d'). If the
159
+ # number is positive, only return (up to) +n+ directories in the path,
160
+ # starting from the left hand side. If +n+ is negative, return (up to)
161
+ # |+n+| directories from the right hand side of the path.
162
+ #
163
+ # Examples:
164
+ #
165
+ # 'a/b/c/d/file.txt'.pathmap("%2d") => 'a/b'
166
+ # 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d'
167
+ #
168
+ # Also the %d, %p, $f, $n, %x, and %X operators can take a
169
+ # pattern/replacement argument to perform simple string substititions on a
170
+ # particular part of the path. The pattern and replacement are speparated
171
+ # by a comma and are enclosed by curly braces. The replacement spec comes
172
+ # after the % character but before the operator letter. (e.g.
173
+ # "%{old,new}d"). Muliple replacement specs should be separated by
174
+ # semi-colons (e.g. "%{old,new;src,bin}d").
175
+ #
176
+ # Regular expressions may be used for the pattern, and back refs may be
177
+ # used in the replacement text. Curly braces, commas and semi-colons are
178
+ # excluded from both the pattern and replacement text (let's keep parsing
179
+ # reasonable).
180
+ #
181
+ # For example:
182
+ #
183
+ # "src/org/onestepback/proj/A.java".pathmap("%{^src,bin}X.class")
184
+ #
185
+ # returns:
186
+ #
187
+ # "bin/org/onestepback/proj/A.class"
188
+ #
189
+ # If the replacement text is '*', then a block may be provided to perform
190
+ # some arbitrary calculation for the replacement.
191
+ #
192
+ # For example:
193
+ #
194
+ # "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext|
195
+ # ext.downcase
196
+ # }
197
+ #
198
+ # Returns:
199
+ #
200
+ # "/path/to/file.txt"
201
+ #
202
+ def pathmap(spec=nil, &block)
203
+ return self if spec.nil?
204
+ result = ''
205
+ spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag|
206
+ case frag
207
+ when '%f'
208
+ result << File.basename(self)
209
+ when '%n'
210
+ result << File.basename(self).ext
211
+ when '%d'
212
+ result << File.dirname(self)
213
+ when '%x'
214
+ result << $1 if self =~ /[^\/](\.[^.]+)$/
215
+ when '%X'
216
+ if self =~ /^(.+[^\/])(\.[^.]+)$/
217
+ result << $1
218
+ else
219
+ result << self
220
+ end
221
+ when '%p'
222
+ result << self
223
+ when '%s'
224
+ result << (File::ALT_SEPARATOR || File::SEPARATOR)
225
+ when '%-'
226
+ # do nothing
227
+ when '%%'
228
+ result << "%"
229
+ when /%(-?\d+)d/
230
+ result << pathmap_partial($1.to_i)
231
+ when /^%\{([^}]*)\}(\d*[dpfnxX])/
232
+ patterns, operator = $1, $2
233
+ result << pathmap('%' + operator).pathmap_replace(patterns, &block)
234
+ when /^%/
235
+ fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'"
236
+ else
237
+ result << frag
238
+ end
239
+ end
240
+ result
241
+ end
116
242
  end
243
+ end
117
244
 
118
- # Is this task needed?
119
- def needed?
120
- true
121
- end
245
+ ##############################################################################
246
+ module Rake
247
+
248
+ # --------------------------------------------------------------------------
249
+ # Rake module singleton methods.
250
+ #
251
+ class << self
252
+ # Current Rake Application
253
+ def application
254
+ @application ||= Rake::Application.new
255
+ end
256
+
257
+ # Set the current Rake application object.
258
+ def application=(app)
259
+ @application = app
260
+ end
261
+
262
+ # Return the original directory where the Rake application was started.
263
+ def original_dir
264
+ application.original_dir
265
+ end
122
266
 
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
127
267
  end
128
268
 
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 = ''
269
+ # ##########################################################################
270
+ # Mixin for creating easily cloned objects.
271
+ #
272
+ module Cloneable
273
+ # Clone an object by making a new object and setting all the instance
274
+ # variables to the same values.
275
+ def clone
276
+ sibling = self.class.new
277
+ instance_variables.each do |ivar|
278
+ value = self.instance_variable_get(ivar)
279
+ new_value = value.clone rescue value
280
+ sibling.instance_variable_set(ivar, new_value)
281
+ end
282
+ sibling
137
283
  end
138
- @comment << $last_comment
139
- $last_comment = nil
284
+ alias dup clone
140
285
  end
286
+ end
141
287
 
142
- # Class Methods ----------------------------------------------------
288
+ module Rake
143
289
 
144
- class << self
290
+ # #########################################################################
291
+ # A Task is the basic unit of work in a Rakefile. Tasks have associated
292
+ # actions (possibly more than one) and a list of prerequisites. When
293
+ # invoked, a task will first ensure that all of its prerequisites have an
294
+ # opportunity to run and then it will execute its own actions.
295
+ #
296
+ # Tasks are not usually created directly using the new method, but rather
297
+ # use the +file+ and +task+ convenience methods.
298
+ #
299
+ class Task
300
+ # List of prerequisites for a task.
301
+ attr_reader :prerequisites
145
302
 
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.)
149
- def clear
150
- TASKS.clear
151
- RULES.clear
152
- end
303
+ # Application owning this task.
304
+ attr_accessor :application
305
+
306
+ # Comment for this task.
307
+ attr_accessor :comment
308
+
309
+ # Array of nested namespaces names used for task lookup by this task.
310
+ attr_reader :scope
153
311
 
154
- # List of all defined tasks.
155
- def tasks
156
- TASKS.keys.sort.collect { |tn| Task[tn] }
312
+ # Return task name
313
+ def to_s
314
+ name
315
+ end
316
+
317
+ # List of sources for task.
318
+ attr_writer :sources
319
+ def sources
320
+ @sources ||= []
157
321
  end
158
322
 
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
167
- end
168
- if task = enhance_with_matching_rule(task_name)
169
- return task
170
- end
171
- if File.exist?(task_name)
172
- return FileTask.define_task(task_name)
323
+ # First source from a rule (nil if no sources)
324
+ def source
325
+ @sources.first if defined?(@sources)
326
+ end
327
+
328
+ # Create a task named +task_name+ with no actions or prerequisites. Use
329
+ # +enhance+ to add actions and prerequisites.
330
+ def initialize(task_name, app)
331
+ @name = task_name.to_s
332
+ @prerequisites = FileList[]
333
+ @actions = []
334
+ @already_invoked = false
335
+ @comment = nil
336
+ @lock = Mutex.new
337
+ @application = app
338
+ @scope = app.current_scope
339
+ end
340
+
341
+ # Enhance a task with prerequisites or actions. Returns self.
342
+ def enhance(deps=nil, &block)
343
+ @prerequisites |= deps if deps
344
+ @actions << block if block_given?
345
+ self
346
+ end
347
+
348
+ # Name of the task, including any namespace qualifiers.
349
+ def name
350
+ @name.to_s
351
+ end
352
+
353
+ # Invoke the task if it is needed. Prerequites are invoked first.
354
+ def invoke
355
+ @lock.synchronize do
356
+ if application.options.trace
357
+ puts "** Invoke #{name} #{format_trace_flags}"
358
+ end
359
+ return if @already_invoked
360
+ @already_invoked = true
361
+ invoke_prerequisites
362
+ execute if needed?
173
363
  end
174
- fail "Don't know how to rake #{task_name}"
175
364
  end
176
365
 
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]
366
+ # Invoke all the prerequisites of a task.
367
+ def invoke_prerequisites
368
+ @prerequisites.each { |n|
369
+ application[n, @scope].invoke
370
+ }
181
371
  end
182
372
 
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)
373
+ # Format the trace flags for display.
374
+ def format_trace_flags
375
+ flags = []
376
+ flags << "first_time" unless @already_invoked
377
+ flags << "not_needed" unless needed?
378
+ flags.empty? ? "" : "(" + flags.join(", ") + ")"
193
379
  end
194
-
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]
380
+ private :format_trace_flags
381
+
382
+ # Execute the actions associated with this task.
383
+ def execute
384
+ if application.options.dryrun
385
+ puts "** Execute (dry run) #{name}"
386
+ return
387
+ end
388
+ if application.options.trace
389
+ puts "** Execute #{name}"
390
+ end
391
+ application.enhance_with_matching_rule(name) if @actions.empty?
392
+ @actions.each { |act| result = act.call(self) }
201
393
  end
202
-
203
394
 
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)
395
+ # Is this task needed?
396
+ def needed?
397
+ true
209
398
  end
210
-
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
399
+
400
+ # Timestamp for this task. Basic tasks return the current time for their
401
+ # time stamp. Other tasks can be more sophisticated.
402
+ def timestamp
403
+ @prerequisites.collect { |p| application[p].timestamp }.max || Time.now
404
+ end
405
+
406
+ # Add a comment to the task. If a comment alread exists, separate
407
+ # the new comment with " / ".
408
+ def add_comment(comment)
409
+ return if ! comment
410
+ if @comment
411
+ @comment << " / "
412
+ else
413
+ @comment = ''
233
414
  end
234
- nil
415
+ @comment << comment
416
+ end
417
+
418
+ # Return a string describing the internal state of a task. Useful for
419
+ # debugging.
420
+ def investigation
421
+ result = "------------------------------\n"
422
+ result << "Investigating #{name}\n"
423
+ result << "class: #{self.class}\n"
424
+ result << "task needed: #{needed?}\n"
425
+ result << "timestamp: #{timestamp}\n"
426
+ result << "pre-requisites: \n"
427
+ prereqs = @prerequisites.collect {|name| application[name]}
428
+ prereqs.sort! {|a,b| a.timestamp <=> b.timestamp}
429
+ prereqs.each do |p|
430
+ result << "--#{p.name} (#{p.timestamp})\n"
431
+ end
432
+ latest_prereq = @prerequisites.collect{|n| application[n].timestamp}.max
433
+ result << "latest-prerequisite time: #{latest_prereq}\n"
434
+ result << "................................\n\n"
435
+ return result
235
436
  end
236
437
 
237
- private
438
+ # ----------------------------------------------------------------
439
+ # Rake Module Methods
440
+ #
441
+ class << self
442
+
443
+ # Clear the task list. This cause rake to immediately forget all the
444
+ # tasks that have been assigned. (Normally used in the unit tests.)
445
+ def clear
446
+ Rake.application.clear
447
+ end
448
+
449
+ # List of all defined tasks.
450
+ def tasks
451
+ Rake.application.tasks
452
+ end
453
+
454
+ # Return a task with the given name. If the task is not currently
455
+ # known, try to synthesize one from the defined rules. If no rules are
456
+ # found, but an existing file matches the task name, assume it is a file
457
+ # task with no dependencies or actions.
458
+ def [](task_name)
459
+ Rake.application[task_name]
460
+ end
461
+
462
+ # TRUE if the task name is already defined.
463
+ def task_defined?(task_name)
464
+ Rake.application.lookup(task_name) != nil
465
+ end
466
+
467
+ # Define a task given +args+ and an option block. If a rule with the
468
+ # given name already exists, the prerequisites and actions are added to
469
+ # the existing task. Returns the defined task.
470
+ def define_task(args, &block)
471
+ Rake.application.define_task(self, args, &block)
472
+ end
473
+
474
+ # Define a rule for synthesizing tasks.
475
+ def create_rule(args, &block)
476
+ Rake.application.create_rule(args, &block)
477
+ end
238
478
 
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)
248
- else
249
- task_name = args
250
- deps = []
479
+ # Apply the scope to the task name according to the rules for
480
+ # this kind of task. Generic tasks will accept the scope as
481
+ # part of the name.
482
+ def scope_name(scope, task_name)
483
+ (scope + [task_name]).join(':')
251
484
  end
252
- [task_name, deps]
485
+
253
486
  end
254
487
  end
255
- end
488
+
489
+
490
+ # #########################################################################
491
+ # A FileTask is a task that includes time based dependencies. If any of a
492
+ # FileTask's prerequisites have a timestamp that is later than the file
493
+ # represented by this task, then the file must be rebuilt (using the
494
+ # supplied actions).
495
+ #
496
+ class FileTask < Task
497
+
498
+ # Is this file task needed? Yes if it doesn't exist, or if its time stamp
499
+ # is out of date.
500
+ def needed?
501
+ return true unless File.exist?(name)
502
+ return true if out_of_date?(timestamp)
503
+ false
504
+ end
505
+
506
+ # Time stamp for file task.
507
+ def timestamp
508
+ if File.exist?(name)
509
+ File.mtime(name.to_s)
510
+ else
511
+ Rake::EARLY
512
+ end
513
+ end
256
514
 
515
+ private
257
516
 
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
265
-
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
517
+ # Are there any prerequisites with a later time than the given time stamp?
518
+ def out_of_date?(stamp)
519
+ @prerequisites.any? { |n| application[n].timestamp > stamp}
520
+ end
521
+
522
+ # ----------------------------------------------------------------
523
+ # Task class methods.
524
+ #
525
+ class << self
526
+ # Apply the scope to the task name according to the rules for this kind
527
+ # of task. File based tasks ignore the scope when creating the name.
528
+ def scope_name(scope, task_name)
529
+ task_name
530
+ end
531
+ end
532
+ end
533
+
534
+ # #########################################################################
535
+ # A FileCreationTask is a file task that when used as a dependency will be
536
+ # needed if and only if the file has not been created. Once created, it is
537
+ # not re-triggered if any of its dependencies are newer, nor does trigger
538
+ # any rebuilds of tasks that depend on it whenever it is updated.
539
+ #
540
+ class FileCreationTask < FileTask
541
+ # Is this file task needed? Yes if it doesn't exist.
542
+ def needed?
543
+ ! File.exist?(name)
544
+ end
545
+
546
+ # Time stamp for file creation task. This time stamp is earlier
547
+ # than any other time stamp.
548
+ def timestamp
549
+ Rake::EARLY
550
+ end
273
551
  end
274
552
 
275
- # Time stamp for file task.
276
- def timestamp
277
- File.mtime(name.to_s)
553
+ # #########################################################################
554
+ # Same as a regular task, but the immediate prerequisites are done in
555
+ # parallel using Ruby threads.
556
+ #
557
+ class MultiTask < Task
558
+ def invoke_prerequisites
559
+ threads = @prerequisites.collect { |p|
560
+ Thread.new(p) { |r| application[r].invoke }
561
+ }
562
+ threads.each { |t| t.join }
563
+ end
278
564
  end
279
565
  end
280
566
 
281
- ######################################################################
567
+ # ###########################################################################
282
568
  # Task Definition Functions ...
283
569
 
284
570
  # Declare a basic task.
@@ -289,7 +575,7 @@ end
289
575
  # end
290
576
  #
291
577
  def task(args, &block)
292
- Task.define_task(args, &block)
578
+ Rake::Task.define_task(args, &block)
293
579
  end
294
580
 
295
581
 
@@ -307,24 +593,54 @@ end
307
593
  # end
308
594
  #
309
595
  def file(args, &block)
310
- FileTask.define_task(args, &block)
596
+ Rake::FileTask.define_task(args, &block)
311
597
  end
312
598
 
313
- # Declare a set of files tasks to create the given directories on
314
- # demand.
599
+ # Declare a file creation task.
600
+ # (Mainly used for the directory command).
601
+ def file_create(args, &block)
602
+ Rake::FileCreationTask.define_task(args, &block)
603
+ end
604
+
605
+ # Declare a set of files tasks to create the given directories on demand.
315
606
  #
316
607
  # Example:
317
608
  # directory "testdata/doc"
318
609
  #
319
610
  def directory(dir)
320
- while dir != '.' && dir != '/'
321
- file dir do |t|
611
+ Rake.each_dir_parent(dir) do |d|
612
+ file_create d do |t|
322
613
  mkdir_p t.name if ! File.exist?(t.name)
323
614
  end
324
- dir = File.dirname(dir)
325
615
  end
326
616
  end
327
617
 
618
+ # Declare a task that performs its prerequisites in parallel. Multitasks does
619
+ # *not* guarantee that its prerequisites will execute in any given order
620
+ # (which is obvious when you think about it)
621
+ #
622
+ # Example:
623
+ # multitask :deploy => [:deploy_gem, :deploy_rdoc]
624
+ #
625
+ def multitask(args, &block)
626
+ Rake::MultiTask.define_task(args, &block)
627
+ end
628
+
629
+ # Create a new rake namespace and use it for evaluating the given block.
630
+ # Returns a NameSpace object that can be used to lookup tasks defined in the
631
+ # namespace.
632
+ #
633
+ # E.g.
634
+ #
635
+ # ns = namespace "nested" do
636
+ # task :run
637
+ # end
638
+ # task_run = ns[:run] # find :run in the given namespace.
639
+ #
640
+ def namespace(name=nil, &block)
641
+ Rake.application.in_namespace(name, &block)
642
+ end
643
+
328
644
  # Declare a rule for auto-tasks.
329
645
  #
330
646
  # Example:
@@ -333,7 +649,7 @@ end
333
649
  # end
334
650
  #
335
651
  def rule(args, &block)
336
- Task.create_rule(args, &block)
652
+ Rake::Task.create_rule(args, &block)
337
653
  end
338
654
 
339
655
  # Describe the next rake task.
@@ -345,30 +661,69 @@ end
345
661
  # end
346
662
  #
347
663
  def desc(comment)
348
- $last_comment = comment
664
+ Rake.application.last_comment = comment
349
665
  end
350
666
 
667
+ # Import the partial Rakefiles +fn+. Imported files are loaded _after_ the
668
+ # current file is completely loaded. This allows the import statement to
669
+ # appear anywhere in the importing file, and yet allowing the imported files
670
+ # to depend on objects defined in the importing file.
671
+ #
672
+ # A common use of the import statement is to include files containing
673
+ # dependency declarations.
674
+ #
675
+ # See also the --rakelibdir command line option.
676
+ #
677
+ # Example:
678
+ # import ".depend", "my_rules"
679
+ #
680
+ def import(*fns)
681
+ fns.each do |fn|
682
+ Rake.application.add_import(fn)
683
+ end
684
+ end
351
685
 
352
- ######################################################################
353
- # This a FileUtils extension that defines several additional commands
354
- # to be added to the FileUtils utility functions.
686
+ # ###########################################################################
687
+ # This a FileUtils extension that defines several additional commands to be
688
+ # added to the FileUtils utility functions.
355
689
  #
356
690
  module FileUtils
357
- RUBY = Config::CONFIG['ruby_install_name']
691
+ RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
358
692
 
359
693
  OPT_TABLE['sh'] = %w(noop verbose)
360
694
  OPT_TABLE['ruby'] = %w(noop verbose)
361
695
 
362
- # Run the system command +cmd+.
696
+ # Run the system command +cmd+. If multiple arguments are given the command
697
+ # is not run with the shell (same semantics as Kernel::exec and
698
+ # Kernel::system).
363
699
  #
364
700
  # Example:
365
701
  # sh %{ls -ltr}
366
702
  #
367
- def sh(cmd, options={})
368
- fu_check_options options, :noop, :verbose
369
- fu_output_message cmd if options[:verbose]
703
+ # sh 'ls', 'file with spaces'
704
+ #
705
+ # # check exit status after command runs
706
+ # sh %{grep pattern file} do |ok, res|
707
+ # if ! ok
708
+ # puts "pattern not found (status = #{res.exitstatus})"
709
+ # end
710
+ # end
711
+ #
712
+ def sh(*cmd, &block)
713
+ options = (Hash === cmd.last) ? cmd.pop : {}
714
+ unless block_given?
715
+ show_command = cmd.join(" ")
716
+ show_command = show_command[0,42] + "..."
717
+ # TODO code application logic heref show_command.length > 45
718
+ block = lambda { |ok, status|
719
+ ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]"
720
+ }
721
+ end
722
+ rake_check_options options, :noop, :verbose
723
+ rake_output_message cmd.join(" ") if options[:verbose]
370
724
  unless options[:noop]
371
- system(cmd) or fail "Command Failed: [#{cmd}]"
725
+ res = system(*cmd)
726
+ block.call(res, $?)
372
727
  end
373
728
  end
374
729
 
@@ -377,28 +732,28 @@ module FileUtils
377
732
  # Example:
378
733
  # ruby %{-pe '$_.upcase!' <README}
379
734
  #
380
- def ruby(*args)
381
- if Hash === args.last
382
- options = args.pop
735
+ def ruby(*args,&block)
736
+ options = (Hash === args.last) ? args.pop : {}
737
+ if args.length > 1 then
738
+ sh(*([RUBY] + args + [options]), &block)
383
739
  else
384
- options = {}
740
+ sh("#{RUBY} #{args}", options, &block)
385
741
  end
386
- sh "#{RUBY} #{args.join(' ')}", options
387
742
  end
388
743
 
389
744
  LN_SUPPORTED = [true]
390
745
 
391
- # Attempt to do a normal file link, but fall back to a copy if the
392
- # link fails.
746
+ # Attempt to do a normal file link, but fall back to a copy if the link
747
+ # fails.
393
748
  def safe_ln(*args)
394
749
  unless LN_SUPPORTED[0]
395
750
  cp(*args)
396
751
  else
397
752
  begin
398
- ln(*args)
399
- rescue Errno::EOPNOTSUPP
400
- LN_SUPPORTED[0] = false
401
- cp(*args)
753
+ ln(*args)
754
+ rescue StandardError, NotImplementedError => ex
755
+ LN_SUPPORTED[0] = false
756
+ cp(*args)
402
757
  end
403
758
  end
404
759
  end
@@ -416,32 +771,44 @@ module FileUtils
416
771
  end
417
772
  end
418
773
 
419
- ######################################################################
420
- # RakeFileUtils provides a custom version of the FileUtils methods
421
- # that respond to the <tt>verbose</tt> and <tt>nowrite</tt> commands.
774
+ # ###########################################################################
775
+ # RakeFileUtils provides a custom version of the FileUtils methods that
776
+ # respond to the <tt>verbose</tt> and <tt>nowrite</tt> commands.
422
777
  #
423
778
  module RakeFileUtils
424
779
  include FileUtils
780
+
781
+ class << self
782
+ attr_accessor :verbose_flag, :nowrite_flag
783
+ end
784
+ RakeFileUtils.verbose_flag = true
785
+ RakeFileUtils.nowrite_flag = false
425
786
 
426
- $fileutils_output = $stderr
427
- $fileutils_label = ''
428
787
  $fileutils_verbose = true
429
788
  $fileutils_nowrite = false
430
789
 
431
790
  FileUtils::OPT_TABLE.each do |name, opts|
432
- next unless opts.include?('verbose')
791
+ default_options = []
792
+ if opts.include?('verbose')
793
+ default_options << ':verbose => RakeFileUtils.verbose_flag'
794
+ end
795
+ if opts.include?('noop')
796
+ default_options << ':noop => RakeFileUtils.nowrite_flag'
797
+ end
798
+
799
+ next if default_options.empty?
433
800
  module_eval(<<-EOS, __FILE__, __LINE__ + 1)
434
- def #{name}( *args )
435
- super(*fu_merge_option(args,
436
- :verbose => $fileutils_verbose,
437
- :noop => $fileutils_nowrite))
801
+ def #{name}( *args, &block )
802
+ super(
803
+ *rake_merge_option(args,
804
+ #{default_options.join(', ')}
805
+ ), &block)
438
806
  end
439
807
  EOS
440
808
  end
441
809
 
442
- # Get/set the verbose flag controlling output from the FileUtils
443
- # utilities. If verbose is true, then the utility method is echoed
444
- # to standard output.
810
+ # Get/set the verbose flag controlling output from the FileUtils utilities.
811
+ # If verbose is true, then the utility method is echoed to standard output.
445
812
  #
446
813
  # Examples:
447
814
  # verbose # return the current value of the verbose flag
@@ -449,21 +816,20 @@ module RakeFileUtils
449
816
  # verbose(v) { code } # Execute code with the verbose flag set temporarily to _v_.
450
817
  # # Return to the original value when code is done.
451
818
  def verbose(value=nil)
452
- oldvalue = $fileutils_verbose
453
- $fileutils_verbose = value unless value.nil?
819
+ oldvalue = RakeFileUtils.verbose_flag
820
+ RakeFileUtils.verbose_flag = value unless value.nil?
454
821
  if block_given?
455
822
  begin
456
- yield
823
+ yield
457
824
  ensure
458
- $fileutils_verbose = oldvalue
825
+ RakeFileUtils.verbose_flag = oldvalue
459
826
  end
460
827
  end
461
- $fileutils_verbose
828
+ RakeFileUtils.verbose_flag
462
829
  end
463
830
 
464
- # Get/set the nowrite flag controlling output from the FileUtils
465
- # utilities. If verbose is true, then the utility method is echoed
466
- # to standard output.
831
+ # Get/set the nowrite flag controlling output from the FileUtils utilities.
832
+ # If verbose is true, then the utility method is echoed to standard output.
467
833
  #
468
834
  # Examples:
469
835
  # nowrite # return the current value of the nowrite flag
@@ -471,20 +837,20 @@ module RakeFileUtils
471
837
  # nowrite(v) { code } # Execute code with the nowrite flag set temporarily to _v_.
472
838
  # # Return to the original value when code is done.
473
839
  def nowrite(value=nil)
474
- oldvalue = $fileutils_nowrite
475
- $fileutils_nowrite = value unless value.nil?
840
+ oldvalue = RakeFileUtils.nowrite_flag
841
+ RakeFileUtils.nowrite_flag = value unless value.nil?
476
842
  if block_given?
477
843
  begin
478
- yield
844
+ yield
479
845
  ensure
480
- $fileutils_nowrite = oldvalue
846
+ RakeFileUtils.nowrite_flag = oldvalue
481
847
  end
482
848
  end
483
849
  oldvalue
484
850
  end
485
851
 
486
- # Use this function to prevent protentially destructive ruby code
487
- # from running when the :nowrite flag is set.
852
+ # Use this function to prevent protentially destructive ruby code from
853
+ # running when the :nowrite flag is set.
488
854
  #
489
855
  # Example:
490
856
  #
@@ -492,13 +858,13 @@ module RakeFileUtils
492
858
  # project.build
493
859
  # end
494
860
  #
495
- # The following code will build the project under normal conditions.
496
- # If the nowrite(true) flag is set, then the example will print:
861
+ # The following code will build the project under normal conditions. If the
862
+ # nowrite(true) flag is set, then the example will print:
497
863
  # DRYRUN: Building Project
498
864
  # instead of actually building the project.
499
865
  #
500
866
  def when_writing(msg=nil)
501
- if $fileutils_nowrite
867
+ if RakeFileUtils.nowrite_flag
502
868
  puts "DRYRUN: #{msg}" if msg
503
869
  else
504
870
  yield
@@ -506,7 +872,7 @@ module RakeFileUtils
506
872
  end
507
873
 
508
874
  # Merge the given options with the default values.
509
- def fu_merge_option(args, defaults)
875
+ def rake_merge_option(args, defaults)
510
876
  if Hash === args.last
511
877
  defaults.update(args.last)
512
878
  args.pop
@@ -514,51 +880,141 @@ module RakeFileUtils
514
880
  args.push defaults
515
881
  args
516
882
  end
517
- private :fu_merge_option
883
+ private :rake_merge_option
884
+
885
+ # Send the message to the default rake output (which is $stderr).
886
+ def rake_output_message(message)
887
+ $stderr.puts(message)
888
+ end
889
+ private :rake_output_message
890
+
891
+ # Check that the options do not contain options not listed in +optdecl+. An
892
+ # ArgumentError exception is thrown if non-declared options are found.
893
+ def rake_check_options(options, *optdecl)
894
+ h = options.dup
895
+ optdecl.each do |name|
896
+ h.delete name
897
+ end
898
+ raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
899
+ end
900
+ private :rake_check_options
518
901
 
519
902
  extend self
520
-
521
903
  end
522
904
 
905
+ # ###########################################################################
906
+ # Include the FileUtils file manipulation functions in the top level module,
907
+ # but mark them private so that they don't unintentionally define methods on
908
+ # other objects.
909
+
523
910
  include RakeFileUtils
911
+ private(*FileUtils.instance_methods(false))
912
+ private(*RakeFileUtils.instance_methods(false))
524
913
 
914
+ ######################################################################
525
915
  module Rake
526
916
 
527
- ####################################################################
528
- # A FileList is essentially an array with a few helper methods
529
- # defined to make file manipulation a bit easier.
530
- #
531
- class FileList < Array
917
+ class RuleRecursionOverflowError < StandardError
918
+ def initialize(*args)
919
+ super
920
+ @targets = []
921
+ end
532
922
 
533
- # Rewrite all array methods (and to_s/inspect) to resolve the list
534
- # before running.
535
- method_list = Array.instance_methods - Object.instance_methods
536
- %w[to_a to_s inspect].each do |meth|
537
- method_list << meth unless method_list.include? meth
923
+ def add_target(target)
924
+ @targets << target
538
925
  end
539
- method_list.each_with_index do |sym, i|
540
- if sym =~ /^[A-Za-z_]+$/
541
- name = sym
926
+
927
+ def message
928
+ super + ": [" + @targets.reverse.join(' => ') + "]"
929
+ end
930
+ end
931
+
932
+ # #########################################################################
933
+ # A FileList is essentially an array with a few helper methods defined to
934
+ # make file manipulation a bit easier.
935
+ #
936
+ # FileLists are lazy. When given a list of glob patterns for possible files
937
+ # to be included in the file list, instead of searching the file structures
938
+ # to find the files, a FileList holds the pattern for latter use.
939
+ #
940
+ # This allows us to define a number of FileList to match any number of
941
+ # files, but only search out the actual files when then FileList itself is
942
+ # actually used. The key is that the first time an element of the
943
+ # FileList/Array is requested, the pending patterns are resolved into a real
944
+ # list of file names.
945
+ #
946
+ class FileList
947
+
948
+ include Cloneable
949
+
950
+ # == Method Delegation
951
+ #
952
+ # The lazy evaluation magic of FileLists happens by implementing all the
953
+ # array specific methods to call +resolve+ before delegating the heavy
954
+ # lifting to an embedded array object (@items).
955
+ #
956
+ # In addition, there are two kinds of delegation calls. The regular kind
957
+ # delegates to the @items array and returns the result directly. Well,
958
+ # almost directly. It checks if the returned value is the @items object
959
+ # itself, and if so will return the FileList object instead.
960
+ #
961
+ # The second kind of delegation call is used in methods that normally
962
+ # return a new Array object. We want to capture the return value of these
963
+ # methods and wrap them in a new FileList object. We enumerate these
964
+ # methods in the +SPECIAL_RETURN+ list below.
965
+
966
+ # List of array methods (that are not in +Object+) that need to be
967
+ # delegated.
968
+ ARRAY_METHODS = Array.instance_methods - Object.instance_methods
969
+
970
+ # List of additional methods that must be delegated.
971
+ MUST_DEFINE = %w[to_a inspect]
972
+
973
+ # List of methods that should not be delegated here (we define special
974
+ # versions of them explicitly below).
975
+ MUST_NOT_DEFINE = %w[to_a to_ary partition *]
976
+
977
+ # List of delegated methods that return new array values which need
978
+ # wrapping.
979
+ SPECIAL_RETURN = %w[
980
+ map collect sort sort_by select find_all reject grep
981
+ compact flatten uniq values_at
982
+ + - & |
983
+ ]
984
+
985
+ DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).collect{ |s| s.to_s }.sort.uniq
986
+
987
+ # Now do the delegation.
988
+ DELEGATING_METHODS.each_with_index do |sym, i|
989
+ if SPECIAL_RETURN.include?(sym)
990
+ ln = __LINE__+1
991
+ class_eval %{
992
+ def #{sym}(*args, &block)
993
+ resolve if @pending
994
+ result = @items.send(:#{sym}, *args, &block)
995
+ FileList.new.import(result)
996
+ end
997
+ }, __FILE__, ln
542
998
  else
543
- name = i
544
- end
545
- alias_method "array_#{name}", sym
546
- class_eval %{
547
- def #{sym}(*args, &block)
548
- resolve if @pending
549
- array_#{name}(*args, &block)
550
- end
551
- }
999
+ ln = __LINE__+1
1000
+ class_eval %{
1001
+ def #{sym}(*args, &block)
1002
+ resolve if @pending
1003
+ result = @items.send(:#{sym}, *args, &block)
1004
+ result.object_id == @items.object_id ? self : result
1005
+ end
1006
+ }, __FILE__, ln
1007
+ end
552
1008
  end
553
1009
 
554
- # Create a file list from the globbable patterns given. If you
555
- # wish to perform multiple includes or excludes at object build
556
- # time, use the "yield self" pattern.
1010
+ # Create a file list from the globbable patterns given. If you wish to
1011
+ # perform multiple includes or excludes at object build time, use the
1012
+ # "yield self" pattern.
557
1013
  #
558
1014
  # Example:
559
- # file_list = FileList.new['lib/**/*.rb', 'test/test*.rb']
1015
+ # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb')
560
1016
  #
561
- # pkg_files = FileList.new['lib/**/*'] do |fl|
1017
+ # pkg_files = FileList.new('lib/**/*') do |fl|
562
1018
  # fl.exclude(/\bCVS\b/)
563
1019
  # end
564
1020
  #
@@ -566,13 +1022,15 @@ module Rake
566
1022
  @pending_add = []
567
1023
  @pending = false
568
1024
  @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
1025
+ @exclude_procs = DEFAULT_IGNORE_PROCS.dup
569
1026
  @exclude_re = nil
1027
+ @items = []
570
1028
  patterns.each { |pattern| include(pattern) }
571
1029
  yield self if block_given?
572
1030
  end
573
1031
 
574
- # Add file names defined by glob patterns to the file list. If an
575
- # array is given, add each element of the array.
1032
+ # Add file names defined by glob patterns to the file list. If an array
1033
+ # is given, add each element of the array.
576
1034
  #
577
1035
  # Example:
578
1036
  # file_list.include("*.java", "*.cfg")
@@ -580,20 +1038,27 @@ module Rake
580
1038
  #
581
1039
  def include(*filenames)
582
1040
  # TODO: check for pending
583
- filenames.each do |fn| @pending_add << fn end
1041
+ filenames.each do |fn|
1042
+ if fn.respond_to? :to_ary
1043
+ include(*fn.to_ary)
1044
+ else
1045
+ @pending_add << fn
1046
+ end
1047
+ end
584
1048
  @pending = true
585
1049
  self
586
1050
  end
587
1051
  alias :add :include
588
1052
 
589
- # Register a list of file name patterns that should be excluded
590
- # from the list. Patterns may be regular expressions, glob
591
- # patterns or regular strings.
1053
+ # Register a list of file name patterns that should be excluded from the
1054
+ # list. Patterns may be regular expressions, glob patterns or regular
1055
+ # strings. In addition, a block given to exclude will remove entries that
1056
+ # return true when given to the block.
592
1057
  #
593
- # Note that glob patterns are expanded against the file system.
594
- # If a file is explicitly added to a file list, but does not exist
595
- # in the file system, then an glob pattern in the exclude list
596
- # will not exclude the file.
1058
+ # Note that glob patterns are expanded against the file system. If a file
1059
+ # is explicitly added to a file list, but does not exist in the file
1060
+ # system, then an glob pattern in the exclude list will not exclude the
1061
+ # file.
597
1062
  #
598
1063
  # Examples:
599
1064
  # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
@@ -605,79 +1070,109 @@ module Rake
605
1070
  # If "a.c" is not a file, then ...
606
1071
  # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
607
1072
  #
608
- def exclude(*patterns)
609
- # TODO: check for pending
610
- patterns.each do |pat| @exclude_patterns << pat end
611
- if ! @pending
612
- calculate_exclude_regexp
613
- reject! { |fn| fn =~ @exclude_re }
1073
+ def exclude(*patterns, &block)
1074
+ patterns.each do |pat|
1075
+ @exclude_patterns << pat
614
1076
  end
1077
+ if block_given?
1078
+ @exclude_procs << block
1079
+ end
1080
+ resolve_exclude if ! @pending
615
1081
  self
616
1082
  end
617
1083
 
618
1084
 
1085
+ # Clear all the exclude patterns so that we exclude nothing.
619
1086
  def clear_exclude
620
1087
  @exclude_patterns = []
1088
+ @exclude_procs = []
621
1089
  calculate_exclude_regexp if ! @pending
1090
+ self
1091
+ end
1092
+
1093
+ # Define equality.
1094
+ def ==(array)
1095
+ to_ary == array
1096
+ end
1097
+
1098
+ # Return the internal array object.
1099
+ def to_a
1100
+ resolve
1101
+ @items
1102
+ end
1103
+
1104
+ # Return the internal array object.
1105
+ def to_ary
1106
+ to_a
1107
+ end
1108
+
1109
+ # Lie about our class.
1110
+ def is_a?(klass)
1111
+ klass == Array || super(klass)
1112
+ end
1113
+ alias kind_of? is_a?
1114
+
1115
+ # Redefine * to return either a string or a new file list.
1116
+ def *(other)
1117
+ result = @items * other
1118
+ case result
1119
+ when Array
1120
+ FileList.new.import(result)
1121
+ else
1122
+ result
1123
+ end
622
1124
  end
623
1125
 
624
1126
  # Resolve all the pending adds now.
625
1127
  def resolve
626
- @pending = false
627
- @pending_add.each do |fn| resolve_add(fn) end
628
- @pending_add = []
629
- resolve_exclude
1128
+ if @pending
1129
+ @pending = false
1130
+ @pending_add.each do |fn| resolve_add(fn) end
1131
+ @pending_add = []
1132
+ resolve_exclude
1133
+ end
630
1134
  self
631
1135
  end
632
1136
 
633
1137
  def calculate_exclude_regexp
634
1138
  ignores = []
635
1139
  @exclude_patterns.each do |pat|
636
- case pat
637
- when Regexp
638
- ignores << pat
639
- when /[*.]/
640
- Dir[pat].each do |p| ignores << p end
641
- else
642
- ignores << Regexp.quote(pat)
643
- end
1140
+ case pat
1141
+ when Regexp
1142
+ ignores << pat
1143
+ when /[*?]/
1144
+ Dir[pat].each do |p| ignores << p end
1145
+ else
1146
+ ignores << Regexp.quote(pat)
1147
+ end
644
1148
  end
645
1149
  if ignores.empty?
646
- @exclude_re = /^$/
1150
+ @exclude_re = /^$/
647
1151
  else
648
- re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
649
- @exclude_re = Regexp.new(re_str)
1152
+ re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
1153
+ @exclude_re = Regexp.new(re_str)
650
1154
  end
651
1155
  end
652
1156
 
653
1157
  def resolve_add(fn)
654
1158
  case fn
655
- when Array
656
- fn.each { |f| self.resolve_add(f) }
657
- when %r{[*?]}
658
- add_matching(fn)
1159
+ when %r{[*?\[\{]}
1160
+ add_matching(fn)
659
1161
  else
660
- self << fn
1162
+ self << fn
661
1163
  end
662
1164
  end
1165
+ private :resolve_add
663
1166
 
664
1167
  def resolve_exclude
665
- @exclude_patterns.each do |pat|
666
- case pat
667
- when Regexp
668
- reject! { |fn| fn =~ pat }
669
- when /[*.]/
670
- reject_list = Dir[pat]
671
- reject! { |fn| reject_list.include?(fn) }
672
- else
673
- reject! { |fn| fn == pat }
674
- end
675
- end
1168
+ calculate_exclude_regexp
1169
+ reject! { |fn| exclude?(fn) }
676
1170
  self
677
1171
  end
1172
+ private :resolve_exclude
678
1173
 
679
- # Return a new FileList with the results of running +sub+ against
680
- # each element of the oringal list.
1174
+ # Return a new FileList with the results of running +sub+ against each
1175
+ # element of the oringal list.
681
1176
  #
682
1177
  # Example:
683
1178
  # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
@@ -686,8 +1181,8 @@ module Rake
686
1181
  inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
687
1182
  end
688
1183
 
689
- # Return a new FileList with the results of running +gsub+ against
690
- # each element of the original list.
1184
+ # Return a new FileList with the results of running +gsub+ against each
1185
+ # element of the original list.
691
1186
  #
692
1187
  # Example:
693
1188
  # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
@@ -709,6 +1204,74 @@ module Rake
709
1204
  self
710
1205
  end
711
1206
 
1207
+ # Apply the pathmap spec to each of the included file names, returning a
1208
+ # new file list with the modified paths. (See String#pathmap for
1209
+ # details.)
1210
+ def pathmap(spec=nil)
1211
+ collect { |fn| fn.pathmap(spec) }
1212
+ end
1213
+
1214
+ # Return a new array with <tt>String#ext</tt> method applied to each
1215
+ # member of the array.
1216
+ #
1217
+ # This method is a shortcut for:
1218
+ #
1219
+ # array.collect { |item| item.ext(newext) }
1220
+ #
1221
+ # +ext+ is a user added method for the Array class.
1222
+ def ext(newext='')
1223
+ collect { |fn| fn.ext(newext) }
1224
+ end
1225
+
1226
+
1227
+ # Grep each of the files in the filelist using the given pattern. If a
1228
+ # block is given, call the block on each matching line, passing the file
1229
+ # name, line number, and the matching line of text. If no block is given,
1230
+ # a standard emac style file:linenumber:line message will be printed to
1231
+ # standard out.
1232
+ def egrep(pattern)
1233
+ each do |fn|
1234
+ open(fn) do |inf|
1235
+ count = 0
1236
+ inf.each do |line|
1237
+ count += 1
1238
+ if pattern.match(line)
1239
+ if block_given?
1240
+ yield fn, count, line
1241
+ else
1242
+ puts "#{fn}:#{count}:#{line}"
1243
+ end
1244
+ end
1245
+ end
1246
+ end
1247
+ end
1248
+ end
1249
+
1250
+ # Return a new file list that only contains file names from the current
1251
+ # file list that exist on the file system.
1252
+ def existing
1253
+ select { |fn| File.exists?(fn) }
1254
+ end
1255
+
1256
+ # Modify the current file list so that it contains only file name that
1257
+ # exist on the file system.
1258
+ def existing!
1259
+ resolve
1260
+ @items = @items.select { |fn| File.exists?(fn) }
1261
+ self
1262
+ end
1263
+
1264
+ # FileList version of partition. Needed because the nested arrays should
1265
+ # be FileLists in this version.
1266
+ def partition(&block) # :nodoc:
1267
+ resolve
1268
+ result = @items.partition(&block)
1269
+ [
1270
+ FileList.new.import(result[0]),
1271
+ FileList.new.import(result[1]),
1272
+ ]
1273
+ end
1274
+
712
1275
  # Convert a FileList to a string by joining all elements with a space.
713
1276
  def to_s
714
1277
  resolve if @pending
@@ -718,7 +1281,7 @@ module Rake
718
1281
  # Add matching glob patterns.
719
1282
  def add_matching(pattern)
720
1283
  Dir[pattern].each do |fn|
721
- self << fn unless exclude?(fn)
1284
+ self << fn unless exclude?(fn)
722
1285
  end
723
1286
  end
724
1287
  private :add_matching
@@ -726,42 +1289,66 @@ module Rake
726
1289
  # Should the given file name be excluded?
727
1290
  def exclude?(fn)
728
1291
  calculate_exclude_regexp unless @exclude_re
729
- fn =~ @exclude_re
1292
+ fn =~ @exclude_re || @exclude_procs.any? { |p| p.call(fn) }
730
1293
  end
731
1294
 
732
1295
  DEFAULT_IGNORE_PATTERNS = [
733
1296
  /(^|[\/\\])CVS([\/\\]|$)/,
1297
+ /(^|[\/\\])\.svn([\/\\]|$)/,
734
1298
  /\.bak$/,
735
- /~$/,
736
- /(^|[\/\\])core$/
1299
+ /~$/
1300
+ ]
1301
+ DEFAULT_IGNORE_PROCS = [
1302
+ proc { |fn| fn =~ /(^|[\/\\])core$/ && ! File.directory?(fn) }
737
1303
  ]
738
1304
  @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
739
1305
 
1306
+ def import(array)
1307
+ @items = array
1308
+ self
1309
+ end
1310
+
740
1311
  class << self
741
1312
  # Create a new file list including the files listed. Similar to:
742
1313
  #
743
1314
  # FileList.new(*args)
744
1315
  def [](*args)
745
- new(*args)
1316
+ new(*args)
746
1317
  end
747
1318
 
748
- # Set the ignore patterns back to the default value. The
749
- # default patterns will ignore files
1319
+ # Set the ignore patterns back to the default value. The default
1320
+ # patterns will ignore files
750
1321
  # * containing "CVS" in the file path
1322
+ # * containing ".svn" in the file path
751
1323
  # * ending with ".bak"
752
1324
  # * ending with "~"
753
1325
  # * named "core"
754
1326
  #
755
- # Note that file names beginning with "." are automatically
756
- # ignored by Ruby's glob patterns and are not specifically
757
- # listed in the ignore patterns.
1327
+ # Note that file names beginning with "." are automatically ignored by
1328
+ # Ruby's glob patterns and are not specifically listed in the ignore
1329
+ # patterns.
758
1330
  def select_default_ignore_patterns
759
- @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
1331
+ @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
760
1332
  end
761
1333
 
762
1334
  # Clear the ignore patterns.
763
1335
  def clear_ignore_patterns
764
- @exclude_patterns = [ /^$/ ]
1336
+ @exclude_patterns = [ /^$/ ]
1337
+ end
1338
+ end
1339
+ end # FileList
1340
+ end
1341
+
1342
+ module Rake
1343
+ class << self
1344
+
1345
+ # Yield each file or directory component.
1346
+ def each_dir_parent(dir)
1347
+ old_length = nil
1348
+ while dir != '.' && dir.length != old_length
1349
+ yield(dir)
1350
+ old_length = dir.length
1351
+ dir = File.dirname(dir)
765
1352
  end
766
1353
  end
767
1354
  end
@@ -770,214 +1357,675 @@ end
770
1357
  # Alias FileList to be available at the top level.
771
1358
  FileList = Rake::FileList
772
1359
 
773
- ######################################################################
774
- # Rake main application object. When invoking +rake+ from the command
775
- # line, a RakeApp object is created and run.
776
- #
777
- class RakeApp
778
- RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb']
779
-
780
- OPTIONS = [
781
- ['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
782
- "Do a dry run without executing actions."],
783
- ['--help', '-H', GetoptLong::NO_ARGUMENT,
784
- "Display this help message."],
785
- ['--libdir', '-I', GetoptLong::REQUIRED_ARGUMENT,
786
- "Include LIBDIR in the search path for required modules."],
787
- ['--nosearch', '-N', GetoptLong::NO_ARGUMENT,
788
- "Do not search parent directories for the Rakefile."],
789
- ['--prereqs', '-P', GetoptLong::NO_ARGUMENT,
790
- "Display the tasks and dependencies, then exit."],
791
- ['--quiet', '-q', GetoptLong::NO_ARGUMENT,
792
- "Do not log messages to standard output."],
793
- ['--rakefile', '-f', GetoptLong::REQUIRED_ARGUMENT,
794
- "Use FILE as the rakefile."],
795
- ['--require', '-r', GetoptLong::REQUIRED_ARGUMENT,
796
- "Require MODULE before executing rakefile."],
797
- ['--tasks', '-T', GetoptLong::NO_ARGUMENT,
798
- "Display the tasks and dependencies, then exit."],
799
- ['--trace', '-t', GetoptLong::NO_ARGUMENT,
800
- "Turn on invoke/execute tracing."],
801
- ['--usage', '-h', GetoptLong::NO_ARGUMENT,
802
- "Display usage."],
803
- ['--verbose', '-v', GetoptLong::NO_ARGUMENT,
804
- "Log message to standard output (default)."],
805
- ['--version', '-V', GetoptLong::NO_ARGUMENT,
806
- "Display the program version."],
807
- ]
808
-
809
- # Create a RakeApp object.
810
- def initialize
811
- @rakefile = nil
812
- @nosearch = false
813
- end
1360
+ # ###########################################################################
1361
+ module Rake
814
1362
 
815
- # True if one of the files in RAKEFILES is in the current directory.
816
- # If a match is found, it is copied into @rakefile.
817
- def have_rakefile
818
- RAKEFILES.each do |fn|
819
- if File.exist?(fn)
820
- @rakefile = fn
821
- return true
822
- end
1363
+ # Default Rakefile loader used by +import+.
1364
+ class DefaultLoader
1365
+ def load(fn)
1366
+ Kernel.load(File.expand_path(fn))
823
1367
  end
824
- return false
825
1368
  end
826
1369
 
827
- # Display the program usage line.
828
- def usage
829
- puts "rake [-f rakefile] {options} targets..."
830
- end
1370
+ # EarlyTime is a fake timestamp that occurs _before_ any other time value.
1371
+ class EarlyTime
1372
+ include Comparable
1373
+ include Singleton
831
1374
 
832
- # Display the rake command line help.
833
- def help
834
- usage
835
- puts
836
- puts "Options are ..."
837
- puts
838
- OPTIONS.sort.each do |long, short, mode, desc|
839
- if mode == GetoptLong::REQUIRED_ARGUMENT
840
- if desc =~ /\b([A-Z]{2,})\b/
841
- long = long + "=#{$1}"
842
- end
843
- end
844
- printf " %-20s (%s)\n", long, short
845
- printf " %s\n", desc
1375
+ def <=>(other)
1376
+ -1
846
1377
  end
847
- end
848
1378
 
849
- # Display the tasks and dependencies.
850
- def display_tasks_and_comments
851
- width = Task.tasks.select { |t|
852
- t.comment
853
- }.collect { |t|
854
- t.name.length
855
- }.max
856
- Task.tasks.each do |t|
857
- if t.comment
858
- printf "rake %-#{width}s # %s\n", t.name, t.comment
859
- end
1379
+ def to_s
1380
+ "<EARLY TIME>"
860
1381
  end
861
1382
  end
862
1383
 
863
- # Display the tasks and prerequisites
864
- def display_prerequisites
865
- Task.tasks.each do |t|
866
- puts "rake #{t.name}"
867
- t.prerequisites.each { |pre| puts " #{pre}" }
1384
+ EARLY = EarlyTime.instance
1385
+ end
1386
+
1387
+ # ###########################################################################
1388
+ # Extensions to time to allow comparisons with an early time class.
1389
+ #
1390
+ class Time
1391
+ alias rake_original_time_compare :<=>
1392
+ def <=>(other)
1393
+ if Rake::EarlyTime === other
1394
+ - other.<=>(self)
1395
+ else
1396
+ rake_original_time_compare(other)
868
1397
  end
869
- end
1398
+ end
1399
+ end
870
1400
 
871
- # Return a list of the command line options supported by the
872
- # program.
873
- def command_line_options
874
- OPTIONS.collect { |lst| lst[0..-2] }
875
- end
1401
+ module Rake
876
1402
 
877
- # Do the option defined by +opt+ and +value+.
878
- def do_option(opt, value)
879
- case opt
880
- when '--dry-run'
881
- verbose(true)
882
- nowrite(true)
883
- $dryrun = true
884
- $trace = true
885
- when '--help'
886
- help
887
- exit
888
- when '--libdir'
889
- $:.push(value)
890
- when '--nosearch'
891
- @nosearch = true
892
- when '--prereqs'
893
- $show_prereqs = true
894
- when '--quiet'
895
- verbose(false)
896
- when '--rakefile'
897
- RAKEFILES.clear
898
- RAKEFILES << value
899
- when '--require'
900
- require value
901
- when '--tasks'
902
- $show_tasks = true
903
- when '--trace'
904
- $trace = true
905
- verbose(true)
906
- when '--usage'
907
- usage
908
- exit
909
- when '--verbose'
910
- verbose(true)
911
- when '--version'
912
- puts "rake, version #{RAKEVERSION}"
913
- exit
914
- else
915
- fail "Unknown option: #{opt}"
1403
+ ####################################################################
1404
+ # The NameSpace class will lookup task names in the the scope
1405
+ # defined by a +namespace+ command.
1406
+ #
1407
+ class NameSpace
1408
+
1409
+ # Create a namespace lookup object using the given task manager
1410
+ # and the list of scopes.
1411
+ def initialize(task_manager, scope_list)
1412
+ @task_manager = task_manager
1413
+ @scope = scope_list.dup
1414
+ end
1415
+
1416
+ # Lookup a task named +name+ in the namespace.
1417
+ def [](name)
1418
+ @task_manager.lookup(name, @scope)
1419
+ end
1420
+
1421
+ # Return the list of tasks defined in this namespace.
1422
+ def tasks
1423
+ @task_manager.tasks
916
1424
  end
917
1425
  end
918
-
919
- # Read and handle the command line options.
920
- def handle_options
921
- opts = GetoptLong.new(*command_line_options)
922
- opts.each { |opt, value| do_option(opt, value) }
923
- end
924
1426
 
925
- def load_rakefile
926
- here = Dir.pwd
927
- while ! have_rakefile
928
- Dir.chdir("..")
929
- if Dir.pwd == here || @nosearch
930
- fail "No Rakefile found (looking for: #{RAKEFILES.join(', ')})"
1427
+
1428
+ ####################################################################
1429
+ # The TaskManager module is a mixin for managing tasks.
1430
+ module TaskManager
1431
+ # Track the last comment made in the Rakefile.
1432
+ attr_accessor :last_comment
1433
+
1434
+ def initialize
1435
+ super
1436
+ @tasks = Hash.new
1437
+ @rules = Array.new
1438
+ @scope = Array.new
1439
+ @last_comment = nil
1440
+ end
1441
+
1442
+ def create_rule(args, &block)
1443
+ pattern, deps = resolve_args(args)
1444
+ pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern
1445
+ @rules << [pattern, deps, block]
1446
+ end
1447
+
1448
+ def define_task(task_class, args, &block)
1449
+ task_name, deps = resolve_args(args)
1450
+ task_name = task_class.scope_name(@scope, task_name)
1451
+ deps = [deps] unless deps.respond_to?(:to_ary)
1452
+ deps = deps.collect {|d| d.to_s }
1453
+ task = intern(task_class, task_name)
1454
+ task.add_comment(@last_comment)
1455
+ @last_comment = nil
1456
+ task.enhance(deps, &block)
1457
+ task
1458
+ end
1459
+
1460
+ # Lookup a task. Return an existing task if found, otherwise
1461
+ # create a task of the current type.
1462
+ def intern(task_class, task_name)
1463
+ @tasks[task_name.to_s] ||= task_class.new(task_name, self)
1464
+ end
1465
+
1466
+ # Find a matching task for +task_name+.
1467
+ def [](task_name, scopes=nil)
1468
+ task_name = task_name.to_s
1469
+ self.lookup(task_name, scopes) or
1470
+ enhance_with_matching_rule(task_name) or
1471
+ synthesize_file_task(task_name) or
1472
+ fail "Don't know how to build task '#{task_name}'"
1473
+ end
1474
+
1475
+ def synthesize_file_task(task_name)
1476
+ return nil unless File.exist?(task_name)
1477
+ define_task(Rake::FileTask, task_name)
1478
+ end
1479
+
1480
+ # Resolve the arguments for a task/rule.
1481
+ def resolve_args(args)
1482
+ case args
1483
+ when Hash
1484
+ fail "Too Many Task Names: #{args.keys.join(' ')}" if args.size > 1
1485
+ fail "No Task Name Given" if args.size < 1
1486
+ task_name = args.keys[0]
1487
+ deps = args[task_name]
1488
+ deps = [deps] if (String===deps) || (Regexp===deps) || (Proc===deps)
1489
+ else
1490
+ task_name = args
1491
+ deps = []
931
1492
  end
932
- here = Dir.pwd
1493
+ [task_name, deps]
1494
+ end
1495
+
1496
+ # If a rule can be found that matches the task name, enhance the
1497
+ # task with the prerequisites and actions from the rule. Set the
1498
+ # source attribute of the task appropriately for the rule. Return
1499
+ # the enhanced task or nil of no rule was found.
1500
+ def enhance_with_matching_rule(task_name, level=0)
1501
+ fail Rake::RuleRecursionOverflowError,
1502
+ "Rule Recursion Too Deep" if level >= 16
1503
+ @rules.each do |pattern, extensions, block|
1504
+ if md = pattern.match(task_name)
1505
+ task = attempt_rule(task_name, extensions, block, level)
1506
+ return task if task
1507
+ end
1508
+ end
1509
+ nil
1510
+ rescue Rake::RuleRecursionOverflowError => ex
1511
+ ex.add_target(task_name)
1512
+ fail ex
1513
+ end
1514
+
1515
+ # List of all defined tasks in this application.
1516
+ def tasks
1517
+ @tasks.values.sort_by { |t| t.name }
1518
+ end
1519
+
1520
+ # Clear all tasks in this application.
1521
+ def clear
1522
+ @tasks.clear
1523
+ @rules.clear
933
1524
  end
934
- puts "(in #{Dir.pwd})"
935
- $rakefile = @rakefile
936
- load @rakefile
937
- end
938
1525
 
939
- # Collect the list of tasks on the command line. If no tasks are
940
- # give, return a list containing only the default task.
941
- # Environmental assignments are processed at this time as well.
942
- def collect_tasks
943
- tasks = []
944
- ARGV.each do |arg|
945
- if arg =~ /^(\w+)=(.*)$/
946
- ENV[$1] = $2
1526
+ # Lookup a task, using scope and the scope hints in the task name.
1527
+ # This method performs straight lookups without trying to
1528
+ # synthesize file tasks or rules. Special scope names (e.g. '^')
1529
+ # are recognized. If no scope argument is supplied, use the
1530
+ # current scope. Return nil if the task cannot be found.
1531
+ def lookup(task_name, initial_scope=nil)
1532
+ initial_scope ||= @scope
1533
+ task_name = task_name.to_s
1534
+ if task_name =~ /^rake:/
1535
+ scopes = []
1536
+ task_name = task_name.sub(/^rake:/, '')
1537
+ elsif task_name =~ /^(\^+)/
1538
+ scopes = initial_scope[0, initial_scope.size - $1.size]
1539
+ task_name = task_name.sub(/^(\^+)/, '')
947
1540
  else
948
- tasks << arg
1541
+ scopes = initial_scope
949
1542
  end
1543
+ lookup_in_scope(task_name, scopes)
1544
+ end
1545
+
1546
+ # Lookup the task name
1547
+ def lookup_in_scope(name, scope)
1548
+ n = scope.size
1549
+ while n >= 0
1550
+ tn = (scope[0,n] + [name]).join(':')
1551
+ task = @tasks[tn]
1552
+ return task if task
1553
+ n -= 1
1554
+ end
1555
+ nil
950
1556
  end
951
- tasks.push("default") if tasks.size == 0
952
- tasks
1557
+ private :lookup_in_scope
1558
+
1559
+ # Return the list of scope names currently active in the task
1560
+ # manager.
1561
+ def current_scope
1562
+ @scope.dup
1563
+ end
1564
+
1565
+ # Evaluate the block in a nested namespace named +name+. Create
1566
+ # an anonymous namespace if +name+ is nil.
1567
+ def in_namespace(name)
1568
+ name ||= generate_name
1569
+ @scope.push(name)
1570
+ ns = NameSpace.new(self, @scope)
1571
+ yield(ns)
1572
+ ns
1573
+ ensure
1574
+ @scope.pop
1575
+ end
1576
+
1577
+ private
1578
+
1579
+ # Generate an anonymous namespace name.
1580
+ def generate_name
1581
+ @seed ||= 0
1582
+ @seed += 1
1583
+ "_anon_#{@seed}"
1584
+ end
1585
+
1586
+ # Attempt to create a rule given the list of prerequisites.
1587
+ def attempt_rule(task_name, extensions, block, level)
1588
+ sources = make_sources(task_name, extensions)
1589
+ prereqs = sources.collect { |source|
1590
+ if File.exist?(source) || Rake::Task.task_defined?(source)
1591
+ source
1592
+ elsif parent = enhance_with_matching_rule(sources.first, level+1)
1593
+ parent.name
1594
+ else
1595
+ return nil
1596
+ end
1597
+ }
1598
+ task = FileTask.define_task({task_name => prereqs}, &block)
1599
+ task.sources = prereqs
1600
+ task
1601
+ end
1602
+
1603
+ # Make a list of sources from the list of file name extensions /
1604
+ # translation procs.
1605
+ def make_sources(task_name, extensions)
1606
+ extensions.collect { |ext|
1607
+ case ext
1608
+ when /^%/
1609
+ task_name.pathmap(ext)
1610
+ when %r{/}
1611
+ ext
1612
+ when /^\./
1613
+ task_name.ext(ext)
1614
+ when String
1615
+ ext
1616
+ when Proc
1617
+ ext.call(task_name)
1618
+ else
1619
+ fail "Don't know how to handle rule dependent: #{ext.inspect}"
1620
+ end
1621
+ }.flatten
1622
+ end
1623
+
953
1624
  end
954
1625
 
955
- # Run the +rake+ application.
956
- def run
957
- handle_options
958
- begin
959
- tasks = collect_tasks
960
- load_rakefile
961
- if $show_tasks
962
- display_tasks_and_comments
963
- elsif $show_prereqs
964
- display_prerequisites
965
- else
966
- tasks.each { |task_name| Task[task_name].invoke }
1626
+ ######################################################################
1627
+ # Rake main application object. When invoking +rake+ from the
1628
+ # command line, a Rake::Application object is created and run.
1629
+ #
1630
+ class Application
1631
+ include TaskManager
1632
+
1633
+ # The name of the application (typically 'rake')
1634
+ attr_reader :name
1635
+
1636
+ # The original directory where rake was invoked.
1637
+ attr_reader :original_dir
1638
+
1639
+ # Name of the actual rakefile used.
1640
+ attr_reader :rakefile
1641
+
1642
+ # List of the top level task names (task names from the command line).
1643
+ attr_reader :top_level_tasks
1644
+
1645
+ DEFAULT_RAKEFILES = ['rakefile', 'Rakefile', 'rakefile.rb', 'Rakefile.rb'].freeze
1646
+
1647
+ OPTIONS = [ # :nodoc:
1648
+ ['--dry-run', '-n', GetoptLong::NO_ARGUMENT,
1649
+ "Do a dry run without executing actions."],
1650
+ ['--help', '-H', GetoptLong::NO_ARGUMENT,
1651
+ "Display this help message."],
1652
+ ['--libdir', '-I', GetoptLong::REQUIRED_ARGUMENT,
1653
+ "Include LIBDIR in the search path for required modules."],
1654
+ ['--rakelibdir', '-R', GetoptLong::REQUIRED_ARGUMENT,
1655
+ "Auto-import any .rake files in RAKELIBDIR. (default is 'rakelib')"],
1656
+ ['--nosearch', '-N', GetoptLong::NO_ARGUMENT,
1657
+ "Do not search parent directories for the Rakefile."],
1658
+ ['--prereqs', '-P', GetoptLong::NO_ARGUMENT,
1659
+ "Display the tasks and dependencies, then exit."],
1660
+ ['--quiet', '-q', GetoptLong::NO_ARGUMENT,
1661
+ "Do not log messages to standard output."],
1662
+ ['--rakefile', '-f', GetoptLong::OPTIONAL_ARGUMENT,
1663
+ "Use FILE as the rakefile."],
1664
+ ['--require', '-r', GetoptLong::REQUIRED_ARGUMENT,
1665
+ "Require MODULE before executing rakefile."],
1666
+ ['--silent', '-s', GetoptLong::NO_ARGUMENT,
1667
+ "Like --quiet, but also suppresses the 'in directory' announcement."],
1668
+ ['--tasks', '-T', GetoptLong::OPTIONAL_ARGUMENT,
1669
+ "Display the tasks (matching optional PATTERN) with descriptions, then exit."],
1670
+ ['--trace', '-t', GetoptLong::NO_ARGUMENT,
1671
+ "Turn on invoke/execute tracing, enable full backtrace."],
1672
+ ['--usage', '-h', GetoptLong::NO_ARGUMENT,
1673
+ "Display usage."],
1674
+ ['--verbose', '-v', GetoptLong::NO_ARGUMENT,
1675
+ "Log message to standard output (default)."],
1676
+ ['--version', '-V', GetoptLong::NO_ARGUMENT,
1677
+ "Display the program version."],
1678
+ ['--classic-namespace', '-C', GetoptLong::NO_ARGUMENT,
1679
+ "Put Task and FileTask in the top level namespace"],
1680
+ ]
1681
+
1682
+ # Initialize a Rake::Application object.
1683
+ def initialize
1684
+ super
1685
+ @name = 'rake'
1686
+ @rakefiles = DEFAULT_RAKEFILES.dup
1687
+ @rakefile = nil
1688
+ @pending_imports = []
1689
+ @imported = []
1690
+ @loaders = {}
1691
+ @default_loader = Rake::DefaultLoader.new
1692
+ @original_dir = Dir.pwd
1693
+ @top_level_tasks = []
1694
+ add_loader('rf', DefaultLoader.new)
1695
+ add_loader('rake', DefaultLoader.new)
1696
+ end
1697
+
1698
+ # Run the Rake application. The run method performs the following three steps:
1699
+ #
1700
+ # * Initialize the command line options (+init+).
1701
+ # * Define the tasks (+load_rakefile+).
1702
+ # * Run the top level tasks (+run_tasks+).
1703
+ #
1704
+ # If you wish to build a custom rake command, you should call +init+ on your
1705
+ # application. The define any tasks. Finally, call +top_level+ to run your top
1706
+ # level tasks.
1707
+ def run
1708
+ standard_exception_handling do
1709
+ init
1710
+ load_rakefile
1711
+ top_level
967
1712
  end
968
- rescue Exception => ex
969
- puts "rake aborted!"
970
- puts ex.message
971
- if $trace
972
- puts ex.backtrace.join("\n")
973
- else
974
- puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
1713
+ end
1714
+
1715
+ # Initialize the command line parameters and app name.
1716
+ def init(app_name='rake')
1717
+ standard_exception_handling do
1718
+ @name = app_name
1719
+ handle_options
1720
+ collect_tasks
1721
+ end
1722
+ end
1723
+
1724
+ # Find the rakefile and then load it and any pending imports.
1725
+ def load_rakefile
1726
+ standard_exception_handling do
1727
+ raw_load_rakefile
1728
+ end
1729
+ end
1730
+
1731
+ # Run the top level tasks of a Rake application.
1732
+ def top_level
1733
+ standard_exception_handling do
1734
+ if options.show_tasks
1735
+ display_tasks_and_comments
1736
+ elsif options.show_prereqs
1737
+ display_prerequisites
1738
+ else
1739
+ top_level_tasks.each { |task_name| self[task_name].invoke }
1740
+ end
1741
+ end
1742
+ end
1743
+
1744
+ # Add a loader to handle imported files ending in the extension
1745
+ # +ext+.
1746
+ def add_loader(ext, loader)
1747
+ ext = ".#{ext}" unless ext =~ /^\./
1748
+ @loaders[ext] = loader
1749
+ end
1750
+
1751
+ # Application options from the command line
1752
+ def options
1753
+ @options ||= OpenStruct.new
1754
+ end
1755
+
1756
+ # private ----------------------------------------------------------------
1757
+
1758
+ # Provide standard execption handling for the given block.
1759
+ def standard_exception_handling
1760
+ begin
1761
+ yield
1762
+ rescue SystemExit, GetoptLong::InvalidOption => ex
1763
+ # Exit silently
1764
+ exit(1)
1765
+ rescue Exception => ex
1766
+ # Exit with error message
1767
+ $stderr.puts "rake aborted!"
1768
+ $stderr.puts ex.message
1769
+ if options.trace
1770
+ $stderr.puts ex.backtrace.join("\n")
1771
+ else
1772
+ $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
1773
+ $stderr.puts "(See full trace by running task with --trace)"
1774
+ end
1775
+ exit(1)
1776
+ end
1777
+ end
1778
+
1779
+ # True if one of the files in RAKEFILES is in the current directory.
1780
+ # If a match is found, it is copied into @rakefile.
1781
+ def have_rakefile
1782
+ @rakefiles.each do |fn|
1783
+ if File.exist?(fn) || fn == ''
1784
+ @rakefile = fn
1785
+ return true
1786
+ end
1787
+ end
1788
+ return false
1789
+ end
1790
+
1791
+ # Display the program usage line.
1792
+ def usage
1793
+ puts "rake [-f rakefile] {options} targets..."
1794
+ end
1795
+
1796
+ # Display the rake command line help.
1797
+ def help
1798
+ usage
1799
+ puts
1800
+ puts "Options are ..."
1801
+ puts
1802
+ OPTIONS.sort.each do |long, short, mode, desc|
1803
+ if mode == GetoptLong::REQUIRED_ARGUMENT
1804
+ if desc =~ /\b([A-Z]{2,})\b/
1805
+ long = long + "=#{$1}"
1806
+ end
1807
+ end
1808
+ printf " %-20s (%s)\n", long, short
1809
+ printf " %s\n", desc
1810
+ end
1811
+ end
1812
+
1813
+ # Display the tasks and dependencies.
1814
+ def display_tasks_and_comments
1815
+ displayable_tasks = tasks.select { |t|
1816
+ t.comment && t.name =~ options.show_task_pattern
1817
+ }
1818
+ width = displayable_tasks.collect { |t| t.name.length }.max
1819
+ displayable_tasks.each do |t|
1820
+ printf "#{name} %-#{width}s # %s\n", t.name, t.comment
1821
+ end
1822
+ end
1823
+
1824
+ # Display the tasks and prerequisites
1825
+ def display_prerequisites
1826
+ tasks.each do |t|
1827
+ puts "rake #{t.name}"
1828
+ t.prerequisites.each { |pre| puts " #{pre}" }
1829
+ end
1830
+ end
1831
+
1832
+ # Return a list of the command line options supported by the
1833
+ # program.
1834
+ def command_line_options
1835
+ OPTIONS.collect { |lst| lst[0..-2] }
1836
+ end
1837
+
1838
+ # Do the option defined by +opt+ and +value+.
1839
+ def do_option(opt, value)
1840
+ case opt
1841
+ when '--dry-run'
1842
+ verbose(true)
1843
+ nowrite(true)
1844
+ options.dryrun = true
1845
+ options.trace = true
1846
+ when '--help'
1847
+ help
1848
+ exit
1849
+ when '--libdir'
1850
+ $:.push(value)
1851
+ when '--nosearch'
1852
+ options.nosearch = true
1853
+ when '--prereqs'
1854
+ options.show_prereqs = true
1855
+ when '--quiet'
1856
+ verbose(false)
1857
+ when '--rakefile'
1858
+ @rakefiles.clear
1859
+ @rakefiles << value
1860
+ when '--rakelibdir'
1861
+ options.rakelib = value.split(':')
1862
+ when '--require'
1863
+ begin
1864
+ require value
1865
+ rescue LoadError => ex
1866
+ begin
1867
+ rake_require value
1868
+ rescue LoadError => ex2
1869
+ raise ex
1870
+ end
1871
+ end
1872
+ when '--silent'
1873
+ verbose(false)
1874
+ options.silent = true
1875
+ when '--tasks'
1876
+ options.show_tasks = true
1877
+ options.show_task_pattern = Regexp.new(value || '.')
1878
+ when '--trace'
1879
+ options.trace = true
1880
+ verbose(true)
1881
+ when '--usage'
1882
+ usage
1883
+ exit
1884
+ when '--verbose'
1885
+ verbose(true)
1886
+ when '--version'
1887
+ puts "rake, version #{RAKEVERSION}"
1888
+ exit
1889
+ when '--classic-namespace'
1890
+ require 'rake/classic_namespace'
1891
+ options.classic_namespace = true
1892
+ end
1893
+ end
1894
+
1895
+ # Read and handle the command line options.
1896
+ def handle_options
1897
+ options.rakelib = 'rakelib'
1898
+
1899
+ opts = GetoptLong.new(*command_line_options)
1900
+ opts.each { |opt, value| do_option(opt, value) }
1901
+
1902
+ # If class namespaces are requested, set the global options
1903
+ # according to the values in the options structure.
1904
+ if options.classic_namespace
1905
+ $show_tasks = options.show_tasks
1906
+ $show_prereqs = options.show_prereqs
1907
+ $trace = options.trace
1908
+ $dryrun = options.dryrun
1909
+ $silent = options.silent
1910
+ end
1911
+ end
1912
+
1913
+ # Similar to the regular Ruby +require+ command, but will check
1914
+ # for .rake files in addition to .rb files.
1915
+ def rake_require(file_name, paths=$LOAD_PATH, loaded=$")
1916
+ return false if loaded.include?(file_name)
1917
+ paths.each do |path|
1918
+ fn = file_name + ".rake"
1919
+ full_path = File.join(path, fn)
1920
+ if File.exist?(full_path)
1921
+ load full_path
1922
+ loaded << fn
1923
+ return true
1924
+ end
1925
+ end
1926
+ fail LoadError, "Can't find #{file_name}"
1927
+ end
1928
+
1929
+ def raw_load_rakefile # :nodoc:
1930
+ here = Dir.pwd
1931
+ while ! have_rakefile
1932
+ Dir.chdir("..")
1933
+ if Dir.pwd == here || options.nosearch
1934
+ fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})"
1935
+ end
1936
+ here = Dir.pwd
1937
+ end
1938
+ puts "(in #{Dir.pwd})" unless options.silent
1939
+ $rakefile = @rakefile
1940
+ load File.expand_path(@rakefile) if @rakefile != ''
1941
+ options.rakelib.each do |rlib|
1942
+ Dir["#{rlib}/*.rake"].each do |name| add_import name end
1943
+ end
1944
+ load_imports
1945
+ end
1946
+
1947
+ # Collect the list of tasks on the command line. If no tasks are
1948
+ # given, return a list containing only the default task.
1949
+ # Environmental assignments are processed at this time as well.
1950
+ def collect_tasks
1951
+ @top_level_tasks = []
1952
+ ARGV.each do |arg|
1953
+ if arg =~ /^(\w+)=(.*)$/
1954
+ ENV[$1] = $2
1955
+ else
1956
+ @top_level_tasks << arg
1957
+ end
1958
+ end
1959
+ @top_level_tasks.push("default") if @top_level_tasks.size == 0
1960
+ end
1961
+
1962
+ # Add a file to the list of files to be imported.
1963
+ def add_import(fn)
1964
+ @pending_imports << fn
1965
+ end
1966
+
1967
+ # Load the pending list of imported files.
1968
+ def load_imports
1969
+ while fn = @pending_imports.shift
1970
+ next if @imported.member?(fn)
1971
+ if fn_task = lookup(fn)
1972
+ fn_task.invoke
1973
+ end
1974
+ ext = File.extname(fn)
1975
+ loader = @loaders[ext] || @default_loader
1976
+ loader.load(fn)
1977
+ @imported << fn
975
1978
  end
976
- exit(1)
977
- end
1979
+ end
1980
+
1981
+ # Warn about deprecated use of top level constant names.
1982
+ def const_warning(const_name)
1983
+ @const_warning ||= false
1984
+ if ! @const_warning
1985
+ $stderr.puts %{WARNING: Deprecated reference to top-level constant '#{const_name}' } +
1986
+ %{found at: #{rakefile_location}} # '
1987
+ $stderr.puts %{ Use --classic-namespace on rake command}
1988
+ $stderr.puts %{ or 'require "rake/classic_namespace"' in Rakefile}
1989
+ end
1990
+ @const_warning = true
1991
+ end
1992
+
1993
+ def rakefile_location
1994
+ begin
1995
+ fail
1996
+ rescue RuntimeError => ex
1997
+ ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
1998
+ end
1999
+ end
978
2000
  end
979
2001
  end
980
2002
 
981
- if __FILE__ == $0 then
982
- RakeApp.new.run
2003
+
2004
+ class Module
2005
+ # Rename the original handler to make it available.
2006
+ alias :rake_original_const_missing :const_missing
2007
+
2008
+ # Check for deprecated uses of top level (i.e. in Object) uses of
2009
+ # Rake class names. If someone tries to reference the constant
2010
+ # name, display a warning and return the proper object. Using the
2011
+ # --classic-namespace command line option will define these
2012
+ # constants in Object and avoid this handler.
2013
+ def const_missing(const_name)
2014
+ case const_name
2015
+ when :Task
2016
+ Rake.application.const_warning(const_name)
2017
+ Rake::Task
2018
+ when :FileTask
2019
+ Rake.application.const_warning(const_name)
2020
+ Rake::FileTask
2021
+ when :FileCreationTask
2022
+ Rake.application.const_warning(const_name)
2023
+ Rake::FileCreationTask
2024
+ when :RakeApp
2025
+ Rake.application.const_warning(const_name)
2026
+ Rake::Application
2027
+ else
2028
+ rake_original_const_missing(const_name)
2029
+ end
2030
+ end
983
2031
  end