warbler 1.3.0.beta1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/History.txt +9 -0
  2. data/LICENSE.txt +1 -1
  3. data/Manifest.txt +22 -0
  4. data/README.txt +6 -3
  5. data/Rakefile +2 -2
  6. data/bin/warble +1 -1
  7. data/ext/JarMain.java +3 -2
  8. data/ext/WarMain.java +3 -2
  9. data/ext/WarblerJar.java +2 -1
  10. data/ext/WarblerJarService.java +2 -1
  11. data/lib/warbler.rb +1 -1
  12. data/lib/warbler/application.rb +17 -1
  13. data/lib/warbler/config.rb +12 -3
  14. data/lib/warbler/gems.rb +1 -1
  15. data/lib/warbler/jar.rb +17 -12
  16. data/lib/warbler/task.rb +6 -15
  17. data/lib/warbler/templates/bundler.erb +3 -0
  18. data/lib/warbler/templates/jar.erb +5 -0
  19. data/lib/warbler/templates/rack.erb +1 -1
  20. data/lib/warbler/templates/war.erb +1 -0
  21. data/lib/warbler/traits.rb +5 -3
  22. data/lib/warbler/traits/bundler.rb +52 -14
  23. data/lib/warbler/traits/gemspec.rb +6 -11
  24. data/lib/warbler/traits/jar.rb +4 -1
  25. data/lib/warbler/traits/merb.rb +2 -1
  26. data/lib/warbler/traits/nogemspec.rb +2 -1
  27. data/lib/warbler/traits/rack.rb +3 -1
  28. data/lib/warbler/traits/rails.rb +13 -8
  29. data/lib/warbler/traits/war.rb +12 -6
  30. data/lib/warbler/version.rb +2 -2
  31. data/lib/warbler/war.rb +7 -0
  32. data/lib/warbler_jar.jar +0 -0
  33. data/spec/drb_helper.rb +41 -0
  34. data/spec/sample_bundler/Gemfile.lock +10 -0
  35. data/spec/sample_bundler/config.ru +0 -0
  36. data/spec/sample_bundler/vendor/bundle/jruby/1.8/cache/rake-0.8.7.gem +0 -0
  37. data/spec/sample_bundler/vendor/bundle/jruby/1.8/gems/rake-0.8.7/lib/rake.rb +2506 -0
  38. data/spec/sample_bundler/vendor/bundle/jruby/1.8/specifications/rake-0.8.7.gemspec +31 -0
  39. data/spec/sample_bundler/vendor/bundle/ruby/1.8/cache/rake-0.8.7.gem +0 -0
  40. data/spec/sample_bundler/vendor/bundle/ruby/1.8/gems/rake-0.8.7/lib/rake.rb +2506 -0
  41. data/spec/sample_bundler/vendor/bundle/ruby/1.8/specifications/rake-0.8.7.gemspec +30 -0
  42. data/spec/sample_bundler/vendor/bundle/ruby/1.9.1/cache/rake-0.8.7.gem +0 -0
  43. data/spec/sample_bundler/vendor/bundle/ruby/1.9.1/gems/rake-0.8.7/lib/rake.rb +2506 -0
  44. data/spec/sample_bundler/vendor/bundle/ruby/1.9.1/specifications/rake-0.8.7.gemspec +30 -0
  45. data/spec/sample_jar/sample_jar.gemspec +0 -1
  46. data/spec/sample_war/config/environments/production.rb +3 -0
  47. data/spec/spec_helper.rb +32 -5
  48. data/spec/warbler/application_spec.rb +11 -2
  49. data/spec/warbler/bundler_spec.rb +136 -0
  50. data/spec/warbler/config_spec.rb +2 -2
  51. data/spec/warbler/gems_spec.rb +2 -2
  52. data/spec/warbler/jar_spec.rb +63 -108
  53. data/spec/warbler/task_spec.rb +29 -20
  54. data/spec/warbler/traits_spec.rb +3 -2
  55. data/spec/warbler/war_spec.rb +3 -2
  56. data/warble.rb +3 -0
  57. data/web.xml.erb +2 -2
  58. metadata +179 -177
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rake}
5
+ s.version = "0.8.7"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Jim Weirich"]
9
+ s.date = %q{2009-05-14}
10
+ s.default_executable = %q{rake}
11
+ s.description = %q{Rake is a Make-like program implemented in Ruby. Tasks and dependencies are specified in standard Ruby syntax.}
12
+ s.email = %q{jim@weirichhouse.org}
13
+ s.executables = ["rake"]
14
+ s.extra_rdoc_files = ["README", "MIT-LICENSE", "TODO", "CHANGES", "doc/command_line_usage.rdoc", "doc/glossary.rdoc", "doc/proto_rake.rdoc", "doc/rakefile.rdoc", "doc/rational.rdoc", "doc/release_notes/rake-0.4.14.rdoc", "doc/release_notes/rake-0.4.15.rdoc", "doc/release_notes/rake-0.5.0.rdoc", "doc/release_notes/rake-0.5.3.rdoc", "doc/release_notes/rake-0.5.4.rdoc", "doc/release_notes/rake-0.6.0.rdoc", "doc/release_notes/rake-0.7.0.rdoc", "doc/release_notes/rake-0.7.1.rdoc", "doc/release_notes/rake-0.7.2.rdoc", "doc/release_notes/rake-0.7.3.rdoc", "doc/release_notes/rake-0.8.0.rdoc", "doc/release_notes/rake-0.8.2.rdoc", "doc/release_notes/rake-0.8.3.rdoc", "doc/release_notes/rake-0.8.4.rdoc", "doc/release_notes/rake-0.8.5.rdoc", "doc/release_notes/rake-0.8.6.rdoc", "doc/release_notes/rake-0.8.7.rdoc"]
15
+ s.files = ["install.rb", "CHANGES", "MIT-LICENSE", "Rakefile", "README", "TODO", "bin/rake", "lib/rake/alt_system.rb", "lib/rake/classic_namespace.rb", "lib/rake/clean.rb", "lib/rake/contrib/compositepublisher.rb", "lib/rake/contrib/ftptools.rb", "lib/rake/contrib/publisher.rb", "lib/rake/contrib/rubyforgepublisher.rb", "lib/rake/contrib/sshpublisher.rb", "lib/rake/contrib/sys.rb", "lib/rake/gempackagetask.rb", "lib/rake/loaders/makefile.rb", "lib/rake/packagetask.rb", "lib/rake/rake_test_loader.rb", "lib/rake/rdoctask.rb", "lib/rake/ruby182_test_unit_fix.rb", "lib/rake/runtest.rb", "lib/rake/tasklib.rb", "lib/rake/testtask.rb", "lib/rake/win32.rb", "lib/rake.rb", "test/capture_stdout.rb", "test/check_expansion.rb", "test/check_no_expansion.rb", "test/contrib/test_sys.rb", "test/data/rakelib/test1.rb", "test/data/rbext/rakefile.rb", "test/filecreation.rb", "test/functional.rb", "test/in_environment.rb", "test/rake_test_setup.rb", "test/reqfile.rb", "test/reqfile2.rb", "test/session_functional.rb", "test/shellcommand.rb", "test/test_application.rb", "test/test_clean.rb", "test/test_definitions.rb", "test/test_earlytime.rb", "test/test_extension.rb", "test/test_file_creation_task.rb", "test/test_file_task.rb", "test/test_filelist.rb", "test/test_fileutils.rb", "test/test_ftp.rb", "test/test_invocation_chain.rb", "test/test_makefile_loader.rb", "test/test_multitask.rb", "test/test_namespace.rb", "test/test_package_task.rb", "test/test_pathmap.rb", "test/test_pseudo_status.rb", "test/test_rake.rb", "test/test_rdoc_task.rb", "test/test_require.rb", "test/test_rules.rb", "test/test_task_arguments.rb", "test/test_task_manager.rb", "test/test_tasklib.rb", "test/test_tasks.rb", "test/test_test_task.rb", "test/test_top_level_functions.rb", "test/test_win32.rb", "test/data/imports/deps.mf", "test/data/sample.mf", "test/data/chains/Rakefile", "test/data/default/Rakefile", "test/data/dryrun/Rakefile", "test/data/file_creation_task/Rakefile", "test/data/imports/Rakefile", "test/data/multidesc/Rakefile", "test/data/namespace/Rakefile", "test/data/statusreturn/Rakefile", "test/data/unittest/Rakefile", "test/data/unittest/subdir", "doc/command_line_usage.rdoc", "doc/example", "doc/example/a.c", "doc/example/b.c", "doc/example/main.c", "doc/example/Rakefile1", "doc/example/Rakefile2", "doc/glossary.rdoc", "doc/jamis.rb", "doc/proto_rake.rdoc", "doc/rake.1.gz", "doc/rakefile.rdoc", "doc/rational.rdoc", "doc/release_notes", "doc/release_notes/rake-0.4.14.rdoc", "doc/release_notes/rake-0.4.15.rdoc", "doc/release_notes/rake-0.5.0.rdoc", "doc/release_notes/rake-0.5.3.rdoc", "doc/release_notes/rake-0.5.4.rdoc", "doc/release_notes/rake-0.6.0.rdoc", "doc/release_notes/rake-0.7.0.rdoc", "doc/release_notes/rake-0.7.1.rdoc", "doc/release_notes/rake-0.7.2.rdoc", "doc/release_notes/rake-0.7.3.rdoc", "doc/release_notes/rake-0.8.0.rdoc", "doc/release_notes/rake-0.8.2.rdoc", "doc/release_notes/rake-0.8.3.rdoc", "doc/release_notes/rake-0.8.4.rdoc", "doc/release_notes/rake-0.8.5.rdoc", "doc/release_notes/rake-0.8.6.rdoc", "doc/release_notes/rake-0.8.7.rdoc"]
16
+ s.homepage = %q{http://rake.rubyforge.org}
17
+ s.rdoc_options = ["--line-numbers", "--main", "README", "--title", "Rake -- Ruby Make"]
18
+ s.require_paths = ["lib"]
19
+ s.rubyforge_project = %q{rake}
20
+ s.rubygems_version = %q{1.5.1}
21
+ s.summary = %q{Ruby based make-like utility.}
22
+
23
+ if s.respond_to? :specification_version then
24
+ s.specification_version = 2
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ end
@@ -0,0 +1,2506 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+
5
+ # Copyright 2003, 2004, 2005, 2006, 2007, 2008 by Jim Weirich (jim@weirichhouse.org)
6
+ #
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
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
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
+ #++
25
+ #
26
+ # = Rake -- Ruby Make
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
+
32
+ RAKEVERSION = '0.8.7'
33
+
34
+ require 'rbconfig'
35
+ require 'fileutils'
36
+ require 'singleton'
37
+ require 'monitor'
38
+ require 'optparse'
39
+ require 'ostruct'
40
+
41
+ require 'rake/win32'
42
+
43
+ $trace = false
44
+
45
+ ######################################################################
46
+ # Rake extensions to Module.
47
+ #
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
72
+
73
+
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
92
+
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
139
+
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
236
+ end
237
+ end # class String
238
+
239
+ ##############################################################################
240
+ module Rake
241
+
242
+ # Errors -----------------------------------------------------------
243
+
244
+ # Error indicating an ill-formed task declaration.
245
+ class TaskArgumentError < ArgumentError
246
+ end
247
+
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
257
+ end
258
+
259
+ def message
260
+ super + ": [" + @targets.reverse.join(' => ') + "]"
261
+ end
262
+ end
263
+
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
+
283
+ end
284
+
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
307
+ end
308
+
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
328
+ end
329
+
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
401
+ end
402
+ end
403
+
404
+ EMPTY_TASK_ARGS = TaskArguments.new([], [])
405
+
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
488
+
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.
562
+ def clear
563
+ clear_prerequisites
564
+ clear_actions
565
+ self
566
+ end
567
+
568
+ # Clear the existing prerequisites of a rake task.
569
+ def clear_prerequisites
570
+ prerequisites.clear
571
+ self
572
+ end
573
+
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
626
+ end
627
+ if application.options.trace
628
+ puts "** Execute #{name}"
629
+ end
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
638
+ end
639
+ end
640
+
641
+ # Is this task needed?
642
+ def needed?
643
+ true
644
+ end
645
+
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
650
+ end
651
+
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?
658
+ end
659
+
660
+ # Writing to the comment attribute is the same as adding a description.
661
+ def comment=(description)
662
+ add_description(description)
663
+ end
664
+
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
678
+ end
679
+ end
680
+ private :add_comment
681
+
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)
778
+ else
779
+ Rake::EARLY
780
+ end
781
+ end
782
+
783
+ private
784
+
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
813
+
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
819
+ end
820
+
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
833
+ end
834
+ end # module Rake
835
+
836
+ ## ###########################################################################
837
+ # Task Definition Functions ...
838
+
839
+ # Declare a basic task.
840
+ #
841
+ # Example:
842
+ # task :clobber => [:clean] do
843
+ # rm_rf "html"
844
+ # end
845
+ #
846
+ def task(*args, &block)
847
+ Rake::Task.define_task(*args, &block)
848
+ end
849
+
850
+
851
+ # Declare a file task.
852
+ #
853
+ # Example:
854
+ # file "config.cfg" => ["config.template"] do
855
+ # open("config.cfg", "w") do |outfile|
856
+ # open("config.template") do |infile|
857
+ # while line = infile.gets
858
+ # outfile.puts line
859
+ # end
860
+ # end
861
+ # end
862
+ # end
863
+ #
864
+ def file(*args, &block)
865
+ Rake::FileTask.define_task(*args, &block)
866
+ end
867
+
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.
875
+ #
876
+ # Example:
877
+ # directory "testdata/doc"
878
+ #
879
+ def directory(dir)
880
+ Rake.each_dir_parent(dir) do |d|
881
+ file_create d do |t|
882
+ mkdir_p t.name if ! File.exist?(t.name)
883
+ end
884
+ end
885
+ end
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
+
913
+ # Declare a rule for auto-tasks.
914
+ #
915
+ # Example:
916
+ # rule '.o' => '.c' do |t|
917
+ # sh %{cc -o #{t.name} #{t.source}}
918
+ # end
919
+ #
920
+ def rule(*args, &block)
921
+ Rake::Task.create_rule(*args, &block)
922
+ end
923
+
924
+ # Describe the next rake task.
925
+ #
926
+ # Example:
927
+ # desc "Run the Unit Tests"
928
+ # task :test => [:build]
929
+ # runtests
930
+ # end
931
+ #
932
+ def desc(description)
933
+ Rake.application.last_description = description
934
+ end
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
954
+
955
+ #############################################################################
956
+ # This a FileUtils extension that defines several additional commands to be
957
+ # added to the FileUtils utility functions.
958
+ #
959
+ module FileUtils
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, '"\&"')
968
+
969
+ OPT_TABLE['sh'] = %w(noop verbose)
970
+ OPT_TABLE['ruby'] = %w(noop verbose)
971
+
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).
975
+ #
976
+ # Example:
977
+ # sh %{ls -ltr}
978
+ #
979
+ # sh 'ls', 'file with spaces'
980
+ #
981
+ # # check exit status after command runs
982
+ # sh %{grep pattern file} do |ok, res|
983
+ # if ! ok
984
+ # puts "pattern not found (status = #{res.exitstatus})"
985
+ # end
986
+ # end
987
+ #
988
+ def sh(*cmd, &block)
989
+ options = (Hash === cmd.last) ? cmd.pop : {}
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
994
+ block = lambda { |ok, status|
995
+ ok or fail "Command failed with status (#{status.exitstatus}): [#{show_command}]"
996
+ }
997
+ end
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)
1011
+ end
1012
+ end
1013
+
1014
+ def rake_system(*cmd)
1015
+ Rake::AltSystem.system(*cmd)
1016
+ end
1017
+ private :rake_system
1018
+
1019
+ # Run a Ruby interpreter with the given arguments.
1020
+ #
1021
+ # Example:
1022
+ # ruby %{-pe '$_.upcase!' <README}
1023
+ #
1024
+ def ruby(*args,&block)
1025
+ options = (Hash === args.last) ? args.pop : {}
1026
+ if args.length > 1 then
1027
+ sh(*([RUBY] + args + [options]), &block)
1028
+ else
1029
+ sh("#{RUBY} #{args.first}", options, &block)
1030
+ end
1031
+ end
1032
+
1033
+ LN_SUPPORTED = [true]
1034
+
1035
+ # Attempt to do a normal file link, but fall back to a copy if the link
1036
+ # fails.
1037
+ def safe_ln(*args)
1038
+ unless LN_SUPPORTED[0]
1039
+ cp(*args)
1040
+ else
1041
+ begin
1042
+ ln(*args)
1043
+ rescue StandardError, NotImplementedError => ex
1044
+ LN_SUPPORTED[0] = false
1045
+ cp(*args)
1046
+ end
1047
+ end
1048
+ end
1049
+
1050
+ # Split a file path into individual directory names.
1051
+ #
1052
+ # Example:
1053
+ # split_all("a/b/c") => ['a', 'b', 'c']
1054
+ #
1055
+ def split_all(path)
1056
+ head, tail = File.split(path)
1057
+ return [tail] if head == '.' || tail == '/'
1058
+ return [head, tail] if head == '/'
1059
+ return split_all(head) + [tail]
1060
+ end
1061
+ end
1062
+
1063
+ #############################################################################
1064
+ # RakeFileUtils provides a custom version of the FileUtils methods that
1065
+ # respond to the <tt>verbose</tt> and <tt>nowrite</tt> commands.
1066
+ #
1067
+ module RakeFileUtils
1068
+ include FileUtils
1069
+
1070
+ class << self
1071
+ attr_accessor :verbose_flag, :nowrite_flag
1072
+ end
1073
+ RakeFileUtils.verbose_flag = :default
1074
+ RakeFileUtils.nowrite_flag = false
1075
+
1076
+ $fileutils_verbose = true
1077
+ $fileutils_nowrite = false
1078
+
1079
+ FileUtils::OPT_TABLE.each do |name, opts|
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?
1089
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1090
+ def #{name}( *args, &block )
1091
+ super(
1092
+ *rake_merge_option(args,
1093
+ #{default_options.join(', ')}
1094
+ ), &block)
1095
+ end
1096
+ EOS
1097
+ end
1098
+
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.
1101
+ #
1102
+ # Examples:
1103
+ # verbose # return the current value of the verbose flag
1104
+ # verbose(v) # set the verbose flag to _v_.
1105
+ # verbose(v) { code } # Execute code with the verbose flag set temporarily to _v_.
1106
+ # # Return to the original value when code is done.
1107
+ def verbose(value=nil)
1108
+ oldvalue = RakeFileUtils.verbose_flag
1109
+ RakeFileUtils.verbose_flag = value unless value.nil?
1110
+ if block_given?
1111
+ begin
1112
+ yield
1113
+ ensure
1114
+ RakeFileUtils.verbose_flag = oldvalue
1115
+ end
1116
+ end
1117
+ RakeFileUtils.verbose_flag
1118
+ end
1119
+
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.
1122
+ #
1123
+ # Examples:
1124
+ # nowrite # return the current value of the nowrite flag
1125
+ # nowrite(v) # set the nowrite flag to _v_.
1126
+ # nowrite(v) { code } # Execute code with the nowrite flag set temporarily to _v_.
1127
+ # # Return to the original value when code is done.
1128
+ def nowrite(value=nil)
1129
+ oldvalue = RakeFileUtils.nowrite_flag
1130
+ RakeFileUtils.nowrite_flag = value unless value.nil?
1131
+ if block_given?
1132
+ begin
1133
+ yield
1134
+ ensure
1135
+ RakeFileUtils.nowrite_flag = oldvalue
1136
+ end
1137
+ end
1138
+ oldvalue
1139
+ end
1140
+
1141
+ # Use this function to prevent protentially destructive ruby code from
1142
+ # running when the :nowrite flag is set.
1143
+ #
1144
+ # Example:
1145
+ #
1146
+ # when_writing("Building Project") do
1147
+ # project.build
1148
+ # end
1149
+ #
1150
+ # The following code will build the project under normal conditions. If the
1151
+ # nowrite(true) flag is set, then the example will print:
1152
+ # DRYRUN: Building Project
1153
+ # instead of actually building the project.
1154
+ #
1155
+ def when_writing(msg=nil)
1156
+ if RakeFileUtils.nowrite_flag
1157
+ puts "DRYRUN: #{msg}" if msg
1158
+ else
1159
+ yield
1160
+ end
1161
+ end
1162
+
1163
+ # Merge the given options with the default values.
1164
+ def rake_merge_option(args, defaults)
1165
+ if Hash === args.last
1166
+ defaults.update(args.last)
1167
+ args.pop
1168
+ end
1169
+ args.push defaults
1170
+ args
1171
+ end
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
1190
+
1191
+ extend self
1192
+ end
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
+
1199
+ include RakeFileUtils
1200
+ private(*FileUtils.instance_methods(false))
1201
+ private(*RakeFileUtils.instance_methods(false))
1202
+
1203
+ ######################################################################
1204
+ module Rake
1205
+
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.
1213
+ #
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
1221
+
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
1272
+ else
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
1282
+ end
1283
+
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.
1287
+ #
1288
+ # Example:
1289
+ # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb')
1290
+ #
1291
+ # pkg_files = FileList.new('lib/**/*') do |fl|
1292
+ # fl.exclude(/\bCVS\b/)
1293
+ # end
1294
+ #
1295
+ def initialize(*patterns)
1296
+ @pending_add = []
1297
+ @pending = false
1298
+ @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
1299
+ @exclude_procs = DEFAULT_IGNORE_PROCS.dup
1300
+ @exclude_re = nil
1301
+ @items = []
1302
+ patterns.each { |pattern| include(pattern) }
1303
+ yield self if block_given?
1304
+ end
1305
+
1306
+ # Add file names defined by glob patterns to the file list. If an array
1307
+ # is given, add each element of the array.
1308
+ #
1309
+ # Example:
1310
+ # file_list.include("*.java", "*.cfg")
1311
+ # file_list.include %w( math.c lib.h *.o )
1312
+ #
1313
+ def include(*filenames)
1314
+ # TODO: check for pending
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
1322
+ @pending = true
1323
+ self
1324
+ end
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.
1331
+ #
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.
1336
+ #
1337
+ # Examples:
1338
+ # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
1339
+ # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c']
1340
+ #
1341
+ # If "a.c" is a file, then ...
1342
+ # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
1343
+ #
1344
+ # If "a.c" is not a file, then ...
1345
+ # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
1346
+ #
1347
+ def exclude(*patterns, &block)
1348
+ patterns.each do |pat|
1349
+ @exclude_patterns << pat
1350
+ end
1351
+ if block_given?
1352
+ @exclude_procs << block
1353
+ end
1354
+ resolve_exclude if ! @pending
1355
+ self
1356
+ end
1357
+
1358
+
1359
+ # Clear all the exclude patterns so that we exclude nothing.
1360
+ def clear_exclude
1361
+ @exclude_patterns = []
1362
+ @exclude_procs = []
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
1398
+ end
1399
+
1400
+ # Resolve all the pending adds now.
1401
+ def resolve
1402
+ if @pending
1403
+ @pending = false
1404
+ @pending_add.each do |fn| resolve_add(fn) end
1405
+ @pending_add = []
1406
+ resolve_exclude
1407
+ end
1408
+ self
1409
+ end
1410
+
1411
+ def calculate_exclude_regexp
1412
+ ignores = []
1413
+ @exclude_patterns.each do |pat|
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
1422
+ end
1423
+ if ignores.empty?
1424
+ @exclude_re = /^$/
1425
+ else
1426
+ re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
1427
+ @exclude_re = Regexp.new(re_str)
1428
+ end
1429
+ end
1430
+
1431
+ def resolve_add(fn)
1432
+ case fn
1433
+ when %r{[*?\[\{]}
1434
+ add_matching(fn)
1435
+ else
1436
+ self << fn
1437
+ end
1438
+ end
1439
+ private :resolve_add
1440
+
1441
+ def resolve_exclude
1442
+ calculate_exclude_regexp
1443
+ reject! { |fn| exclude?(fn) }
1444
+ self
1445
+ end
1446
+ private :resolve_exclude
1447
+
1448
+ # Return a new FileList with the results of running +sub+ against each
1449
+ # element of the oringal list.
1450
+ #
1451
+ # Example:
1452
+ # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
1453
+ #
1454
+ def sub(pat, rep)
1455
+ inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
1456
+ end
1457
+
1458
+ # Return a new FileList with the results of running +gsub+ against each
1459
+ # element of the original list.
1460
+ #
1461
+ # Example:
1462
+ # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
1463
+ # => ['lib\\test\\file', 'x\\y']
1464
+ #
1465
+ def gsub(pat, rep)
1466
+ inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) }
1467
+ end
1468
+
1469
+ # Same as +sub+ except that the oringal file list is modified.
1470
+ def sub!(pat, rep)
1471
+ each_with_index { |fn, i| self[i] = fn.sub(pat,rep) }
1472
+ self
1473
+ end
1474
+
1475
+ # Same as +gsub+ except that the original file list is modified.
1476
+ def gsub!(pat, rep)
1477
+ each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) }
1478
+ self
1479
+ end
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
+
1549
+ # Convert a FileList to a string by joining all elements with a space.
1550
+ def to_s
1551
+ resolve
1552
+ self.join(' ')
1553
+ end
1554
+
1555
+ # Add matching glob patterns.
1556
+ def add_matching(pattern)
1557
+ Dir[pattern].each do |fn|
1558
+ self << fn unless exclude?(fn)
1559
+ end
1560
+ end
1561
+ private :add_matching
1562
+
1563
+ # Should the given file name be excluded?
1564
+ def exclude?(fn)
1565
+ calculate_exclude_regexp unless @exclude_re
1566
+ fn =~ @exclude_re || @exclude_procs.any? { |p| p.call(fn) }
1567
+ end
1568
+
1569
+ DEFAULT_IGNORE_PATTERNS = [
1570
+ /(^|[\/\\])CVS([\/\\]|$)/,
1571
+ /(^|[\/\\])\.svn([\/\\]|$)/,
1572
+ /\.bak$/,
1573
+ /~$/
1574
+ ]
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
1584
+
1585
+ class << self
1586
+ # Create a new file list including the files listed. Similar to:
1587
+ #
1588
+ # FileList.new(*args)
1589
+ def [](*args)
1590
+ new(*args)
1591
+ end
1592
+ end
1593
+ end # FileList
1594
+ end
1595
+
1596
+ module Rake
1597
+ class << self
1598
+
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)
1606
+ end
1607
+ end
1608
+ end
1609
+ end # module Rake
1610
+
1611
+ # Alias FileList to be available at the top level.
1612
+ FileList = Rake::FileList
1613
+
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.
1643
+ #
1644
+ class Time
1645
+ alias rake_original_time_compare :<=>
1646
+ def <=>(other)
1647
+ if Rake::EarlyTime === other
1648
+ - other.<=>(self)
1649
+ else
1650
+ rake_original_time_compare(other)
1651
+ end
1652
+ end
1653
+ end # class Time
1654
+
1655
+ module Rake
1656
+
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)
1744
+ end
1745
+ end
1746
+
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
1760
+ else
1761
+ arg_names = args
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
+ }
1829
+ end
1830
+
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(/^(\^+)/, '')
1851
+ else
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
2018
+ end
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*/)
2058
+ else
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
2099
+ 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
2475
+ end
2476
+ end
2477
+
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
2506
+ end