rant 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/NEWS +12 -0
  2. data/README +3 -3
  3. data/Rantfile +7 -1
  4. data/doc/Untitled-1~ +7 -0
  5. data/doc/command.rdoc +1 -1
  6. data/doc/csharp.rdoc +49 -72
  7. data/doc/csharp.rdoc~ +37 -0
  8. data/doc/csharp_deprecated.rdoc +73 -0
  9. data/doc/homepage/index.html +5 -8
  10. data/doc/homepage/index.html~ +134 -0
  11. data/doc/sys.rdoc +14 -0
  12. data/lib/rant/coregen.rb~ +262 -0
  13. data/lib/rant/csharp/base_compiler_adapter.rb +125 -0
  14. data/lib/rant/csharp/base_compiler_adapter.rb~ +105 -0
  15. data/lib/rant/csharp/compiler_adapter_factory.rb +40 -0
  16. data/lib/rant/csharp/compiler_adapter_factory.rb~ +39 -0
  17. data/lib/rant/csharp/csc_compiler.rb +15 -0
  18. data/lib/rant/csharp/csc_compiler.rb~ +39 -0
  19. data/lib/rant/csharp/gmcs_compiler.rb +9 -0
  20. data/lib/rant/csharp/gmcs_compiler.rb~ +10 -0
  21. data/lib/rant/csharp/mcs_compiler.rb +13 -0
  22. data/lib/rant/csharp/mcs_compiler.rb~ +40 -0
  23. data/lib/rant/csharp/msc_compiler.rb~ +39 -0
  24. data/lib/rant/import/csharp.rb +48 -0
  25. data/lib/rant/import/csharp.rb~ +58 -0
  26. data/lib/rant/import/nunittest.rb +32 -0
  27. data/lib/rant/import/resgen.rb +21 -0
  28. data/lib/rant/import/resgen.rb~ +20 -0
  29. data/lib/rant/init.rb +1 -1
  30. data/lib/rant/rantlib.rb +1 -0
  31. data/lib/rant/rantlib.rb~ +1376 -0
  32. data/lib/rant/rantsys.rb +6 -0
  33. data/run_import +1 -1
  34. data/run_rant +1 -1
  35. data/test/import/package/test_package.rb~ +628 -0
  36. data/test/rule.rf +4 -0
  37. data/test/test_filetask.rb~ +97 -0
  38. data/test/test_rule.rb +10 -0
  39. data/test/test_sys_methods.rb +22 -0
  40. data/test/units/csharp/test_base_compiler_adapter.rb +107 -0
  41. data/test/units/csharp/test_base_compiler_adapter.rb~ +73 -0
  42. data/test/units/csharp/test_compiler_adapter_factory.rb +66 -0
  43. data/test/units/csharp/test_compiler_adapter_factory.rb~ +66 -0
  44. data/test/units/csharp/test_csc_compiler.rb +23 -0
  45. data/test/units/csharp/test_csc_compiler.rb~ +23 -0
  46. data/test/units/csharp/test_gmsc_compiler.rb +19 -0
  47. data/test/units/csharp/test_gmsc_compiler.rb~ +19 -0
  48. data/test/units/csharp/test_msc_compiler.rb +23 -0
  49. data/test/units/csharp/test_msc_compiler.rb~ +23 -0
  50. data/test/units/csharp_test_helper.rb +6 -0
  51. data/test/units/import/test_csharp.rb +127 -0
  52. data/test/units/import/test_csharp.rb~ +126 -0
  53. data/test/units/import/test_nunittest.rb +122 -0
  54. data/test/units/import/test_resgen.rb +107 -0
  55. data/test/units/import/test_resgen.rb~ +88 -0
  56. metadata +269 -224
@@ -452,6 +452,7 @@ class Rant::RantApp
452
452
  sub.empty? ? @rootdir : File.join(@rootdir, sub)
453
453
  end
454
454
  def abs_path(subdir, fn)
455
+ return fn if Rant::Sys.absolute_path?(fn)
455
456
  path = File.join(@rootdir, subdir, fn)
456
457
  path.gsub!(%r{/+}, "/")
457
458
  path.sub!(%r{/$}, "") if path.length > 1
@@ -0,0 +1,1376 @@
1
+
2
+ # rantlib.rb - The core of Rant.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+
10
+ require 'getoptlong'
11
+ require 'rant/init'
12
+ require 'rant/rantvar'
13
+ require 'rant/rantsys'
14
+ require 'rant/node'
15
+ require 'rant/import/nodes/default' # could be optimized away
16
+ require 'rant/coregen'
17
+
18
+ # There is one problem with executing Rantfiles in a special context:
19
+ # In the top-level execution environment, there are some methods
20
+ # available which are not available to all objects. One example is the
21
+ # +include+ method.
22
+ #
23
+ # To (at least partially) solve this problem, we capture the `main'
24
+ # object here and delegate methods from RantContext#method_missing to
25
+ # this object.
26
+ Rant::MAIN_OBJECT = self
27
+
28
+ class String
29
+ alias sub_ext _rant_sub_ext
30
+ def to_rant_target
31
+ self
32
+ end
33
+ end
34
+
35
+ # This module and its methods don't belong to Rant's public API.
36
+ # For (Rant) internal usage only!
37
+ module Rant::Lib
38
+ # Parses one string (elem) as it occurs in the array
39
+ # which is returned by caller.
40
+ # E.g.:
41
+ # p parse_caller_elem "/usr/local/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'"
42
+ # prints:
43
+ # {:ln=>52, :file=>"/usr/local/lib/ruby/1.8/irb/workspace.rb"}
44
+ #
45
+ # Note: This method splits on the pattern <tt>:(\d+)(:|$)</tt>,
46
+ # assuming anything before is the filename.
47
+ def parse_caller_elem(elem)
48
+ return { :file => "", :ln => 0 } unless elem
49
+ if elem =~ /^(.+):(\d+)(?::|$)/
50
+ { :file => $1, :ln => $2.to_i }
51
+ else
52
+ # should never occur
53
+ $stderr.puts "parse_caller_elem: #{elem.inspect}"
54
+ { :file => elem, :ln => 0 }
55
+ end
56
+
57
+ #parts = elem.split(":")
58
+ #{ :file => parts[0], :ln => parts[1].to_i }
59
+ end
60
+ module_function :parse_caller_elem
61
+ end # module Lib
62
+
63
+ module Rant::Console
64
+ RANT_PREFIX = "rant: "
65
+ ERROR_PREFIX = "[ERROR] "
66
+ WARN_PREFIX = "[WARNING] "
67
+ def msg_prefix
68
+ if defined? @msg_prefix and @msg_prefix
69
+ @msg_prefix
70
+ else
71
+ RANT_PREFIX
72
+ end
73
+ end
74
+ def msg(*text)
75
+ pre = msg_prefix
76
+ $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
77
+ end
78
+ def vmsg(importance, *text)
79
+ msg(*text) if verbose >= importance
80
+ end
81
+ def err_msg(*text)
82
+ pre = msg_prefix + ERROR_PREFIX
83
+ $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
84
+ end
85
+ def warn_msg(*text)
86
+ pre = msg_prefix + WARN_PREFIX
87
+ $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
88
+ end
89
+ def ask_yes_no text
90
+ $stderr.print msg_prefix + text + " [y|n] "
91
+ case $stdin.readline
92
+ when /y|yes/i: true
93
+ when /n|no/i: false
94
+ else
95
+ $stderr.puts(' ' * msg_prefix.length +
96
+ "Please answer with `yes' or `no'")
97
+ ask_yes_no text
98
+ end
99
+ end
100
+ def prompt text
101
+ $stderr.print msg_prefix + text
102
+ input = $stdin.readline
103
+ input ? input.chomp : input
104
+ end
105
+ def option_listing opts
106
+ rs = ""
107
+ opts.each { |lopt, *opt_a|
108
+ if opt_a.size == 2
109
+ # no short option
110
+ mode, desc = opt_a
111
+ else
112
+ sopt, mode, desc = opt_a
113
+ end
114
+ next unless desc # "private" option
115
+ optstr = ""
116
+ arg = nil
117
+ if mode != GetoptLong::NO_ARGUMENT
118
+ if desc =~ /(\b[A-Z_]{2,}\b)/
119
+ arg = $1
120
+ end
121
+ end
122
+ if lopt
123
+ optstr << lopt
124
+ if arg
125
+ optstr << " " << arg
126
+ end
127
+ optstr = optstr.ljust(30)
128
+ end
129
+ if sopt
130
+ optstr << " " unless optstr.empty?
131
+ optstr << sopt
132
+ if arg
133
+ optstr << " " << arg
134
+ end
135
+ end
136
+ rs << " #{optstr}\n"
137
+ rs << " #{desc.split("\n").join("\n ")}\n"
138
+ }
139
+ rs
140
+ end
141
+ extend self
142
+ end # module Rant::Console
143
+
144
+ # The methods in this module are the public interface to Rant that can
145
+ # be used in Rantfiles.
146
+ module RantContext
147
+ include Rant::Generators
148
+
149
+ Env = Rant::Env
150
+ FileList = Rant::FileList
151
+
152
+ # Define a basic task.
153
+ def task(targ, &block)
154
+ rant.task(targ, &block)
155
+ end
156
+
157
+ # Define a file task.
158
+ def file(targ, &block)
159
+ rant.file(targ, &block)
160
+ end
161
+
162
+ # Add code and/or prerequisites to existing task.
163
+ def enhance(targ, &block)
164
+ rant.enhance(targ, &block)
165
+ end
166
+
167
+ def desc(*args)
168
+ rant.desc(*args)
169
+ end
170
+
171
+ def gen(*args, &block)
172
+ rant.gen(*args, &block)
173
+ end
174
+
175
+ def import(*args, &block)
176
+ rant.import(*args, &block)
177
+ end
178
+
179
+ def plugin(*args, &block)
180
+ rant.plugin(*args, &block)
181
+ end
182
+
183
+ # Look in the subdirectories, given by args,
184
+ # for rantfiles.
185
+ def subdirs(*args)
186
+ rant.subdirs(*args)
187
+ end
188
+
189
+ def source(opt, rantfile = nil)
190
+ rant.source(opt, rantfile)
191
+ end
192
+
193
+ def sys(*args, &block)
194
+ rant.sys(*args, &block)
195
+ end
196
+
197
+ def var(*args, &block)
198
+ rant.var(*args, &block)
199
+ end
200
+
201
+ def make(*args, &block)
202
+ rant.make(*args, &block)
203
+ end
204
+
205
+ =begin
206
+ # +rac+ stands for "rant compiler"
207
+ def rac
208
+ ch = Rant::Lib.parse_caller_elem caller[0]
209
+ rant.warn_msg(rant.pos_text(ch[:file], ch[:ln]),
210
+ "Method `rac' is deprecated. Use `rant' instead.")
211
+ rant
212
+ end
213
+ =end
214
+ end # module RantContext
215
+
216
+ class RantAppContext
217
+ include RantContext
218
+
219
+ def initialize(app)
220
+ @__rant__ = app
221
+ end
222
+
223
+ def rant
224
+ @__rant__
225
+ end
226
+
227
+ def method_missing(sym, *args)
228
+ # See the documentation for Rant::MAIN_OBJECT why we're doing
229
+ # this...
230
+ # Note also that the +send+ method also invokes private
231
+ # methods, this is very important for our intent.
232
+ Rant::MAIN_OBJECT.send(sym, *args)
233
+ rescue NoMethodError
234
+ raise NameError, "NameError: undefined local " +
235
+ "variable or method `#{sym}' for main:Object", caller
236
+ end
237
+ end
238
+
239
+ module Rant
240
+
241
+ @__rant__ = nil
242
+ class << self
243
+
244
+ # Run a new rant application in the current working directory.
245
+ # This has the same effect as running +rant+ from the
246
+ # commandline. You can give arguments as you would give them
247
+ # on the commandline. If no argument is given, ARGV will be
248
+ # used.
249
+ #
250
+ # This method returns 0 if the rant application was
251
+ # successfull and 1 on failure. So if you need your own rant
252
+ # startscript, it could look like:
253
+ #
254
+ # exit Rant.run
255
+ #
256
+ # This runs rant in the current directory, using the arguments
257
+ # given to your script and the exit code as suggested by the
258
+ # rant application.
259
+ #
260
+ # Or if you want rant to always be quiet with this script,
261
+ # use:
262
+ #
263
+ # exit Rant.run("--quiet", ARGV)
264
+ #
265
+ # Of course, you can invoke rant directly at the bottom of
266
+ # your rantfile, so you can run it directly with ruby.
267
+ def run(first_arg=nil, *other_args)
268
+ other_args = other_args.flatten
269
+ args = first_arg.nil? ? ARGV.dup : ([first_arg] + other_args)
270
+ if rant && !rant.run?
271
+ rant.run(args.flatten)
272
+ else
273
+ @__rant__ = Rant::RantApp.new
274
+ rant.run(args)
275
+ end
276
+ end
277
+
278
+ def rant
279
+ @__rant__
280
+ end
281
+ end
282
+
283
+ end # module Rant
284
+
285
+ class Rant::RantApp
286
+ include Rant::Console
287
+
288
+ class AutoLoadNodeFactory
289
+ def initialize(rant)
290
+ @rant = rant
291
+ end
292
+ def method_missing(sym, *args, &block)
293
+ @rant.import "nodes/default"
294
+ @rant.node_factory.send(sym, *args, &block)
295
+ end
296
+ end
297
+
298
+ # Important: We try to synchronize all tasks referenced indirectly
299
+ # by @rantfiles with the task hash @tasks. The task hash is
300
+ # intended for fast task lookup per task name.
301
+ #
302
+ # All tasks are registered to the system by the +prepare_task+
303
+ # method.
304
+
305
+ # The RantApp class has no own state.
306
+
307
+ OPTIONS = [
308
+ [ "--help", "-h", GetoptLong::NO_ARGUMENT,
309
+ "Print this help and exit." ],
310
+ [ "--version", "-V", GetoptLong::NO_ARGUMENT,
311
+ "Print version of Rant and exit." ],
312
+ [ "--verbose", "-v", GetoptLong::NO_ARGUMENT,
313
+ "Print more messages to stderr." ],
314
+ [ "--quiet", "-q", GetoptLong::NO_ARGUMENT,
315
+ "Don't print commands." ],
316
+ [ "--err-commands", GetoptLong::NO_ARGUMENT,
317
+ "Print failed commands and their exit status." ],
318
+ [ "--directory","-C", GetoptLong::REQUIRED_ARGUMENT,
319
+ "Run rant in DIRECTORY." ],
320
+ [ "--cd-parent","-c", GetoptLong::NO_ARGUMENT,
321
+ "Run rant in parent directory with Rantfile." ],
322
+ [ "--look-up", "-u", GetoptLong::NO_ARGUMENT,
323
+ "Look in parent directories for root Rantfile." ],
324
+ [ "--rantfile", "-f", GetoptLong::REQUIRED_ARGUMENT,
325
+ "Process RANTFILE instead of standard rantfiles.\n" +
326
+ "Multiple files may be specified with this option." ],
327
+ [ "--force-run","-a", GetoptLong::REQUIRED_ARGUMENT,
328
+ "Force rebuild of TARGET and all dependencies." ],
329
+ [ "--dry-run", "-n", GetoptLong::NO_ARGUMENT,
330
+ "Print info instead of actually executing actions." ],
331
+ [ "--tasks", "-T", GetoptLong::NO_ARGUMENT,
332
+ "Show a list of all described tasks and exit." ],
333
+
334
+ # "private" options intended for debugging, testing and
335
+ # internal use. A private option is distuingished from others
336
+ # by having +nil+ as description!
337
+
338
+ [ "--import", "-i", GetoptLong::REQUIRED_ARGUMENT, nil ],
339
+ [ "--stop-after-load", GetoptLong::NO_ARGUMENT, nil ],
340
+ # Print caller to $stderr on abort.
341
+ [ "--trace-abort", GetoptLong::NO_ARGUMENT, nil ],
342
+ ]
343
+
344
+ # Reference project's root directory in task names by preceding
345
+ # them with this character.
346
+ ROOT_DIR_ID = "@"
347
+ ESCAPE_ID = "\\"
348
+
349
+ # Arguments, usually those given on commandline.
350
+ attr_reader :args
351
+ # A list of all Rantfiles used by this app.
352
+ attr_reader :rantfiles
353
+ # A list of target names to be forced (run even
354
+ # if not required). Each of these targets will be removed
355
+ # from this list after the first run.
356
+ #
357
+ # Forced targets will be run before other targets.
358
+ attr_reader :force_targets
359
+ # A list of all registered plugins.
360
+ attr_reader :plugins
361
+ # The context in which Rantfiles are loaded. RantContext methods
362
+ # may be called through this object (e.g. from plugins).
363
+ attr_reader :context
364
+ alias cx context
365
+ # A hash with all tasks. For fast task lookup use this hash with
366
+ # the taskname as key.
367
+ #
368
+ # See also: #resolve, #make
369
+ attr_reader :tasks
370
+ # A list of all imports (code loaded with +import+).
371
+ attr_reader :imports
372
+ # Current subdirectory relative to project's root directory
373
+ # (#rootdir).
374
+ attr_reader :current_subdir
375
+ # List of proc objects used to automatically create required
376
+ # tasks. (Especially used for Rules.)
377
+ #
378
+ # Note: Might change before 1.0
379
+ attr_reader :resolve_hooks
380
+ # Root directory of project. Will be initialized to working
381
+ # directory in #initialize. This is always an absolute path
382
+ # beginning with a <tt>/</tt> and not ending in a slash (unless
383
+ # rootdir is <tt>/</tt>).
384
+ attr_reader :rootdir
385
+
386
+ attr_accessor :node_factory
387
+
388
+ def initialize
389
+ @args = []
390
+ # Rantfiles will be loaded in the context of this object.
391
+ @context = RantAppContext.new(self)
392
+ @sys = ::Rant::SysObject.new(self)
393
+ @rantfiles = []
394
+ @tasks = {}
395
+ @opts = {
396
+ :verbose => 0,
397
+ :quiet => false,
398
+ }
399
+ @rootdir = Dir.pwd # root directory of project
400
+ @arg_rantfiles = [] # rantfiles given in args
401
+ @arg_targets = [] # targets given in args
402
+ @force_targets = [] # targets given with -a option
403
+ @run = false # run method was called at least once
404
+ @done = false # run method was successful
405
+ @plugins = []
406
+ @var = Rant::RantVar::Space.new
407
+ @var.query :ignore, :AutoList, []
408
+ @imports = []
409
+
410
+ @task_desc = nil
411
+ @last_build_subdir = ""
412
+
413
+ @current_subdir = ""
414
+ @resolve_hooks = []
415
+
416
+ @node_factory = AutoLoadNodeFactory.new(self)
417
+ end
418
+
419
+ def [](opt)
420
+ @opts[opt]
421
+ end
422
+
423
+ def []=(opt, val)
424
+ @opts[opt] = val
425
+ end
426
+
427
+ ### support for subdirectories ###################################
428
+ def expand_path(subdir, path)
429
+ case path
430
+ when nil: subdir.dup
431
+ when "": subdir.dup
432
+ when /^@/: path.sub(/^@/, '')
433
+ else
434
+ path = path.sub(/^\\(?=@)/, '')
435
+ if subdir.empty?
436
+ # we are in project's root directory
437
+ path
438
+ else
439
+ File.join(subdir, path)
440
+ end
441
+ end
442
+ end
443
+ def resolve_root_ref(path)
444
+ return File.join(@rootdir, path[1..-1]) if path =~ /^@/
445
+ path.sub(/^\\(?=@)/, '')
446
+ end
447
+ # Returns an absolute path. If path resolves to a directory this
448
+ # method ensures that the returned absolute path doesn't end in a
449
+ # slash.
450
+ def project_to_fs_path(path)
451
+ sub = expand_path(@current_subdir, path)
452
+ sub.empty? ? @rootdir : File.join(@rootdir, sub)
453
+ end
454
+ def abs_path(subdir, fn)
455
+ return fn if Rant::Sys.absolute_path?(fn)
456
+ path = File.join(@rootdir, subdir, fn)
457
+ path.gsub!(%r{/+}, "/")
458
+ path.sub!(%r{/$}, "") if path.length > 1
459
+ path
460
+ end
461
+ def goto(dir)
462
+ goto_project_dir(expand_path(@current_subdir, dir))
463
+ end
464
+ # +dir+ is a path relative to +rootdir+. It has to be a "clean"
465
+ # path string, i.e. it mustn't start with <tt>./</tt>, contain any
466
+ # <tt>..</tt> parent reference and it mustn't have a trailing
467
+ # slash.
468
+ #
469
+ # To go to the root directory, dir has to be an empty string,
470
+ # which is the default value.
471
+ def goto_project_dir(dir='')
472
+ @current_subdir = dir
473
+ abs_path = @current_subdir.empty? ?
474
+ @rootdir : File.join(@rootdir, @current_subdir)
475
+ unless Dir.pwd == abs_path
476
+ Dir.chdir abs_path
477
+ vmsg 1, "in #{abs_path}"
478
+ end
479
+ end
480
+ ##################################################################
481
+
482
+ def run?
483
+ @run
484
+ end
485
+
486
+ def done?
487
+ @done
488
+ end
489
+
490
+ # Run this Rant application with the given arguments. The process
491
+ # working directory after this method returns, will be the same as
492
+ # before invocation.
493
+ #
494
+ # Returns 0 on success and 1 on failure.
495
+ def run(*args)
496
+ @run = true
497
+ @args.concat(args.flatten)
498
+ # remind pwd
499
+ orig_pwd = @rootdir = Dir.pwd
500
+ # Process commandline.
501
+ process_args
502
+ Dir.chdir(@rootdir) rescue abort $!.message
503
+ # read rantfiles, might change @rootdir and Dir.pwd
504
+ load_rantfiles
505
+
506
+ raise Rant::RantDoneException if @opts[:stop_after_load]
507
+
508
+ # Notify plugins before running tasks
509
+ @plugins.each { |plugin| plugin.rant_start }
510
+ if @opts[:tasks]
511
+ show_descriptions
512
+ raise Rant::RantDoneException
513
+ end
514
+ # run tasks
515
+ run_tasks
516
+ raise Rant::RantDoneException
517
+ rescue Rant::RantDoneException
518
+ @done = true
519
+ # Notify plugins
520
+ @plugins.each { |plugin| plugin.rant_done }
521
+ return 0
522
+ rescue Rant::RantAbortException
523
+ $stderr.puts "rant aborted!"
524
+ return 1
525
+ rescue Exception => e
526
+ ch = get_ch_from_backtrace(e.backtrace)
527
+ if ch && !@opts[:trace_abort]
528
+ err_msg(pos_text(ch[:file], ch[:ln]), e.message)
529
+ else
530
+ err_msg e.message, e.backtrace[0..4]
531
+ end
532
+ $stderr.puts "rant aborted!"
533
+ return 1
534
+ ensure
535
+ # TODO: exception handling!
536
+ Dir.chdir @rootdir if test ?d, @rootdir
537
+ hooks = var._get("__at_return__")
538
+ hooks.each { |hook| hook.call } if hooks
539
+ @plugins.each { |plugin| plugin.rant_plugin_stop }
540
+ @plugins.each { |plugin| plugin.rant_quit }
541
+ # restore pwd
542
+ Dir.chdir orig_pwd
543
+ end
544
+
545
+ ###### methods accessible through RantContext ####################
546
+
547
+ def desc(*args)
548
+ if args.empty? || (args.size == 1 && args.first.nil?)
549
+ @task_desc = nil
550
+ else
551
+ @task_desc = args.join("\n")
552
+ end
553
+ end
554
+
555
+ def task(targ, &block)
556
+ prepare_task(targ, block) { |name,pre,blk|
557
+ @node_factory.new_task(self, name, pre, blk)
558
+ }
559
+ end
560
+
561
+ def file(targ, &block)
562
+ prepare_task(targ, block) { |name,pre,blk|
563
+ @node_factory.new_file(self, name, pre, blk)
564
+ }
565
+ end
566
+
567
+ def gen(*args, &block)
568
+ # retrieve caller info
569
+ ch = Rant::Lib::parse_caller_elem(caller[1])
570
+ # validate args
571
+ generator = args.shift
572
+ unless generator.respond_to? :rant_gen
573
+ abort_at(ch,
574
+ "gen: First argument has to be a task-generator.")
575
+ end
576
+ # ask generator to produce a task for this application
577
+ generator.rant_gen(self, ch, args, &block)
578
+ end
579
+
580
+ # Currently ignores block.
581
+ def import(*args, &block)
582
+ ch = Rant::Lib::parse_caller_elem(caller[1])
583
+ if block
584
+ warn_msg pos_text(ch[:file], ch[:ln]),
585
+ "import: ignoring block"
586
+ end
587
+ args.flatten.each { |arg|
588
+ unless String === arg
589
+ abort_at(ch, "import: only strings allowed as arguments")
590
+ end
591
+ unless @imports.include? arg
592
+ unless Rant::CODE_IMPORTS.include? arg
593
+ begin
594
+ vmsg 2, "import #{arg}"
595
+ require "rant/import/#{arg}"
596
+ rescue LoadError => e
597
+ abort_at(ch, "No such import - #{arg}")
598
+ end
599
+ Rant::CODE_IMPORTS << arg.dup
600
+ end
601
+ init_msg = "init_import_#{arg.gsub(/[^\w]/, '__')}"
602
+ Rant.send init_msg, self if Rant.respond_to? init_msg
603
+ @imports << arg.dup
604
+ end
605
+ }
606
+ end
607
+
608
+ def plugin(*args, &block)
609
+ # retrieve caller info
610
+ clr = caller[1]
611
+ ch = Rant::Lib::parse_caller_elem(clr)
612
+ name = nil
613
+ pre = []
614
+ ln = ch[:ln] || 0
615
+ file = ch[:file]
616
+
617
+ pl_name = args.shift
618
+ pl_name = pl_name.to_str if pl_name.respond_to? :to_str
619
+ pl_name = pl_name.to_s if pl_name.is_a? Symbol
620
+ unless pl_name.is_a? String
621
+ abort(pos_text(file, ln),
622
+ "Plugin name has to be a string or symbol.")
623
+ end
624
+ lc_pl_name = pl_name.downcase
625
+ import_name = "plugin/#{lc_pl_name}"
626
+ unless Rant::CODE_IMPORTS.include? import_name
627
+ begin
628
+ require "rant/plugin/#{lc_pl_name}"
629
+ Rant::CODE_IMPORTS << import_name
630
+ rescue LoadError
631
+ abort(pos_text(file, ln),
632
+ "no such plugin library -- #{lc_pl_name}")
633
+ end
634
+ end
635
+ pl_class = nil
636
+ begin
637
+ pl_class = ::Rant::Plugin.const_get(pl_name)
638
+ rescue NameError, ArgumentError
639
+ abort(pos_text(file, ln),
640
+ "no such plugin -- #{pl_name}")
641
+ end
642
+
643
+ plugin = pl_class.rant_plugin_new(self, ch, *args, &block)
644
+ # TODO: check for rant_plugin?
645
+ @plugins << plugin
646
+ vmsg 2, "Plugin `#{plugin.rant_plugin_name}' registered."
647
+ plugin.rant_plugin_init
648
+ # return plugin instance
649
+ plugin
650
+ end
651
+
652
+ # Add block and prerequisites to the task specified by the
653
+ # name given as only key in targ.
654
+ # If there is no task with the given name, generate a warning
655
+ # and a new file task.
656
+ def enhance(targ, &block)
657
+ prepare_task(targ, block) { |name,pre,blk|
658
+ t = resolve(name).last
659
+ if t
660
+ unless t.respond_to? :enhance
661
+ abort("Can't enhance task `#{name}'")
662
+ end
663
+ t.enhance(pre, &blk)
664
+ # Important: return from method, don't break to
665
+ # prepare_task which would add task t again
666
+ return t
667
+ end
668
+ warn_msg "enhance \"#{name}\": no such task",
669
+ "Generating a new file task with the given name."
670
+ @node_factory.new_file(self, name, pre, blk)
671
+ }
672
+ end
673
+
674
+ # Returns the value of the last expression executed in +rantfile+.
675
+ def source(opt, rantfile = nil)
676
+ unless rantfile
677
+ rantfile = opt
678
+ opt = nil
679
+ end
680
+ make_rf = opt != :n && opt != :now
681
+ rf, is_new = rantfile_for_path(rantfile)
682
+ return false unless is_new
683
+ make rantfile if make_rf
684
+ unless File.exist? rf.path
685
+ abort("source: No such file -- #{rantfile}")
686
+ end
687
+
688
+ load_file rf
689
+ end
690
+
691
+ # Search the given directories for Rantfiles.
692
+ def subdirs(*args)
693
+ args.flatten!
694
+ ch = Rant::Lib::parse_caller_elem(caller[1])
695
+ args.each { |arg|
696
+ if arg.respond_to? :to_str
697
+ arg = arg.to_str
698
+ else
699
+ abort_at(ch, "subdirs: arguments must be strings")
700
+ end
701
+ loaded = false
702
+ prev_subdir = @current_subdir
703
+ begin
704
+ #puts "* subdir *",
705
+ # " rootdir: #{rootdir}",
706
+ # " current subdir: #@current_subdir",
707
+ # " pwd: #{Dir.pwd}",
708
+ # " arg: #{arg}"
709
+ goto arg
710
+ if test(?f, Rant::SUB_RANTFILE)
711
+ path = Rant::SUB_RANTFILE
712
+ else
713
+ path = rantfile_in_dir
714
+ end
715
+ if path
716
+ if defined? @initial_subdir and
717
+ @initial_subdir == @current_subdir
718
+ rf, is_new = rantfile_for_path(path, false)
719
+ @rantfiles.unshift rf if is_new
720
+ else
721
+ rf, is_new = rantfile_for_path(path)
722
+ end
723
+ load_file rf if is_new
724
+ elsif !@opts[:no_warn_subdir]
725
+ warn_msg(pos_text(ch[:file], ch[:ln]),
726
+ "subdirs: No Rantfile in subdir `#{arg}'.")
727
+ end
728
+ ensure
729
+ #puts " going back to project dir: #{prev_subdir}"
730
+ goto_project_dir prev_subdir
731
+ end
732
+ }
733
+ rescue SystemCallError => e
734
+ abort_at(ch, "subdirs: " + e.message)
735
+ end
736
+
737
+ def sys(*args, &block)
738
+ args.empty? ? @sys : @sys.sh(*args, &block)
739
+ end
740
+
741
+ # The [] and []= operators may be used to set/get values from this
742
+ # object (like a hash). It is intended to let the different
743
+ # modules, plugins and tasks to communicate with each other.
744
+ def var(*args, &block)
745
+ args.empty? ? @var : @var.query(*args, &block)
746
+ end
747
+ ##################################################################
748
+
749
+ # Pop (remove and return) current pending task description.
750
+ def pop_desc
751
+ td = @task_desc
752
+ @task_desc = nil
753
+ td
754
+ end
755
+
756
+ # Prints msg as error message and raises an RantAbortException.
757
+ def abort(*msg)
758
+ err_msg(msg) unless msg.empty?
759
+ $stderr.puts caller if @opts[:trace_abort]
760
+ raise Rant::RantAbortException
761
+ end
762
+
763
+ def abort_at(ch, *msg)
764
+ err_msg(pos_text(ch[:file], ch[:ln]), msg)
765
+ $stderr.puts caller if @opts[:trace_abort]
766
+ raise Rant::RantAbortException
767
+ end
768
+
769
+ def show_help
770
+ puts "rant [-f Rantfile] [Options] [targets]"
771
+ puts
772
+ puts "Options are:"
773
+ print option_listing(OPTIONS)
774
+ end
775
+
776
+ def show_descriptions
777
+ tlist = select_tasks { |t| t.description }
778
+ # +target_list+ aborts if no task defined, so we can be sure
779
+ # that +default+ is not nil
780
+ def_target = target_list.first
781
+ if tlist.empty?
782
+ puts "rant # => " + list_task_names(
783
+ resolve(def_target)).join(', ')
784
+ msg "No described tasks."
785
+ return
786
+ end
787
+ prefix = "rant "
788
+ infix = " # "
789
+ name_length = (tlist.map{ |t| t.to_s.length } << 7).max
790
+ cmd_length = prefix.length + name_length
791
+ unless tlist.first.to_s == def_target
792
+ defaults = list_task_names(
793
+ resolve(def_target)).join(', ')
794
+ puts "#{prefix}#{' ' * name_length}#{infix}=> #{defaults}"
795
+ end
796
+ tlist.each { |t|
797
+ print(prefix + t.to_s.ljust(name_length) + infix)
798
+ dt = t.description.sub(/\s+$/, "")
799
+ puts dt.gsub(/\n/, "\n" + ' ' * cmd_length + infix + " ")
800
+ }
801
+ true
802
+ end
803
+
804
+ def list_task_names(*tasks)
805
+ rsl = []
806
+ tasks.flatten.each { |t|
807
+ if t.respond_to?(:has_actions?) && t.has_actions?
808
+ rsl << t
809
+ elsif t.respond_to? :prerequisites
810
+ if t.prerequisites.empty?
811
+ rsl << t
812
+ else
813
+ t.prerequisites.each { |pre|
814
+ rsl.concat(list_task_names(
815
+ resolve(pre, t.project_subdir)))
816
+ }
817
+ end
818
+ else
819
+ rsl << t
820
+ end
821
+ }
822
+ rsl
823
+ end
824
+ private :list_task_names
825
+
826
+ # This is actually an integer indicating the verbosity level.
827
+ # Usual values range from 0 to 3.
828
+ def verbose
829
+ @opts[:verbose]
830
+ end
831
+
832
+ def quiet?
833
+ @opts[:quiet]
834
+ end
835
+
836
+ def pos_text(file, ln)
837
+ t = "in file `#{file}'"
838
+ t << ", line #{ln}" if ln && ln > 0
839
+ t << ": "
840
+ end
841
+
842
+ # Print a command message as would be done from a call to a
843
+ # sys method.
844
+ def cmd_msg(cmd)
845
+ puts cmd unless quiet?
846
+ end
847
+
848
+ def cmd_print(text)
849
+ print text unless quiet?
850
+ $stdout.flush
851
+ end
852
+
853
+ # All targets given on commandline, including those given
854
+ # with the -a option. The list will be in processing order.
855
+ def cmd_targets
856
+ @force_targets + @arg_targets
857
+ end
858
+
859
+ def running_task(task)
860
+ if @current_subdir != @last_build_subdir
861
+ cmd_msg "(in #{@current_subdir.empty? ?
862
+ @rootdir : @current_subdir})"
863
+ @last_build_subdir = @current_subdir
864
+ end
865
+ # TODO: model feels sick... this functionality should
866
+ # be implemented in Node
867
+ if @opts[:dry_run]
868
+ task.dry_run
869
+ true
870
+ end
871
+ end
872
+
873
+ private
874
+ def have_any_task?
875
+ !@tasks.empty?
876
+ end
877
+
878
+ def target_list
879
+ if !have_any_task? && @resolve_hooks.empty?
880
+ abort("No tasks defined for this rant application!")
881
+ end
882
+
883
+ # Target selection strategy:
884
+ # Run tasks specified on commandline, if not given:
885
+ # run default task, if not given:
886
+ # run first defined task.
887
+ target_list = @force_targets + @arg_targets
888
+ # The target list is a list of strings, not node objects!
889
+ if target_list.empty?
890
+ def_tasks = resolve "default"
891
+ unless def_tasks.empty?
892
+ target_list << "default"
893
+ else
894
+ @rantfiles.each { |f|
895
+ first = f.tasks.first
896
+ if first
897
+ target_list << first.reference_name
898
+ break
899
+ end
900
+ }
901
+ end
902
+ end
903
+ target_list
904
+ end
905
+
906
+ # If this method returns (i.e. no exception was risen),
907
+ # current_subdir is the same as before invocation.
908
+ def run_tasks
909
+ # Now, run all specified tasks in all rantfiles,
910
+ # rantfiles in reverse order.
911
+ target_list.each { |target|
912
+ # build ensures that current_subdir is the same before
913
+ # and after invocation
914
+ if build(target) == 0
915
+ abort("Don't know how to make `#{target}'.")
916
+ end
917
+ }
918
+ end
919
+
920
+ def make(target, *args, &block)
921
+ ch = nil
922
+ if target.respond_to? :to_hash
923
+ targ = target.to_hash
924
+ ch = Rant::Lib.parse_caller_elem(caller[1])
925
+ abort_at(ch, "make: too many arguments") unless args.empty?
926
+ tn = nil
927
+ prepare_task(targ, block, ch) { |name,pre,blk|
928
+ tn = name
929
+ @node_factory.new_file(self, name, pre, blk)
930
+ }
931
+ build(tn)
932
+ elsif target.respond_to? :to_rant_target
933
+ rt = target.to_rant_target
934
+ opt = args.shift
935
+ unless args.empty?
936
+ ch ||= Rant::Lib.parse_caller_elem(caller[1])
937
+ abort_at(ch, "make: too many arguments")
938
+ end
939
+ if block
940
+ # create a file task
941
+ ch ||= Rant::Lib.parse_caller_elem(caller[1])
942
+ prepare_task(rt, block, ch) { |name,pre,blk|
943
+ @node_factory.new_file(self, name, pre, blk)
944
+ }
945
+ build(rt)
946
+ else
947
+ build(rt, opt||{})
948
+ end
949
+ elsif target.respond_to? :rant_gen
950
+ ch = Rant::Lib.parse_caller_elem(caller[1])
951
+ rv = target.rant_gen(self, ch, args, &block)
952
+ unless rv.respond_to? :to_rant_target
953
+ abort_at(ch, "make: invalid generator return value")
954
+ end
955
+ build(rv.to_rant_target)
956
+ rv
957
+ else
958
+ ch = Rant::Lib.parse_caller_elem(caller[1])
959
+ abort_at(ch,
960
+ "make: generator or target as first argument required.")
961
+ end
962
+ end
963
+ public :make
964
+
965
+ # Invoke all tasks necessary to build +target+. Returns the number
966
+ # of tasks invoked.
967
+ def build(target, opt = {})
968
+ opt[:force] = true if @force_targets.delete(target)
969
+ # Currently either the whole application has to by run in
970
+ # dry-run mode or nothing.
971
+ opt[:dry_run] = @opts[:dry_run]
972
+ matching_tasks = 0
973
+ old_subdir = @current_subdir
974
+ old_pwd = Dir.pwd
975
+ resolve(target).each { |t|
976
+ unless opt[:type] == :file && !t.file_target?
977
+ matching_tasks += 1
978
+ begin
979
+ t.invoke(opt)
980
+ rescue Rant::TaskFail => e
981
+ err_task_fail(e)
982
+ abort
983
+ end
984
+ end
985
+ }
986
+ @current_subdir = old_subdir
987
+ Dir.chdir old_pwd
988
+ matching_tasks
989
+ end
990
+ public :build
991
+
992
+ # Currently always returns an array (which might actually be an
993
+ # empty array, but never nil).
994
+ def resolve(task_name, rel_project_dir = @current_subdir)
995
+ # alternative implementation:
996
+ # rec_save_resolve(task_name, nil, rel_project_dir)
997
+ s = @tasks[expand_path(rel_project_dir, task_name)]
998
+ case s
999
+ when nil
1000
+ @resolve_hooks.each { |s|
1001
+ # Note: will probably change to get more params
1002
+ s = s[task_name, rel_project_dir]
1003
+ #if s
1004
+ # puts s.size
1005
+ # t = s.first
1006
+ # puts t.full_name
1007
+ # puts t.name
1008
+ # puts t.deps
1009
+ #end
1010
+ return s if s
1011
+ }
1012
+ []
1013
+ when Rant::Node: [s]
1014
+ else # assuming list of tasks
1015
+ s
1016
+ end
1017
+ end
1018
+ public :resolve
1019
+
1020
+ def rec_save_resolve(task_name, excl_hook, rel_project_dir = @current_subdir)
1021
+ s = @tasks[expand_path(rel_project_dir, task_name)]
1022
+ case s
1023
+ when nil
1024
+ @resolve_hooks.each { |s|
1025
+ next if s == excl_hook
1026
+ s = s[task_name, rel_project_dir]
1027
+ return s if s
1028
+ }
1029
+ []
1030
+ when Rant::Node: [s]
1031
+ else
1032
+ s
1033
+ end
1034
+ end
1035
+ public :rec_save_resolve
1036
+
1037
+ # This hook will be invoked when no matching task is found for a
1038
+ # target. It may create one or more tasks for the target, which is
1039
+ # given as argument, on the fly and return an array of the created
1040
+ # tasks or nil.
1041
+ def at_resolve(&block)
1042
+ @resolve_hooks << block if block
1043
+ end
1044
+ public :at_resolve
1045
+
1046
+ # block will be called before this rant returns from #run
1047
+ # pwd will be the projects root directory
1048
+ def at_return(&block)
1049
+ hooks = var._get("__at_return__")
1050
+ if hooks
1051
+ hooks << block
1052
+ else
1053
+ var._set("__at_return__", [block])
1054
+ end
1055
+ end
1056
+ public :at_return
1057
+
1058
+ # Returns a list with all tasks for which yield
1059
+ # returns true.
1060
+ def select_tasks
1061
+ selection = []
1062
+ @rantfiles.each { |rf|
1063
+ rf.tasks.each { |t|
1064
+ selection << t if yield t
1065
+ }
1066
+ }
1067
+ selection
1068
+ end
1069
+ public :select_tasks
1070
+
1071
+ def load_rantfiles
1072
+ # Take care: When rant isn't invoked from commandline,
1073
+ # some "rant code" could already have run!
1074
+ # We run the default Rantfiles only if no tasks where
1075
+ # already defined and no Rantfile was given in args.
1076
+ unless @arg_rantfiles.empty?
1077
+ @arg_rantfiles.each { |fn|
1078
+ if test(?f, fn)
1079
+ rf, is_new = rantfile_for_path(fn)
1080
+ load_file rf if is_new
1081
+ else
1082
+ abort "No such file -- #{fn}"
1083
+ end
1084
+ }
1085
+ return
1086
+ end
1087
+ return if have_any_task?
1088
+ # look for standard Rantfile in working directory
1089
+ fn = rantfile_in_dir
1090
+ if @opts[:cd_parent]
1091
+ # search for Rantfile in parent directories
1092
+ old_root = @rootdir
1093
+ until fn or @rootdir == "/"
1094
+ @rootdir = File.dirname(@rootdir)
1095
+ fn = rantfile_in_dir(@rootdir)
1096
+ end
1097
+ if @rootdir != old_root and fn
1098
+ Dir.chdir @rootdir
1099
+ cmd_msg "(in #@rootdir)"
1100
+ end
1101
+ end
1102
+ if fn
1103
+ rf, is_new = rantfile_for_path(fn)
1104
+ load_file rf if is_new
1105
+ return
1106
+ end
1107
+ have_sub_rantfile = test(?f, Rant::SUB_RANTFILE)
1108
+ if have_sub_rantfile || @opts[:look_up]
1109
+ # search for "root" Rantfile in parent directories, treat
1110
+ # current working directory as project subdirectory
1111
+ cur_dir = Dir.pwd
1112
+ until cur_dir == "/"
1113
+ cur_dir = File.dirname(cur_dir)
1114
+ Dir.chdir cur_dir
1115
+ fn = rantfile_in_dir
1116
+ if fn
1117
+ @initial_subdir = @rootdir.sub(
1118
+ /^#{Regexp.escape cur_dir}\//, '')
1119
+ # adjust rootdir
1120
+ @rootdir = cur_dir
1121
+ cmd_msg "(root is #@rootdir, in #@initial_subdir)"
1122
+ @last_build_subdir = @initial_subdir
1123
+ rf, is_new = rantfile_for_path(fn)
1124
+ load_file rf if is_new
1125
+ goto_project_dir @initial_subdir
1126
+ # ensure to read sub.rant in initial subdir even
1127
+ # if it wasn't mentioned with +subdirs+.
1128
+ if have_sub_rantfile
1129
+ rf, is_new = rantfile_for_path(
1130
+ Rant::SUB_RANTFILE, false)
1131
+ if is_new
1132
+ @rantfiles.unshift rf
1133
+ load_file rf
1134
+ end
1135
+ end
1136
+ break
1137
+ end
1138
+ end
1139
+ end
1140
+ if @rantfiles.empty?
1141
+ abort("No Rantfile found, looking for:",
1142
+ Rant::RANTFILES.join(", "))
1143
+ end
1144
+ end
1145
+
1146
+ # Returns the value of the last expression executed in +rantfile+.
1147
+ # +rantfile+ has to be an Rant::Rantfile instance.
1148
+ def load_file(rantfile)
1149
+ vmsg 1, "source #{rantfile}"
1150
+ @context.instance_eval(File.read(rantfile), rantfile)
1151
+ end
1152
+ private :load_file
1153
+
1154
+ # Get path to Rantfile in +dir+ or nil if dir doesn't contain an
1155
+ # Rantfile.
1156
+ #
1157
+ # If dir is nil, look in current directory.
1158
+ def rantfile_in_dir(dir=nil)
1159
+ ::Rant::RANTFILES.each { |rfn|
1160
+ path = dir ? File.join(dir, rfn) : rfn
1161
+ return path if test ?f, path
1162
+ }
1163
+ nil
1164
+ end
1165
+
1166
+ def process_args
1167
+ # WARNING: we currently have to fool getoptlong,
1168
+ # by temporary changing ARGV!
1169
+ # This could cause problems (e.g. multithreading).
1170
+ old_argv = ARGV.dup
1171
+ ARGV.replace(@args.dup)
1172
+ cmd_opts = GetoptLong.new(*OPTIONS.collect { |lst| lst[0..-2] })
1173
+ cmd_opts.quiet = true
1174
+ cmd_opts.each { |opt, value|
1175
+ case opt
1176
+ when "--verbose": @opts[:verbose] += 1
1177
+ when "--version"
1178
+ puts "rant #{Rant::VERSION}"
1179
+ raise Rant::RantDoneException
1180
+ when "--help"
1181
+ show_help
1182
+ raise Rant::RantDoneException
1183
+ when "--directory"
1184
+ @rootdir = File.expand_path(value)
1185
+ when "--rantfile"
1186
+ @arg_rantfiles << value
1187
+ when "--force-run"
1188
+ @force_targets << value
1189
+ when "--import"
1190
+ import value
1191
+ else
1192
+ # simple switch
1193
+ @opts[opt.sub(/^--/, '').tr('-', "_").to_sym] = true
1194
+ end
1195
+ }
1196
+ rescue GetoptLong::Error => e
1197
+ abort(e.message)
1198
+ ensure
1199
+ rem_args = ARGV.dup
1200
+ ARGV.replace(old_argv)
1201
+ rem_args.each { |ra|
1202
+ if ra =~ /(^[^=]+)=([^=]+)$/
1203
+ vmsg 2, "var: #$1=#$2"
1204
+ @var[$1] = $2
1205
+ else
1206
+ @arg_targets << ra
1207
+ end
1208
+ }
1209
+ end
1210
+
1211
+ # Every task has to be registered with this method.
1212
+ def prepare_task(targ, block, clr = caller[2])
1213
+ #STDERR.puts "prepare task (#@current_subdir):\n #{targ.inspect}"
1214
+
1215
+ # Allow override of caller, useful for plugins and libraries
1216
+ # that define tasks.
1217
+ if targ.is_a? Hash
1218
+ targ.reject! { |k, v| clr = v if k == :__caller__ }
1219
+ end
1220
+ ch = Hash === clr ? clr : Rant::Lib::parse_caller_elem(clr)
1221
+
1222
+ name, pre = normalize_task_arg(targ, ch)
1223
+
1224
+ file, is_new = rantfile_for_path(ch[:file])
1225
+ nt = yield(name, pre, block)
1226
+ nt.rantfile = file
1227
+ #nt.project_subdir = file.project_subdir
1228
+ nt.project_subdir = @current_subdir
1229
+ nt.line_number = ch[:ln]
1230
+ nt.description = @task_desc
1231
+ @task_desc = nil
1232
+ file.tasks << nt
1233
+ hash_task nt
1234
+ nt
1235
+ end
1236
+ public :prepare_task
1237
+
1238
+ def hash_task(task)
1239
+ n = task.full_name
1240
+ #STDERR.puts "hash_task: `#{n}'"
1241
+ et = @tasks[n]
1242
+ case et
1243
+ when nil
1244
+ @tasks[n] = task
1245
+ when Rant::Node
1246
+ mt = [et, task]
1247
+ @tasks[n] = mt
1248
+ else # assuming list of tasks
1249
+ et << task
1250
+ end
1251
+ end
1252
+
1253
+ # Tries to extract task name and prerequisites from the typical
1254
+ # argument to the +task+ command. +targ+ should be one of String,
1255
+ # Symbol or Hash. ch is the caller (hash with the elements :file
1256
+ # and :ln) and is used for error reporting and debugging.
1257
+ #
1258
+ # Returns two values, the first is a string which is the task name
1259
+ # and the second is an array with the prerequisites.
1260
+ def normalize_task_arg(targ, ch)
1261
+ name = nil
1262
+ pre = []
1263
+
1264
+ # process and validate targ
1265
+ if targ.is_a? Hash
1266
+ if targ.empty?
1267
+ abort_at(ch, "Empty hash as task argument, " +
1268
+ "task name required.")
1269
+ end
1270
+ if targ.size > 1
1271
+ abort_at(ch, "Too many hash elements, " +
1272
+ "should only be one.")
1273
+ end
1274
+ targ.each_pair { |k,v|
1275
+ name = normalize_task_name(k, ch)
1276
+ pre = v
1277
+ }
1278
+ unless ::Rant::FileList === pre
1279
+ if pre.respond_to? :to_ary
1280
+ pre = pre.to_ary.dup
1281
+ pre.map! { |elem|
1282
+ normalize_task_name(elem, ch)
1283
+ }
1284
+ else
1285
+ pre = [normalize_task_name(pre, ch)]
1286
+ end
1287
+ end
1288
+ else
1289
+ name = normalize_task_name(targ, ch)
1290
+ end
1291
+
1292
+ [name, pre]
1293
+ end
1294
+ public :normalize_task_arg
1295
+
1296
+ # Tries to make a task name out of arg and returns
1297
+ # the valid task name. If not possible, calls abort
1298
+ # with an appropriate error message using file and ln.
1299
+ def normalize_task_name(arg, ch)
1300
+ return arg if arg.is_a? String
1301
+ if Symbol === arg
1302
+ arg.to_s
1303
+ elsif arg.respond_to? :to_str
1304
+ arg.to_str
1305
+ else
1306
+ abort_at(ch, "Task name has to be a string or symbol.")
1307
+ end
1308
+ end
1309
+
1310
+ # Returns a Rant::Rantfile object as first value
1311
+ # and a boolean value as second. If the second is true,
1312
+ # the rantfile was created and added, otherwise the rantfile
1313
+ # already existed.
1314
+ def rantfile_for_path(path, register=true)
1315
+ # all rantfiles have an absolute path as path attribute
1316
+ abs_path = File.expand_path(path)
1317
+ file = @rantfiles.find { |rf| rf.path == abs_path }
1318
+ if file
1319
+ [file, false]
1320
+ else
1321
+ # create new Rantfile object
1322
+ file = Rant::Rantfile.new abs_path
1323
+ file.project_subdir = @current_subdir
1324
+ @rantfiles << file if register
1325
+ [file, true]
1326
+ end
1327
+ end
1328
+
1329
+ # Returns the usual hash with :file and :ln as keys for the first
1330
+ # element in backtrace which comes from an Rantfile, or nil if no
1331
+ # Rantfile is involved.
1332
+ #
1333
+ # Note that this method is very time consuming!
1334
+ def get_ch_from_backtrace(backtrace)
1335
+ backtrace.each { |clr|
1336
+ ch = ::Rant::Lib.parse_caller_elem(clr)
1337
+ if ::Rant::Env.on_windows?
1338
+ return ch if @rantfiles.any? { |rf|
1339
+ # sigh... a bit hackish: replace any backslash
1340
+ # with a slash and remove any leading drive (e.g.
1341
+ # C:) from the path
1342
+ rf.path.tr("\\", "/").sub(/^\w\:/, '') ==
1343
+ ch[:file].tr("\\", "/").sub(/^\w\:/, '')
1344
+ }
1345
+ else
1346
+ return ch if @rantfiles.any? { |rf|
1347
+ rf.path == ch[:file]
1348
+ }
1349
+ end
1350
+ }
1351
+ nil
1352
+ end
1353
+
1354
+ def err_task_fail(e)
1355
+ msg = []
1356
+ t_msg = ["Task `#{e.tname}' fail."]
1357
+ orig = e
1358
+ loop { orig = orig.orig; break unless Rant::TaskFail === orig }
1359
+ if orig && orig != e && !(Rant::RantAbortException === orig)
1360
+ ch = get_ch_from_backtrace(orig.backtrace)
1361
+ msg << pos_text(ch[:file], ch[:ln]) if ch
1362
+ unless Rant::CommandError === orig && !@opts[:err_commands]
1363
+ msg << orig.message
1364
+ msg << orig.backtrace[0..4] unless ch
1365
+ end
1366
+ end
1367
+ if e.msg && !e.msg.empty?
1368
+ ch = get_ch_from_backtrace(e.backtrace)
1369
+ t_msg.unshift(e.msg)
1370
+ t_msg.unshift(pos_text(ch[:file], ch[:ln])) if ch
1371
+ end
1372
+ err_msg msg unless msg.empty?
1373
+ err_msg t_msg
1374
+ end
1375
+ end # class Rant::RantApp
1376
+ # this line prevents ruby 1.8.3 from segfaulting