rant 0.3.6 → 0.3.8

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 (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