rake 0.4.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rake might be problematic. Click here for more details.

@@ -0,0 +1,151 @@
1
+ = Why rake?
2
+
3
+ Ok, let me state from the beginning that I never intended to write this
4
+ code. I'm not convinced it is useful, and I'm not convinced anyone
5
+ would even be interested in it. All I can say is that Why's onion truck
6
+ must by been passing through the Ohio valley.
7
+
8
+ What am I talking about? ... A Ruby version of Make.
9
+
10
+ See, I can sense you cringing already, and I agree. The world certainly
11
+ doesn't need yet another reworking of the "make" program. I mean, we
12
+ already have "ant". Isn't that enough?
13
+
14
+ It started yesterday. I was helping a coworker fix a problem in one of
15
+ the Makefiles we use in our project. Not a particularly tough problem,
16
+ but during the course of the conversation I began lamenting some of the
17
+ shortcomings of make. In particular, in one of my makefiles I wanted to
18
+ determine the name of a file dynamically and had to resort to some
19
+ simple scripting (in Ruby) to make it work. "Wouldn't it be nice if you
20
+ could just use Ruby inside a Makefile" I said.
21
+
22
+ My coworker (a recent convert to Ruby) agreed, but wondered what it
23
+ would look like. So I sketched the following on the whiteboard...
24
+
25
+ "What if you could specify the make tasks in Ruby, like this ..."
26
+
27
+ task "build" do
28
+ java_compile(...args, etc ...)
29
+ end
30
+
31
+ "The task function would register "build" as a target to be made,
32
+ and the block would be the action executed whenever the build
33
+ system determined that it was time to do the build target."
34
+
35
+ We agreed that would be cool, but writing make from scratch would be WAY
36
+ too much work. And that was the end of that!
37
+
38
+ ... Except I couldn't get the thought out of my head. What exactly
39
+ would be needed to make the about syntax work as a make file? Hmmm, you
40
+ would need to register the tasks, you need some way of specifying
41
+ dependencies between tasks, and some way of kicking off the process.
42
+ Hey! What if we did ... and fifteen minutes later I had a working
43
+ prototype of Ruby make, complete with dependencies and actions.
44
+
45
+ I showed the code to my coworker and we had a good laugh. It was just
46
+ about a page worth of code that reproduced an amazing amount of the
47
+ functionality of make. We were both truely stunned with the power of
48
+ Ruby.
49
+
50
+ But it didn't do everything make did. In particular, it didn't have
51
+ timestamp based file dependencies (where a file is rebuilt if any of its
52
+ prerequisite files have a later timestamp). Obviously THAT would be a
53
+ pain to add and so Ruby Make would remain an interesting experiment.
54
+
55
+ ... Except as I walked back to my desk, I started thinking about what
56
+ file based dependecies would really need. Rats! I was hooked again,
57
+ and by adding a new class and two new methods, file/timestamp
58
+ dependencies were implemented.
59
+
60
+ Ok, now I was really hooked. Last night (during CSI!) I massaged the
61
+ code and cleaned it up a bit. The result is a bare-bones replacement
62
+ for make in exactly 100 lines of code.
63
+
64
+ For the curious, you can see it at ...
65
+ * doc/proto_rake.rdoc
66
+
67
+ Oh, about the name. When I wrote the example Ruby Make task on my
68
+ whiteboard, my coworker exclaimed "Oh! I have the perfect name: Rake ...
69
+ Get it? Ruby-Make. Rake!" He said he envisioned the tasks as leaves
70
+ and Rake would clean them up ... or something like that. Anyways, the
71
+ name stuck.
72
+
73
+ Some quick examples ...
74
+
75
+ A simple task to delete backup files ...
76
+
77
+ task :clean do
78
+ Dir['*~'].each {|fn| rm fn rescue nil}
79
+ end
80
+
81
+ Note that task names are symbols (they are slightly easier to type
82
+ than quoted strings ... but you may use quoted string if you would
83
+ rather). Rake makes the methods of the FileUtils module directly
84
+ available, so we take advantage of the <tt>rm</tt> command. Also note
85
+ the use of "rescue nil" to trap and ignore errors in the <tt>rm</tt>
86
+ command.
87
+
88
+ To run it, just type "rake clean". Rake will automatically find a
89
+ Rakefile in the current directory (or above!) and will invoke the
90
+ targets named on the command line. If there are no targets explicitly
91
+ named, rake will invoke the task "default".
92
+
93
+ Here's another task with dependencies ...
94
+
95
+ task :clobber => [:clean] do
96
+ rm_r "tempdir"
97
+ end
98
+
99
+ Task :clobber depends upon task :clean, so :clean will be run before
100
+ :clobber is executed.
101
+
102
+ Files are specified by using the "file" command. It is similar to the
103
+ task command, except that the task name represents a file, and the task
104
+ will be run only if the file doesn't exist, or if its modification time
105
+ is earlier than any of its prerequisites.
106
+
107
+ Here is a file based dependency that will compile "hello.cc" to
108
+ "hello.o".
109
+
110
+ file "hello.cc"
111
+ file "hello.o" => ["hello.cc"] do |t|
112
+ srcfile = t.name.sub(/\.o$/, ".cc")
113
+ sh %{g++ #{srcfile} -c -o #{t.name}}
114
+ end
115
+
116
+ I normally specify file tasks with string (rather than symbols). Some
117
+ file names can't be represented by symbols. Plus it makes the
118
+ distinction between them more clear to the casual reader.
119
+
120
+ Currently writing a task for each and every file in the project would be
121
+ tedious at best. I envision a set of libraries to make this job
122
+ easier. For instance, perhaps something like this ...
123
+
124
+ require 'rake/ctools'
125
+ Dir['*.c'].each do |fn|
126
+ c_source_file(fn)
127
+ end
128
+
129
+ where "c_source_file" will create all the tasks need to compile all the
130
+ C source files in a directory. Any number of useful libraries could be
131
+ created for rake.
132
+
133
+ That's it. There's no documentation (other than whats in this
134
+ message). Does this sound interesting to anyone? If so, I'll continue
135
+ to clean it up and write it up and publish it on RAA. Otherwise, I'll
136
+ leave it as an interesting excerise and a tribute to the power of Ruby.
137
+
138
+ Why /might/ rake be interesting to Ruby programmers. I don't know,
139
+ perhaps ...
140
+
141
+ * No weird make syntax (only weird Ruby syntax :-)
142
+ * No need to edit or read XML (a la ant)
143
+ * Platform independent build scripts.
144
+ * Will run anywhere Ruby exists, so no need to have "make" installed.
145
+ If you stay away from the "sys" command and use things like
146
+ 'ftools', you can have a perfectly platform independent
147
+ build script. Also rake is only 100 lines of code, so it can
148
+ easily be packaged along with the rest of your code.
149
+
150
+ So ... Sorry for the long rambling message. Like I said, I never
151
+ intended to write this code at all.
@@ -0,0 +1,88 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ $ruby = CONFIG['ruby_install_name']
8
+
9
+ ##
10
+ # Install a binary file. We patch in on the way through to
11
+ # insert a #! line. If this is a Unix install, we name
12
+ # the command (for example) 'rake' and let the shebang line
13
+ # handle running it. Under windows, we add a '.rb' extension
14
+ # and let file associations to their stuff
15
+ #
16
+
17
+ def installBIN(from, opfile)
18
+
19
+ tmp_dir = nil
20
+ for t in [".", "/tmp", "c:/temp", $bindir]
21
+ stat = File.stat(t) rescue next
22
+ if stat.directory? and stat.writable?
23
+ tmp_dir = t
24
+ break
25
+ end
26
+ end
27
+
28
+ fail "Cannot find a temporary directory" unless tmp_dir
29
+ tmp_file = File.join(tmp_dir, "_tmp")
30
+
31
+ File.open(from) do |ip|
32
+ File.open(tmp_file, "w") do |op|
33
+ ruby = File.join($realbindir, $ruby)
34
+ op.puts "#!#{ruby} -w"
35
+ op.write ip.read
36
+ end
37
+ end
38
+
39
+ opfile += ".rb" if CONFIG["target_os"] =~ /mswin/i
40
+ File::install(tmp_file, File.join($bindir, opfile), 0755, true)
41
+ File::unlink(tmp_file)
42
+ end
43
+
44
+ $sitedir = CONFIG["sitelibdir"]
45
+ unless $sitedir
46
+ version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
47
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
48
+ $sitedir = $:.find {|x| x =~ /site_ruby/}
49
+ if !$sitedir
50
+ $sitedir = File.join($libdir, "site_ruby")
51
+ elsif $sitedir !~ Regexp.quote(version)
52
+ $sitedir = File.join($sitedir, version)
53
+ end
54
+ end
55
+
56
+ $bindir = CONFIG["bindir"]
57
+
58
+ $realbindir = $bindir
59
+
60
+ bindir = CONFIG["bindir"]
61
+ if (destdir = ENV['DESTDIR'])
62
+ $bindir = destdir + $bindir
63
+ $sitedir = destdir + $sitedir
64
+
65
+ File::makedirs($bindir)
66
+ File::makedirs($sitedir)
67
+ end
68
+
69
+ rake_dest = File.join($sitedir, "rake")
70
+ File::makedirs(rake_dest, true)
71
+ File::chmod(0755, rake_dest)
72
+
73
+ # The library files
74
+
75
+ files = Dir.chdir('lib') { Dir['**/*.rb'] }
76
+
77
+ for fn in files
78
+ fn_dir = File.dirname(fn)
79
+ target_dir = File.join($sitedir, fn_dir)
80
+ if ! File.exist?(target_dir)
81
+ File.makedirs(target_dir)
82
+ end
83
+ File::install(File.join('lib', fn), File.join($sitedir, fn), 0644, true)
84
+ end
85
+
86
+ # and the executable
87
+
88
+ installBIN("bin/rake", "rake")
@@ -0,0 +1,983 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # Copyright (c) 2003, 2004 Jim Weirich
5
+ #
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:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
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.
24
+ #++
25
+ #
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.
31
+
32
+ RAKEVERSION = '0.4.8'
33
+
34
+ require 'rbconfig'
35
+ require 'ftools'
36
+ require 'getoptlong'
37
+ require 'fileutils'
38
+
39
+ $last_comment = nil
40
+ $show_tasks = nil
41
+ $show_prereqs = nil
42
+ $trace = nil
43
+ $dryrun = nil
44
+
45
+ ######################################################################
46
+ # A Task is the basic unit of work in a Rakefile. Tasks have
47
+ # associated actions (possibly more than one) and a list of
48
+ # prerequisites. When invoked, a task will first ensure that all of
49
+ # its prerequisites have an opportunity to run and then it will
50
+ # execute its own actions.
51
+ #
52
+ # Tasks are not usually created directly using the new method, but
53
+ # rather use the +file+ and +task+ convenience methods.
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
78
+
79
+ # Enhance a task with prerequisites or actions. Returns self.
80
+ def enhance(deps=nil, &block)
81
+ @prerequisites |= deps if deps
82
+ @actions << block if block_given?
83
+ self
84
+ end
85
+
86
+ # Name of the task.
87
+ def name
88
+ @name.to_s
89
+ end
90
+
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}"
95
+ end
96
+ return if @already_invoked
97
+ @already_invoked = true
98
+ @prerequisites.each { |n| Task[n].invoke }
99
+ execute if needed?
100
+ end
101
+
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
110
+
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) }
116
+ end
117
+
118
+ # Is this task needed?
119
+ def needed?
120
+ true
121
+ end
122
+
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
+ end
128
+
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 = ''
137
+ end
138
+ @comment << $last_comment
139
+ $last_comment = nil
140
+ end
141
+
142
+ # Class Methods ----------------------------------------------------
143
+
144
+ class << self
145
+
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
153
+
154
+ # List of all defined tasks.
155
+ def tasks
156
+ TASKS.keys.sort.collect { |tn| Task[tn] }
157
+ end
158
+
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)
173
+ end
174
+ fail "Don't know how to rake #{task_name}"
175
+ end
176
+
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]
181
+ end
182
+
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)
193
+ 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]
201
+ end
202
+
203
+
204
+ # Lookup a task. Return an existing task if found, otherwise
205
+ # create a task of the current type.
206
+ def lookup(task_name)
207
+ name = task_name.to_s
208
+ TASKS[name] ||= self.new(task_name)
209
+ 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
233
+ end
234
+ nil
235
+ end
236
+
237
+ private
238
+
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 = []
251
+ end
252
+ [task_name, deps]
253
+ end
254
+ end
255
+ end
256
+
257
+
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
273
+ end
274
+
275
+ # Time stamp for file task.
276
+ def timestamp
277
+ File.mtime(name.to_s)
278
+ end
279
+ end
280
+
281
+ ######################################################################
282
+ # Task Definition Functions ...
283
+
284
+ # Declare a basic task.
285
+ #
286
+ # Example:
287
+ # task :clobber => [:clean] do
288
+ # rm_rf "html"
289
+ # end
290
+ #
291
+ def task(args, &block)
292
+ Task.define_task(args, &block)
293
+ end
294
+
295
+
296
+ # Declare a file task.
297
+ #
298
+ # Example:
299
+ # file "config.cfg" => ["config.template"] do
300
+ # open("config.cfg", "w") do |outfile|
301
+ # open("config.template") do |infile|
302
+ # while line = infile.gets
303
+ # outfile.puts line
304
+ # end
305
+ # end
306
+ # end
307
+ # end
308
+ #
309
+ def file(args, &block)
310
+ FileTask.define_task(args, &block)
311
+ end
312
+
313
+ # Declare a set of files tasks to create the given directories on
314
+ # demand.
315
+ #
316
+ # Example:
317
+ # directory "testdata/doc"
318
+ #
319
+ def directory(dir)
320
+ while dir != '.' && dir != '/'
321
+ file dir do |t|
322
+ mkdir_p t.name if ! File.exist?(t.name)
323
+ end
324
+ dir = File.dirname(dir)
325
+ end
326
+ end
327
+
328
+ # Declare a rule for auto-tasks.
329
+ #
330
+ # Example:
331
+ # rule '.o' => '.c' do |t|
332
+ # sh %{cc -o #{t.name} #{t.source}}
333
+ # end
334
+ #
335
+ def rule(args, &block)
336
+ Task.create_rule(args, &block)
337
+ end
338
+
339
+ # Describe the next rake task.
340
+ #
341
+ # Example:
342
+ # desc "Run the Unit Tests"
343
+ # task :test => [:build]
344
+ # runtests
345
+ # end
346
+ #
347
+ def desc(comment)
348
+ $last_comment = comment
349
+ end
350
+
351
+
352
+ ######################################################################
353
+ # This a FileUtils extension that defines several additional commands
354
+ # to be added to the FileUtils utility functions.
355
+ #
356
+ module FileUtils
357
+ RUBY = Config::CONFIG['ruby_install_name']
358
+
359
+ OPT_TABLE['sh'] = %w(noop verbose)
360
+ OPT_TABLE['ruby'] = %w(noop verbose)
361
+
362
+ # Run the system command +cmd+.
363
+ #
364
+ # Example:
365
+ # sh %{ls -ltr}
366
+ #
367
+ def sh(cmd, options={})
368
+ fu_check_options options, :noop, :verbose
369
+ fu_output_message cmd if options[:verbose]
370
+ unless options[:noop]
371
+ system(cmd) or fail "Command Failed: [#{cmd}]"
372
+ end
373
+ end
374
+
375
+ # Run a Ruby interpreter with the given arguments.
376
+ #
377
+ # Example:
378
+ # ruby %{-pe '$_.upcase!' <README}
379
+ #
380
+ def ruby(*args)
381
+ if Hash === args.last
382
+ options = args.pop
383
+ else
384
+ options = {}
385
+ end
386
+ sh "#{RUBY} #{args.join(' ')}", options
387
+ end
388
+
389
+ LN_SUPPORTED = [true]
390
+
391
+ # Attempt to do a normal file link, but fall back to a copy if the
392
+ # link fails.
393
+ def safe_ln(*args)
394
+ unless LN_SUPPORTED[0]
395
+ cp(*args)
396
+ else
397
+ begin
398
+ ln(*args)
399
+ rescue Errno::EOPNOTSUPP
400
+ LN_SUPPORTED[0] = false
401
+ cp(*args)
402
+ end
403
+ end
404
+ end
405
+
406
+ # Split a file path into individual directory names.
407
+ #
408
+ # Example:
409
+ # split_all("a/b/c") => ['a', 'b', 'c']
410
+ #
411
+ def split_all(path)
412
+ head, tail = File.split(path)
413
+ return [tail] if head == '.' || tail == '/'
414
+ return [head, tail] if head == '/'
415
+ return split_all(head) + [tail]
416
+ end
417
+ end
418
+
419
+ ######################################################################
420
+ # RakeFileUtils provides a custom version of the FileUtils methods
421
+ # that respond to the <tt>verbose</tt> and <tt>nowrite</tt> commands.
422
+ #
423
+ module RakeFileUtils
424
+ include FileUtils
425
+
426
+ $fileutils_output = $stderr
427
+ $fileutils_label = ''
428
+ $fileutils_verbose = true
429
+ $fileutils_nowrite = false
430
+
431
+ FileUtils::OPT_TABLE.each do |name, opts|
432
+ next unless opts.include?('verbose')
433
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
434
+ def #{name}( *args )
435
+ super(*fu_merge_option(args,
436
+ :verbose => $fileutils_verbose,
437
+ :noop => $fileutils_nowrite))
438
+ end
439
+ EOS
440
+ end
441
+
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.
445
+ #
446
+ # Examples:
447
+ # verbose # return the current value of the verbose flag
448
+ # verbose(v) # set the verbose flag to _v_.
449
+ # verbose(v) { code } # Execute code with the verbose flag set temporarily to _v_.
450
+ # # Return to the original value when code is done.
451
+ def verbose(value=nil)
452
+ oldvalue = $fileutils_verbose
453
+ $fileutils_verbose = value unless value.nil?
454
+ if block_given?
455
+ begin
456
+ yield
457
+ ensure
458
+ $fileutils_verbose = oldvalue
459
+ end
460
+ end
461
+ $fileutils_verbose
462
+ end
463
+
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.
467
+ #
468
+ # Examples:
469
+ # nowrite # return the current value of the nowrite flag
470
+ # nowrite(v) # set the nowrite flag to _v_.
471
+ # nowrite(v) { code } # Execute code with the nowrite flag set temporarily to _v_.
472
+ # # Return to the original value when code is done.
473
+ def nowrite(value=nil)
474
+ oldvalue = $fileutils_nowrite
475
+ $fileutils_nowrite = value unless value.nil?
476
+ if block_given?
477
+ begin
478
+ yield
479
+ ensure
480
+ $fileutils_nowrite = oldvalue
481
+ end
482
+ end
483
+ oldvalue
484
+ end
485
+
486
+ # Use this function to prevent protentially destructive ruby code
487
+ # from running when the :nowrite flag is set.
488
+ #
489
+ # Example:
490
+ #
491
+ # when_writing("Building Project") do
492
+ # project.build
493
+ # end
494
+ #
495
+ # The following code will build the project under normal conditions.
496
+ # If the nowrite(true) flag is set, then the example will print:
497
+ # DRYRUN: Building Project
498
+ # instead of actually building the project.
499
+ #
500
+ def when_writing(msg=nil)
501
+ if $fileutils_nowrite
502
+ puts "DRYRUN: #{msg}" if msg
503
+ else
504
+ yield
505
+ end
506
+ end
507
+
508
+ # Merge the given options with the default values.
509
+ def fu_merge_option(args, defaults)
510
+ if Hash === args.last
511
+ defaults.update(args.last)
512
+ args.pop
513
+ end
514
+ args.push defaults
515
+ args
516
+ end
517
+ private :fu_merge_option
518
+
519
+ extend self
520
+
521
+ end
522
+
523
+ include RakeFileUtils
524
+
525
+ module Rake
526
+
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
532
+
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
538
+ end
539
+ method_list.each_with_index do |sym, i|
540
+ if sym =~ /^[A-Za-z_]+$/
541
+ name = sym
542
+ 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
+ }
552
+ end
553
+
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.
557
+ #
558
+ # Example:
559
+ # file_list = FileList.new['lib/**/*.rb', 'test/test*.rb']
560
+ #
561
+ # pkg_files = FileList.new['lib/**/*'] do |fl|
562
+ # fl.exclude(/\bCVS\b/)
563
+ # end
564
+ #
565
+ def initialize(*patterns)
566
+ @pending_add = []
567
+ @pending = false
568
+ @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
569
+ @exclude_re = nil
570
+ patterns.each { |pattern| include(pattern) }
571
+ yield self if block_given?
572
+ end
573
+
574
+ # Add file names defined by glob patterns to the file list. If an
575
+ # array is given, add each element of the array.
576
+ #
577
+ # Example:
578
+ # file_list.include("*.java", "*.cfg")
579
+ # file_list.include %w( math.c lib.h *.o )
580
+ #
581
+ def include(*filenames)
582
+ # TODO: check for pending
583
+ filenames.each do |fn| @pending_add << fn end
584
+ @pending = true
585
+ self
586
+ end
587
+ alias :add :include
588
+
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.
592
+ #
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.
597
+ #
598
+ # Examples:
599
+ # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
600
+ # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c']
601
+ #
602
+ # If "a.c" is a file, then ...
603
+ # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
604
+ #
605
+ # If "a.c" is not a file, then ...
606
+ # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
607
+ #
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 }
614
+ end
615
+ self
616
+ end
617
+
618
+
619
+ def clear_exclude
620
+ @exclude_patterns = []
621
+ calculate_exclude_regexp if ! @pending
622
+ end
623
+
624
+ # Resolve all the pending adds now.
625
+ def resolve
626
+ @pending = false
627
+ @pending_add.each do |fn| resolve_add(fn) end
628
+ @pending_add = []
629
+ resolve_exclude
630
+ self
631
+ end
632
+
633
+ def calculate_exclude_regexp
634
+ ignores = []
635
+ @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
644
+ end
645
+ if ignores.empty?
646
+ @exclude_re = /^$/
647
+ else
648
+ re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
649
+ @exclude_re = Regexp.new(re_str)
650
+ end
651
+ end
652
+
653
+ def resolve_add(fn)
654
+ case fn
655
+ when Array
656
+ fn.each { |f| self.resolve_add(f) }
657
+ when %r{[*?]}
658
+ add_matching(fn)
659
+ else
660
+ self << fn
661
+ end
662
+ end
663
+
664
+ 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
676
+ self
677
+ end
678
+
679
+ # Return a new FileList with the results of running +sub+ against
680
+ # each element of the oringal list.
681
+ #
682
+ # Example:
683
+ # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
684
+ #
685
+ def sub(pat, rep)
686
+ inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
687
+ end
688
+
689
+ # Return a new FileList with the results of running +gsub+ against
690
+ # each element of the original list.
691
+ #
692
+ # Example:
693
+ # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
694
+ # => ['lib\\test\\file', 'x\\y']
695
+ #
696
+ def gsub(pat, rep)
697
+ inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) }
698
+ end
699
+
700
+ # Same as +sub+ except that the oringal file list is modified.
701
+ def sub!(pat, rep)
702
+ each_with_index { |fn, i| self[i] = fn.sub(pat,rep) }
703
+ self
704
+ end
705
+
706
+ # Same as +gsub+ except that the original file list is modified.
707
+ def gsub!(pat, rep)
708
+ each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) }
709
+ self
710
+ end
711
+
712
+ # Convert a FileList to a string by joining all elements with a space.
713
+ def to_s
714
+ resolve if @pending
715
+ self.join(' ')
716
+ end
717
+
718
+ # Add matching glob patterns.
719
+ def add_matching(pattern)
720
+ Dir[pattern].each do |fn|
721
+ self << fn unless exclude?(fn)
722
+ end
723
+ end
724
+ private :add_matching
725
+
726
+ # Should the given file name be excluded?
727
+ def exclude?(fn)
728
+ calculate_exclude_regexp unless @exclude_re
729
+ fn =~ @exclude_re
730
+ end
731
+
732
+ DEFAULT_IGNORE_PATTERNS = [
733
+ /(^|[\/\\])CVS([\/\\]|$)/,
734
+ /\.bak$/,
735
+ /~$/,
736
+ /(^|[\/\\])core$/
737
+ ]
738
+ @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
739
+
740
+ class << self
741
+ # Create a new file list including the files listed. Similar to:
742
+ #
743
+ # FileList.new(*args)
744
+ def [](*args)
745
+ new(*args)
746
+ end
747
+
748
+ # Set the ignore patterns back to the default value. The
749
+ # default patterns will ignore files
750
+ # * containing "CVS" in the file path
751
+ # * ending with ".bak"
752
+ # * ending with "~"
753
+ # * named "core"
754
+ #
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.
758
+ def select_default_ignore_patterns
759
+ @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
760
+ end
761
+
762
+ # Clear the ignore patterns.
763
+ def clear_ignore_patterns
764
+ @exclude_patterns = [ /^$/ ]
765
+ end
766
+ end
767
+ end
768
+ end
769
+
770
+ # Alias FileList to be available at the top level.
771
+ FileList = Rake::FileList
772
+
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
814
+
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
823
+ end
824
+ return false
825
+ end
826
+
827
+ # Display the program usage line.
828
+ def usage
829
+ puts "rake [-f rakefile] {options} targets..."
830
+ end
831
+
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
846
+ end
847
+ end
848
+
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
860
+ end
861
+ end
862
+
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}" }
868
+ end
869
+ end
870
+
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
876
+
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}"
916
+ end
917
+ 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
+
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(', ')})"
931
+ end
932
+ here = Dir.pwd
933
+ end
934
+ puts "(in #{Dir.pwd})"
935
+ $rakefile = @rakefile
936
+ load @rakefile
937
+ end
938
+
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
947
+ else
948
+ tasks << arg
949
+ end
950
+ end
951
+ tasks.push("default") if tasks.size == 0
952
+ tasks
953
+ end
954
+
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 }
967
+ 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}/ } || ""
975
+ end
976
+ exit(1)
977
+ end
978
+ end
979
+ end
980
+
981
+ if __FILE__ == $0 then
982
+ RakeApp.new.run
983
+ end