rant 0.3.6 → 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/NEWS +13 -0
  2. data/README +7 -1
  3. data/Rantfile +10 -14
  4. data/TODO +3 -0
  5. data/devel-notes +5 -0
  6. data/doc/advanced.rdoc +46 -0
  7. data/doc/c.rdoc +64 -0
  8. data/doc/examples/c_dependencies/Rantfile +27 -0
  9. data/doc/examples/c_dependencies/include/hello.h +7 -0
  10. data/doc/examples/c_dependencies/include/util.h +7 -0
  11. data/doc/examples/c_dependencies/src/main.c +9 -0
  12. data/doc/examples/c_dependencies/src/util.c +9 -0
  13. data/doc/examples/directedrule/Rantfile +0 -1
  14. data/doc/rantfile.rdoc +12 -9
  15. data/doc/rubyproject.rdoc +26 -0
  16. data/lib/rant/c/include.rb +51 -0
  17. data/lib/rant/import/autoclean.rb +16 -9
  18. data/lib/rant/import/c/dependencies.rb +127 -0
  19. data/lib/rant/import/directedrule.rb +8 -4
  20. data/lib/rant/import/rubypackage.rb +2 -1
  21. data/lib/rant/import/subfile.rb +41 -0
  22. data/lib/rant/import/truth.rb +6 -1
  23. data/lib/rant/import/win32/rubycmdwrapper.rb +37 -0
  24. data/lib/rant/import.rb +26 -3
  25. data/lib/rant/rantenv.rb +0 -32
  26. data/lib/rant/rantfile.rb +207 -194
  27. data/lib/rant/rantlib.rb +83 -150
  28. data/lib/rant/rantsys.rb +7 -10
  29. data/lib/rant/rantvar.rb +4 -6
  30. data/lib/rant.rb +57 -0
  31. data/rantmethods.rb +1 -47
  32. data/setup.rb +2 -2
  33. data/test/Rantfile +6 -1
  34. data/test/c/source.c +23 -0
  35. data/test/c/test_parse_includes.rb +41 -0
  36. data/test/import/c/dependencies/Rantfile +34 -0
  37. data/test/import/c/dependencies/bar.h +2 -0
  38. data/test/import/c/dependencies/foo.h +5 -0
  39. data/test/import/c/dependencies/hello.c +7 -0
  40. data/test/import/c/dependencies/include/foo.h +0 -0
  41. data/test/import/c/dependencies/include/sub/sub.h +8 -0
  42. data/test/import/c/dependencies/include/with space.h +7 -0
  43. data/test/import/c/dependencies/src/abc +5 -0
  44. data/test/import/c/dependencies/src/abc.c +5 -0
  45. data/test/import/c/dependencies/src/bar.c +11 -0
  46. data/test/import/c/dependencies/test_c_dependencies.rb +92 -0
  47. data/test/import/c/dependencies/test_on_the_fly.rb +44 -0
  48. data/test/import/directedrule/Rantfile +7 -2
  49. data/test/import/directedrule/test_directedrule.rb +6 -0
  50. data/test/import/subfile/Rantfile +28 -0
  51. data/test/import/subfile/autoclean.rf +16 -0
  52. data/test/import/subfile/test_subfile.rb +91 -0
  53. data/test/import/truth/Rantfile +7 -0
  54. data/test/import/truth/test_truth.rb +3 -0
  55. data/test/project2/buildfile +2 -0
  56. data/test/project2/test_project.rb +5 -3
  57. data/test/rant-import/Rantfile +4 -0
  58. data/test/rant-import/test_rant-import.rb +104 -1
  59. data/test/rule.rf +6 -0
  60. data/test/test_autosubfiletask.rb +59 -0
  61. data/test/test_clean.rb +48 -5
  62. data/test/test_dirtask.rb +45 -1
  63. data/test/test_examples.rb +25 -3
  64. data/test/test_filelist.rb +14 -2
  65. data/test/test_lighttask.rb +4 -6
  66. data/test/test_rant_interface.rb +8 -8
  67. data/test/test_rantfile_api.rb +37 -1
  68. data/test/test_rule.rb +6 -3
  69. data/test/test_source.rb +28 -1
  70. data/test/test_sourcenode.rb +163 -0
  71. data/test/test_task.rb +2 -2
  72. data/test/test_var.rb +3 -3
  73. data/test/tutil.rb +23 -2
  74. metadata +45 -3
  75. data/test/test_metatask.rb +0 -29
data/lib/rant/rantfile.rb CHANGED
@@ -24,31 +24,34 @@ module Rant
24
24
  end
25
25
  end
26
26
 
27
- class Rantfile < Path
27
+ class Rantfile
28
28
 
29
- attr_reader :tasks
29
+ attr_reader :tasks, :path
30
30
  attr_accessor :project_subdir
31
31
 
32
- def initialize(*args)
33
- super
32
+ def initialize(path)
33
+ @path = path or raise ArgumentError, "path required"
34
34
  @tasks = []
35
35
  @project_subdir = nil
36
36
  end
37
+ def to_s
38
+ @path
39
+ end
37
40
  end # class Rantfile
38
41
 
39
42
  # Any +object+ is considered a _task_ if
40
- # <tt>Rant::Worker === object</tt> is true.
43
+ # <tt>Rant::Node === object</tt> is true.
41
44
  #
42
45
  # Most important classes including this module are the Rant::Task
43
46
  # class and the Rant::FileTask class.
44
- module Worker
47
+ module Node
45
48
 
46
49
  INVOKE_OPT = {}.freeze
47
50
 
48
51
  # Name of the task, this is always a string.
49
52
  attr_reader :name
50
- # A reference to the application this task belongs to.
51
- attr_reader :app
53
+ # A reference to the Rant compiler this task belongs to.
54
+ attr_reader :rac
52
55
  # Description for this task.
53
56
  attr_accessor :description
54
57
  # The rantfile this task was defined in.
@@ -90,7 +93,7 @@ module Rant
90
93
  # Important for subclasses: Call this method always before
91
94
  # invoking code from Rantfiles (e.g. task action blocks).
92
95
  def goto_task_home
93
- @app.goto_project_dir project_subdir
96
+ @rac.goto_project_dir project_subdir
94
97
  end
95
98
 
96
99
  def done?
@@ -107,11 +110,11 @@ module Rant
107
110
  end
108
111
 
109
112
  # +opt+ is a Hash and shouldn't be modified.
110
- # All objects implementing the Rant::Worker protocol should
113
+ # All objects implementing the Rant::Node protocol should
111
114
  # know about the following +opt+ values:
112
115
  # <tt>:needed?</tt>::
113
116
  # Just check if this task is needed. Should do the same
114
- # as calling Worker#needed?
117
+ # as calling Node#needed?
115
118
  # <tt>:force</tt>::
116
119
  # Run task action even if needed? is false.
117
120
  # Returns true if task action was run.
@@ -147,7 +150,7 @@ module Rant
147
150
  private :run
148
151
 
149
152
  def circular_dep
150
- app.warn_msg "Circular dependency on task `#{full_name}'."
153
+ rac.warn_msg "Circular dependency on task `#{full_name}'."
151
154
  false
152
155
  end
153
156
  private :circular_dep
@@ -158,121 +161,40 @@ module Rant
158
161
  end
159
162
 
160
163
  def eql? other
161
- Worker === other and full_name.eql? other.full_name
162
- end
163
- end # module Worker
164
-
165
- # A list of tasks with an equal full_name.
166
- class MetaTask < Array
167
- include Worker
168
-
169
- class << self
170
- def for_task t
171
- mt = self.new(t.full_name)
172
- mt << t
173
- end
174
- def for_tasks *tasks
175
- mt = self.new(tasks.first.full_name)
176
- mt.concat tasks
177
- mt
178
- end
179
- def for_task_list tasks
180
- mt = self.new(tasks.first.full_name)
181
- mt.concat tasks
182
- mt
183
- end
164
+ Node === other and full_name.eql? other.full_name
184
165
  end
185
-
186
- def initialize(name)
187
- super()
188
- @name = name or raise ArgumentError, "no name given"
189
- end
190
-
191
- def done?
192
- all? { |t| t.done? }
193
- end
194
-
195
- def needed?
196
- any? { |t| t.needed? }
197
- end
198
-
199
- def invoke(opt = INVOKE_OPT)
200
- uf = false
201
- each { |t| t.invoke(opt) && uf = true }
202
- uf
203
- end
204
-
205
- def description
206
- nil
207
- end
208
-
209
- def description=(val)
210
- # spit out a warning?
211
- end
212
-
213
- def rantfile
214
- nil
215
- end
216
-
217
- def rantfile=(val)
218
- # spit out a warning?
219
- end
220
-
221
- def line_number
222
- 0
223
- end
224
-
225
- def line_number=(val)
226
- # spit out a warning?
227
- end
228
-
229
- def app
230
- empty? ? nil : first.app
231
- end
232
-
233
- def each_target &block
234
- self.each { |t| t.each_target &block }
235
- end
236
- end # class MetaTask
166
+ end # module Node
237
167
 
238
168
  # A very lightweight task for special purposes.
239
169
  class LightTask
240
- include Worker
170
+ include Node
241
171
 
242
172
  class << self
243
- def rant_generate(app, ch, args, &block)
173
+ def rant_generate(rac, ch, args, &block)
244
174
  unless args.size == 1
245
- app.abort("LightTask takes only one argument " +
175
+ rac.abort("LightTask takes only one argument " +
246
176
  "which has to be the taskname (string or symbol)")
247
177
  end
248
- app.prepare_task({args.first => [], :__caller__ => ch},
178
+ rac.prepare_task({args.first => [], :__caller__ => ch},
249
179
  block) { |name,pre,blk|
250
180
  # TODO: ensure pre is empty
251
- # TODO: earlier setting of app?
252
- self.new(app, name, &blk)
181
+ self.new(rac, name, &blk)
253
182
  }
254
183
  end
255
184
  end
256
185
 
257
- def initialize(app, name)
186
+ def initialize(rac, name)
258
187
  super()
259
- @app = app or raise ArgumentError, "no app given"
260
- @name = case name
261
- when String: name
262
- when Symbol: name.to_s
263
- else
264
- raise ArgumentError,
265
- "invalid name argument: #{name.inspect}"
266
- end
188
+ @rac = rac or raise ArgumentError, "no rac given"
189
+ @name = name
267
190
  @needed = nil
268
191
  @block = nil
269
192
  @done = false
270
-
271
193
  yield self if block_given?
272
194
  end
273
195
 
274
- def app
275
- @app
196
+ def rac
197
+ @rac
276
198
  end
277
199
 
278
200
  def needed &block
@@ -297,19 +219,12 @@ module Rant
297
219
  return needed? if opt[:needed?]
298
220
  # +run+ already calls +goto_task_home+
299
221
  #goto_task_home
300
- if opt[:force] && !@done
301
- self.run
222
+ if opt[:force] && !@done or needed?
223
+ run
302
224
  @done = true
303
- else
304
- if needed?
305
- run
306
- @done = true
307
- else
308
- false
309
- end
310
225
  end
311
226
  rescue CommandError => e
312
- err_msg e.message if app[:err_commands]
227
+ err_msg e.message if rac[:err_commands]
313
228
  self.fail(nil, e)
314
229
  rescue SystemCallError => e
315
230
  err_msg e.message
@@ -321,25 +236,25 @@ module Rant
321
236
  end # LightTask
322
237
 
323
238
  class Task
324
- include Worker
239
+ include Node
325
240
  include Console
326
241
 
327
242
  T0 = Time.at(0).freeze
328
243
 
329
244
  class << self
330
- def rant_generate(app, ch, args, &block)
245
+ def rant_generate(rac, ch, args, &block)
331
246
  if args.size == 1
332
- UserTask.rant_generate(app, ch, args, &block)
247
+ UserTask.rant_generate(rac, ch, args, &block)
333
248
  else
334
- app.abort("Task generator currently takes only one" +
249
+ rac.abort("Task generator currently takes only one" +
335
250
  " argument. (generates a UserTask)")
336
251
  end
337
252
  end
338
253
  end
339
254
 
340
- def initialize(app, name, prerequisites = [], &block)
255
+ def initialize(rac, name, prerequisites = [], &block)
341
256
  super()
342
- @app = app || Rant.rac
257
+ @rac = rac || Rant.rac
343
258
  @name = name or raise ArgumentError, "name not given"
344
259
  @pre = prerequisites || []
345
260
  @pre_resolved = false
@@ -435,12 +350,12 @@ module Rant
435
350
  dep = nil
436
351
  uf = false
437
352
  each_dep { |dep|
438
- if FileTask === dep
439
- handle_filetask(dep, opt) && update = true
440
- elsif Worker === dep
441
- handle_worker(dep, opt) && update = true
353
+ if dep.respond_to? :timestamp
354
+ handle_timestamped(dep, opt) && update = true
355
+ elsif Node === dep
356
+ handle_node(dep, opt) && update = true
442
357
  else
443
- dep, uf = handle_non_worker(dep, opt)
358
+ dep, uf = handle_non_node(dep, opt)
444
359
  uf && update = true
445
360
  dep
446
361
  end
@@ -458,25 +373,25 @@ module Rant
458
373
  private :internal_invoke
459
374
 
460
375
  # Called from internal_invoke. +dep+ is a prerequisite which
461
- # is_a? Worker, but not a FileTask. +opt+ are opts as given to
462
- # Worker#invoke.
376
+ # is_a? Node, but not a FileTask. +opt+ are opts as given to
377
+ # Node#invoke.
463
378
  #
464
379
  # Override this method in subclasses to modify behaviour of
465
380
  # prerequisite handling.
466
381
  #
467
- # See also: handle_filetask, handle_non_worker
468
- def handle_worker(dep, opt)
382
+ # See also: handle_timestamped, handle_non_node
383
+ def handle_node(dep, opt)
469
384
  dep.invoke opt
470
385
  end
471
386
 
472
387
  # Called from internal_invoke. +dep+ is a prerequisite which
473
- # is_a? FileTask. +opt+ are opts as given to Worker#invoke.
388
+ # is_a? FileTask. +opt+ are opts as given to Node#invoke.
474
389
  #
475
390
  # Override this method in subclasses to modify behaviour of
476
391
  # prerequisite handling.
477
392
  #
478
- # See also: handle_worker, handle_non_worker
479
- def handle_filetask(dep, opt)
393
+ # See also: handle_node, handle_non_node
394
+ def handle_timestamped(dep, opt)
480
395
  dep.invoke opt
481
396
  end
482
397
 
@@ -488,8 +403,8 @@ module Rant
488
403
  # [1] Fail with an exception.
489
404
  # [2] Return two values: replacement_for_dep, update_required
490
405
  #
491
- # See also: handle_worker, handle_filetask
492
- def handle_non_worker(dep, opt)
406
+ # See also: handle_node, handle_timestamped
407
+ def handle_non_node(dep, opt)
493
408
  err_msg "Unknown task `#{dep}',",
494
409
  "referenced in `#{rantfile.path}', line #{@line_number}!"
495
410
  self.fail
@@ -506,7 +421,7 @@ module Rant
506
421
  my_full_name = full_name
507
422
  my_project_subdir = project_subdir
508
423
  @pre.map! { |t|
509
- if Worker === t
424
+ if Node === t
510
425
  # Remove references to self from prerequisites!
511
426
  if t.full_name == my_full_name
512
427
  nil
@@ -520,7 +435,7 @@ module Rant
520
435
  nil
521
436
  else
522
437
  #STDERR.puts "selecting `#{t}'"
523
- selection = @app.resolve t,
438
+ selection = @rac.resolve t,
524
439
  my_project_subdir
525
440
  #STDERR.puts selection.size
526
441
  if selection.empty?
@@ -544,14 +459,14 @@ module Rant
544
459
  class UserTask < Task
545
460
 
546
461
  class << self
547
- def rant_generate(app, ch, args, &block)
462
+ def rant_generate(rac, ch, args, &block)
548
463
  unless args.size == 1
549
- app.abort("UserTask takes only one argument " +
464
+ rac.abort("UserTask takes only one argument " +
550
465
  "which has to be like one given to the " +
551
466
  "`task' function")
552
467
  end
553
- app.prepare_task(args.first, nil, ch) { |name,pre,blk|
554
- self.new(app, name, pre, &block)
468
+ rac.prepare_task(args.first, nil, ch) { |name,pre,blk|
469
+ self.new(rac, name, pre, &block)
555
470
  }
556
471
  end
557
472
  end
@@ -602,19 +517,9 @@ module Rant
602
517
 
603
518
  def initialize *args
604
519
  super
605
- if @name.is_a? Path
606
- @path = @name
607
- @name = @path.to_s
608
- else
609
- @path = Path.new @name
610
- end
611
520
  @ts = T0
612
521
  end
613
522
 
614
- def path
615
- @path
616
- end
617
-
618
523
  def needed?
619
524
  return false if done?
620
525
  invoke(:needed? => true)
@@ -626,8 +531,8 @@ module Rant
626
531
  begin
627
532
  return if done?
628
533
  goto_task_home
629
- if @path.exist?
630
- @ts = @path.mtime
534
+ if File.exist? @name
535
+ @ts = File.mtime @name
631
536
  internal_invoke opt, false
632
537
  else
633
538
  @ts = T0
@@ -638,23 +543,23 @@ module Rant
638
543
  end
639
544
  end
640
545
 
641
- def handle_filetask(dep, opt)
546
+ def timestamp
547
+ File.exist?(@name) ? File.mtime(@name) : T0
548
+ end
549
+
550
+ def handle_timestamped(dep, opt)
642
551
  return true if dep.invoke opt
643
- # TODO: require dep to exist after invoke?
644
- if dep.path.exist?
645
- #puts "***`#{dep.name}' requires update" if dep.path.mtime > @ts
646
- dep.path.mtime > @ts
647
- end
552
+ #puts "***`#{dep.name}' requires update" if dep.timestamp > @ts
553
+ dep.timestamp > @ts
648
554
  end
649
555
 
650
- def handle_non_worker(dep, opt)
651
- dep = Path.new(dep) unless Path === dep
652
- unless dep.exist?
653
- err_msg @app.pos_text(rantfile.path, line_number),
556
+ def handle_non_node(dep, opt)
557
+ unless File.exist? dep
558
+ err_msg @rac.pos_text(rantfile.path, line_number),
654
559
  "in prerequisites: no such file or task: `#{dep}'"
655
560
  self.fail
656
561
  end
657
- [dep, dep.mtime > @ts]
562
+ [dep, File.mtime(dep) > @ts]
658
563
  end
659
564
 
660
565
  def each_target
@@ -663,6 +568,18 @@ module Rant
663
568
  end
664
569
  end # class FileTask
665
570
 
571
+ class AutoSubFileTask < FileTask
572
+ private
573
+ def run
574
+ dir, = File.split(name)
575
+ unless dir == "."
576
+ dt = @rac.resolve(dir, project_subdir).last
577
+ dt.invoke if DirTask === dt
578
+ end
579
+ super
580
+ end
581
+ end # class AutoSubFileTask
582
+
666
583
  # An instance of this class is a task to create a _single_
667
584
  # directory.
668
585
  class DirTask < Task
@@ -677,12 +594,23 @@ module Rant
677
594
  # block will be called after complete directory creation.
678
595
  # After the block execution, the modification time of the
679
596
  # directory will be updated.
680
- def rant_generate(app, ch, args, &block)
681
- if args && args.size == 1
682
- name, pre, file, ln = app.normalize_task_arg(args.first, ch)
683
- self.task(app, ch, name, pre, &block)
597
+ def rant_generate(rac, ch, args, &block)
598
+ case args.size
599
+ when 1
600
+ name, pre, file, ln = rac.normalize_task_arg(args.first, ch)
601
+ self.task(rac, ch, name, pre, &block)
602
+ when 2
603
+ basedir = args.shift
604
+ if basedir.respond_to? :to_str
605
+ basedir = basedir.to_str
606
+ else
607
+ rac.abort_at(ch,
608
+ "Directory: basedir argument has to be a string.")
609
+ end
610
+ name, pre, file, ln = rac.normalize_task_arg(args.first, ch)
611
+ self.task(rac, ch, name, pre, basedir, &block)
684
612
  else
685
- app.abort(app.pos_text(ch[:file], ch[:ln]),
613
+ rac.abort(rac.pos_text(ch[:file], ch[:ln]),
686
614
  "Directory takes one argument, " +
687
615
  "which should be like one given to the `task' command.")
688
616
  end
@@ -691,44 +619,40 @@ module Rant
691
619
  # Returns the task which creates the last directory
692
620
  # element (and has all other necessary directories as
693
621
  # prerequisites).
694
- def task(app, ch, name, prerequisites = [], &block)
622
+ def task(rac, ch, name, prerequisites=[], basedir=nil, &block)
695
623
  dirs = ::Rant::Sys.split_path(name)
696
624
  if dirs.empty?
697
- app.abort(app.pos_text(ch[:file], ch[:ln]),
625
+ rac.abort_at(ch,
698
626
  "Not a valid directory name: `#{name}'")
699
627
  end
700
- ld = nil
701
- path = nil
628
+ path = basedir
702
629
  last_task = nil
703
630
  task_block = nil
631
+ desc_for_last = rac.pop_desc
704
632
  dirs.each { |dir|
705
- pre = [ld]
633
+ pre = [path]
706
634
  pre.compact!
707
635
  if dir.equal?(dirs.last)
636
+ rac.cx.desc desc_for_last
708
637
  pre.concat prerequisites if prerequisites
709
638
  task_block = block
710
639
  end
711
640
  path = path.nil? ? dir : File.join(path, dir)
712
- last_task = app.prepare_task({:__caller__ => ch,
641
+ last_task = rac.prepare_task({:__caller__ => ch,
713
642
  path => pre}, task_block) { |name,pre,blk|
714
- self.new(app, name, pre, &blk)
643
+ self.new(rac, name, pre, &blk)
715
644
  }
716
- ld = dir
717
645
  }
718
646
  last_task
719
647
  end
720
648
  end
721
649
 
722
- def intialize *args
650
+ def initialize *args
723
651
  super
724
652
  @ts = T0
725
653
  @isdir = nil
726
654
  end
727
655
 
728
- #def needed?
729
- # invoke(:needed? => true)
730
- #end
731
-
732
656
  def invoke(opt = INVOKE_OPT)
733
657
  return circular_dep if @run
734
658
  @run = true
@@ -748,29 +672,26 @@ module Rant
748
672
  end
749
673
  end
750
674
 
751
- def handle_filetask(dep, opt)
675
+ def handle_timestamped(dep, opt)
752
676
  return @block if dep.invoke opt
753
- if dep.path.exist?
754
- @block && dep.path.mtime > @ts
755
- end
677
+ @block && dep.timestamp > @ts
756
678
  end
757
679
 
758
- def handle_non_worker(dep, opt)
759
- dep = Path.new(dep) unless Path === dep
760
- unless dep.exist?
761
- err_msg @app.pos_text(rantfile.path, line_number),
680
+ def handle_non_node(dep, opt)
681
+ unless File.exist? dep
682
+ err_msg @rac.pos_text(rantfile.path, line_number),
762
683
  "in prerequisites: no such file or task: `#{dep}'"
763
684
  self.fail
764
685
  end
765
- [dep, @block && dep.mtime > @ts]
686
+ [dep, @block && File.mtime(dep) > @ts]
766
687
  end
767
688
 
768
689
  def run
769
- @app.sys.mkdir @name unless @isdir
690
+ @rac.sys.mkdir @name unless @isdir
770
691
  if @block
771
692
  @block.arity == 0 ? @block.call : @block[self]
772
693
  goto_task_home
773
- @app.sys.touch @name
694
+ @rac.sys.touch @name
774
695
  end
775
696
  end
776
697
 
@@ -779,10 +700,102 @@ module Rant
779
700
  yield name
780
701
  end
781
702
  end # class DirTask
703
+
704
+ # A SourceNode describes dependencies between source files. Thus
705
+ # there is no action attached to a SourceNode. The target should
706
+ # be an existing file as well as all dependencies.
707
+ #
708
+ # An example would be a C source file which depends on other C
709
+ # source files because of <tt>#include</tt> statements.
710
+ #
711
+ # Rantfile usage:
712
+ # gen SourceNode, "myext.c" => %w(ruby.h myext.h)
713
+ class SourceNode
714
+ include Node
715
+
716
+ def self.rant_generate(rac, ch, args)
717
+ unless args.size == 1
718
+ rac.abort_at(ch, "SourceNode takes one argument.")
719
+ end
720
+ if block_given?
721
+ rac.abort_at(ch, "SourceNode doesn't take a block.")
722
+ end
723
+ rac.prepare_task(args.first, nil, ch) { |name, pre, blk|
724
+ new(rac, name, pre, &blk)
725
+ }
726
+ end
727
+
728
+ def initialize(rac, name, prerequisites = [])
729
+ super()
730
+ @rac = rac
731
+ @name = name or raise ArgumentError, "name not given"
732
+ @pre = prerequisites
733
+ @run = false
734
+ # The timestamp is the latest of this file and all
735
+ # dependencies:
736
+ @ts = nil
737
+ end
738
+
739
+ # Use this readonly!
740
+ def prerequisites
741
+ @pre
742
+ end
743
+
744
+ # Note: The timestamp will only be calculated once!
745
+ def timestamp
746
+ # Circular dependencies don't generate endless
747
+ # recursion/loops because before calling the timestamp
748
+ # method of any other node, we set @ts to some non-nil
749
+ # value.
750
+ return @ts if @ts
751
+ goto_task_home
752
+ if File.exist?(@name)
753
+ @ts = File.mtime @name
754
+ else
755
+ rac.abort(rac.pos_text(@rantfile, @line_number),
756
+ "SourceNode: no such file -- #@name")
757
+ end
758
+ sd = project_subdir
759
+ @pre.each { |f|
760
+ nodes = rac.resolve f, sd
761
+ if nodes.empty?
762
+ if File.exist? f
763
+ mtime = File.mtime f
764
+ @ts = mtime if mtime > @ts
765
+ else
766
+ rac.abort(rac.pos_text(@rantfile, @line_number),
767
+ "SourceNode: no such file -- #{f}")
768
+ end
769
+ else
770
+ nodes.each { |node|
771
+ if node.respond_to? :timestamp
772
+ node_ts = node.timestamp
773
+ @ts = node_ts if node_ts > @ts
774
+ else
775
+ rac.abort(rac.pos_text(@rantfile, @line_number),
776
+ "SourceNode can't depend on #{node.name}")
777
+ end
778
+ }
779
+ end
780
+ }
781
+ @ts
782
+ end
783
+
784
+ def needed?
785
+ false
786
+ end
787
+
788
+ def invoke(opt = INVOKE_OPT)
789
+ false
790
+ end
791
+
792
+ end # class SourceNode
793
+
782
794
  module Generators
783
795
  Task = ::Rant::Task
784
796
  LightTask = ::Rant::LightTask
785
797
  Directory = ::Rant::DirTask
798
+ SourceNode = ::Rant::SourceNode
786
799
 
787
800
  class Rule < ::Proc
788
801
  # Generate a rule by installing an at_resolve hook for
@@ -843,7 +856,7 @@ module Rant
843
856
  end
844
857
  }
845
858
  blk.target_rx = target_rx
846
- rac.at_resolve &blk
859
+ rac.resolve_hooks << blk
847
860
  nil
848
861
  end
849
862
  attr_accessor :target_rx