rant 0.5.6 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
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