rant 0.4.2 → 0.4.4

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 +14 -0
  2. data/README +13 -7
  3. data/Rantfile +11 -0
  4. data/doc/md5.rdoc +49 -0
  5. data/doc/rantfile.rdoc +1 -1
  6. data/lib/rant/coregen.rb +193 -0
  7. data/lib/rant/import/archive/zip.rb +2 -0
  8. data/lib/rant/import/archive.rb +10 -2
  9. data/lib/rant/import/autoclean.rb +16 -7
  10. data/lib/rant/import/c/dependencies.rb +1 -1
  11. data/lib/rant/import/directedrule.rb +2 -2
  12. data/lib/rant/import/md5.rb +16 -0
  13. data/lib/rant/import/metadata.rb +162 -0
  14. data/lib/rant/import/nodes/default.rb +490 -0
  15. data/lib/rant/import/nodes/signed.rb +84 -0
  16. data/lib/rant/import/package/zip.rb +2 -0
  17. data/lib/rant/import/rubydoc.rb +5 -1
  18. data/lib/rant/import/rubypackage.rb +2 -1
  19. data/lib/rant/import/signature/md5.rb +38 -0
  20. data/lib/rant/import/signedfile.rb +235 -0
  21. data/lib/rant/import/subfile.rb +1 -1
  22. data/lib/rant/import.rb +5 -1
  23. data/lib/rant/node.rb +165 -0
  24. data/lib/rant/plugin/csharp.rb +2 -0
  25. data/lib/rant/rantlib.rb +64 -9
  26. data/lib/rant/rantsys.rb +39 -27
  27. data/lib/rant/rantvar.rb +32 -2
  28. data/misc/TODO +66 -0
  29. data/test/import/c/dependencies/test_on_the_fly.rb +52 -0
  30. data/test/import/metadata/Rantfile +16 -0
  31. data/test/import/metadata/sub/Rantfile +17 -0
  32. data/test/import/metadata/test_metadata.rb +126 -0
  33. data/test/import/nodes/signed/Rantfile +89 -0
  34. data/test/import/nodes/signed/sub1/Rantfile +6 -0
  35. data/test/import/nodes/signed/test_signed.rb +455 -0
  36. data/test/import/package/md5.rf +10 -0
  37. data/test/import/package/test_package.rb +127 -1
  38. data/test/import/signeddirectory/Rantfile +15 -0
  39. data/test/import/signeddirectory/test_signeddirectory.rb +84 -0
  40. data/test/import/signedfile/Rantfile +90 -0
  41. data/test/import/signedfile/sub1/Rantfile +4 -0
  42. data/test/import/signedfile/test_signedfile.rb +338 -0
  43. data/test/project1/Rantfile +0 -9
  44. data/test/project1/test_project.rb +2 -0
  45. data/test/project_rb1/test_project_rb1.rb +27 -10
  46. data/test/rant-import/test_rant-import.rb +46 -9
  47. data/test/subdirs/sub2/sub/rantfile +0 -5
  48. data/test/subdirs/test_subdirs.rb +0 -9
  49. data/test/test_examples.rb +131 -3
  50. data/test/test_filelist.rb +44 -0
  51. data/test/test_sys.rb +19 -1
  52. data/test/test_task.rb +2 -2
  53. data/test/tutil.rb +9 -3
  54. metadata +34 -4
  55. data/lib/rant/rantfile.rb +0 -897
  56. data/test/test_lighttask.rb +0 -68
@@ -0,0 +1,490 @@
1
+
2
+ # default.rb - Default node types for Rant.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ module Rant
7
+
8
+ def self.init_import_nodes__default(rac, *rest)
9
+ rac.node_factory = DefaultNodeFactory.new
10
+ end
11
+
12
+ class DefaultNodeFactory
13
+ def new_task(rac, name, pre, blk)
14
+ Task.new(rac, name, pre, &blk)
15
+ end
16
+ def new_file(rac, name, pre, blk)
17
+ FileTask.new(rac, name, pre, &blk)
18
+ end
19
+ def new_dir(rac, name, pre, blk)
20
+ DirTask.new(rac, name, pre, &blk)
21
+ end
22
+ def new_source(rac, name, pre, blk)
23
+ SourceNode.new(rac, name, pre, &blk)
24
+ end
25
+ def new_custom(rac, name, pre, blk)
26
+ UserTask.new(rac, name, pre, &blk)
27
+ end
28
+ def new_auto_subfile(rac, name, pre, blk)
29
+ AutoSubFileTask.new(rac, name, pre, &blk)
30
+ end
31
+ end
32
+
33
+ class Task
34
+ include Node
35
+ include Console
36
+
37
+ def initialize(rac, name, prerequisites = [], &block)
38
+ super()
39
+ @rac = rac || Rant.rac
40
+ @name = name or raise ArgumentError, "name not given"
41
+ @pre = prerequisites || []
42
+ @pre_resolved = false
43
+ @block = block
44
+ @run = false
45
+ # success has one of three values:
46
+ # nil no invoke
47
+ # false invoked, but fail
48
+ # true invoked and run successfully
49
+ @success = nil
50
+ end
51
+
52
+ # Get a list of the *names* of all prerequisites. The
53
+ # underlying list of prerequisites can't be modified by the
54
+ # value returned by this method.
55
+ def prerequisites
56
+ @pre.collect { |pre| pre.to_s }
57
+ end
58
+ alias deps prerequisites
59
+
60
+ # First prerequisite.
61
+ def source
62
+ @pre.first.to_s
63
+ end
64
+
65
+ # True if this task has at least one action (block to be
66
+ # executed) associated.
67
+ def has_actions?
68
+ !!@block
69
+ end
70
+
71
+ # Add a prerequisite.
72
+ def <<(pre)
73
+ @pre_resolved = false
74
+ @pre << pre
75
+ end
76
+
77
+ # Was this task ever invoked? If this is true, it doesn't
78
+ # necessarily mean that the run was successfull!
79
+ def invoked?
80
+ !@success.nil?
81
+ end
82
+
83
+ # True if last task run fail.
84
+ def fail?
85
+ @success == false
86
+ end
87
+
88
+ # Task was run and didn't fail.
89
+ def done?
90
+ @success
91
+ end
92
+
93
+ # Enhance this task with the given dependencies and blk.
94
+ def enhance(deps = nil, &blk)
95
+ if deps
96
+ @pre_resolved = false
97
+ @pre.concat deps
98
+ end
99
+ if @block
100
+ if blk
101
+ first_block = @block
102
+ @block = lambda { |t|
103
+ first_block[t]
104
+ blk[t]
105
+ }
106
+ end
107
+ else
108
+ @block = blk
109
+ end
110
+ end
111
+
112
+ def needed?
113
+ invoke(:needed? => true)
114
+ end
115
+
116
+ # Returns a true value if task was acutally run.
117
+ # Raises Rant::TaskFail to signal task (or prerequiste) failure.
118
+ def invoke(opt = INVOKE_OPT)
119
+ return circular_dep if @run
120
+ @run = true
121
+ begin
122
+ return if done?
123
+ internal_invoke opt
124
+ ensure
125
+ @run = false
126
+ end
127
+ end
128
+
129
+ def internal_invoke opt, ud_init = true
130
+ goto_task_home
131
+ update = ud_init || opt[:force]
132
+ dep = nil
133
+ uf = false
134
+ each_dep { |dep|
135
+ if dep.respond_to? :timestamp
136
+ handle_timestamped(dep, opt) && update = true
137
+ elsif Node === dep
138
+ handle_node(dep, opt) && update = true
139
+ else
140
+ dep, uf = handle_non_node(dep, opt)
141
+ uf && update = true
142
+ dep
143
+ end
144
+ }
145
+ # Never run a task block for a "needed?" query.
146
+ return update if opt[:needed?]
147
+ run if update
148
+ @success = true
149
+ # IMPORTANT: return update flag
150
+ update
151
+ rescue StandardError => e
152
+ @success = false
153
+ self.fail(nil, e)
154
+ end
155
+ private :internal_invoke
156
+
157
+ # Called from internal_invoke. +dep+ is a prerequisite which
158
+ # is_a? Node, but not a FileTask. +opt+ are opts as given to
159
+ # Node#invoke.
160
+ #
161
+ # Override this method in subclasses to modify behaviour of
162
+ # prerequisite handling.
163
+ #
164
+ # See also: handle_timestamped, handle_non_node
165
+ def handle_node(dep, opt)
166
+ dep.invoke opt
167
+ end
168
+
169
+ # Called from internal_invoke. +dep+ is a prerequisite which
170
+ # is_a? FileTask. +opt+ are opts as given to Node#invoke.
171
+ #
172
+ # Override this method in subclasses to modify behaviour of
173
+ # prerequisite handling.
174
+ #
175
+ # See also: handle_node, handle_non_node
176
+ def handle_timestamped(dep, opt)
177
+ dep.invoke opt
178
+ end
179
+
180
+ # Override in subclass if specific task can handle
181
+ # non-task-prerequisites.
182
+ #
183
+ # Notes for overriding:
184
+ # This method should do one of the two following:
185
+ # [1] Fail with an exception.
186
+ # [2] Return two values: replacement_for_dep, update_required
187
+ #
188
+ # See also: handle_node, handle_timestamped
189
+ def handle_non_node(dep, opt)
190
+ err_msg "Unknown task `#{dep}',",
191
+ "referenced in `#{rantfile.path}', line #{@line_number}!"
192
+ self.fail
193
+ end
194
+
195
+ # For each non-worker prerequiste, the value returned from yield
196
+ # will replace the original prerequisite (of course only if
197
+ # @pre_resolved is false).
198
+ def each_dep
199
+ t = nil
200
+ if @pre_resolved
201
+ return @pre.each { |t| yield(t) }
202
+ end
203
+ my_full_name = full_name
204
+ my_project_subdir = project_subdir
205
+ @pre.map! { |t|
206
+ if Node === t
207
+ # Remove references to self from prerequisites!
208
+ if t.full_name == my_full_name
209
+ nil
210
+ else
211
+ yield(t)
212
+ t
213
+ end
214
+ else
215
+ t = t.to_s if Symbol === t
216
+ if t == my_full_name #TODO
217
+ nil
218
+ else
219
+ #STDERR.puts "selecting `#{t}'"
220
+ selection = @rac.resolve t,
221
+ my_project_subdir
222
+ #STDERR.puts selection.size
223
+ if selection.empty?
224
+ # use return value of yield
225
+ yield(t)
226
+ else
227
+ selection.each { |st| yield(st) }
228
+ selection
229
+ end
230
+ end
231
+ end
232
+ }
233
+ @pre.flatten!
234
+ @pre.compact!
235
+ @pre_resolved = true
236
+ end
237
+ end # class Task
238
+
239
+ # A UserTask is equivalent to a Task, but it additionally takes a
240
+ # block (see #needed) which is used to determine if it is needed?.
241
+ class UserTask < Task
242
+
243
+ def initialize(*args)
244
+ super
245
+ # super will set @block to a given block, but the block is
246
+ # used for initialization, not ment as action
247
+ @block = nil
248
+ @needed = nil
249
+ # allow setting of @block and @needed
250
+ yield self if block_given?
251
+ end
252
+
253
+ def act(&block)
254
+ @block = block
255
+ end
256
+
257
+ def needed(&block)
258
+ @needed = block
259
+ end
260
+
261
+ # We simply override this method and call internal_invoke with
262
+ # the +ud_init+ flag according to the result of a call to the
263
+ # +needed+ block.
264
+ def invoke(opt = INVOKE_OPT)
265
+ return circular_dep if @run
266
+ @run = true
267
+ begin
268
+ return if done?
269
+ internal_invoke(opt, ud_init_by_needed)
270
+ ensure
271
+ @run = false
272
+ end
273
+ end
274
+
275
+ private
276
+ def ud_init_by_needed
277
+ if @needed
278
+ goto_task_home
279
+ @needed.arity == 0 ? @needed.call : @needed[self]
280
+ #else: true #??
281
+ end
282
+ end
283
+ end # class UserTask
284
+
285
+ class FileTask < Task
286
+
287
+ def initialize(*args)
288
+ super
289
+ @ts = T0
290
+ end
291
+
292
+ def needed?
293
+ return false if done?
294
+ invoke(:needed? => true)
295
+ end
296
+
297
+ def invoke(opt = INVOKE_OPT)
298
+ return circular_dep if @run
299
+ @run = true
300
+ begin
301
+ return if done?
302
+ goto_task_home
303
+ if File.exist? @name
304
+ @ts = File.mtime @name
305
+ internal_invoke opt, false
306
+ else
307
+ @ts = T0
308
+ internal_invoke opt, true
309
+ end
310
+ ensure
311
+ @run = false
312
+ end
313
+ end
314
+
315
+ def timestamp
316
+ File.exist?(@name) ? File.mtime(@name) : T0
317
+ end
318
+
319
+ def handle_timestamped(dep, opt)
320
+ return true if dep.invoke opt
321
+ #puts "***`#{dep.name}' requires update" if dep.timestamp > @ts
322
+ dep.timestamp > @ts
323
+ end
324
+
325
+ def handle_non_node(dep, opt)
326
+ goto_task_home # !!??
327
+ unless File.exist? dep
328
+ err_msg @rac.pos_text(rantfile.path, line_number),
329
+ "in prerequisites: no such file or task: `#{dep}'"
330
+ self.fail
331
+ end
332
+ [dep, File.mtime(dep) > @ts]
333
+ end
334
+
335
+ def each_target
336
+ goto_task_home
337
+ yield name
338
+ end
339
+ end # class FileTask
340
+
341
+ module AutoInvokeDirNode
342
+ private
343
+ def run
344
+ goto_task_home
345
+ dir = File.dirname(name)
346
+ @rac.build dir unless dir == "."
347
+ return unless @block
348
+ @block.arity == 0 ? @block.call : @block[self]
349
+ end
350
+ end
351
+
352
+ class AutoSubFileTask < FileTask
353
+ include AutoInvokeDirNode
354
+ end
355
+
356
+ # An instance of this class is a task to create a _single_
357
+ # directory.
358
+ class DirTask < Task
359
+
360
+ def initialize(*args)
361
+ super
362
+ @ts = T0
363
+ @isdir = nil
364
+ end
365
+
366
+ def invoke(opt = INVOKE_OPT)
367
+ return circular_dep if @run
368
+ @run = true
369
+ begin
370
+ return if done?
371
+ goto_task_home
372
+ @isdir = test(?d, @name)
373
+ if @isdir
374
+ @ts = @block ? test(?M, @name) : Time.now
375
+ internal_invoke opt, false
376
+ else
377
+ @ts = T0
378
+ internal_invoke opt, true
379
+ end
380
+ ensure
381
+ @run = false
382
+ end
383
+ end
384
+
385
+ def handle_timestamped(dep, opt)
386
+ return @block if dep.invoke opt
387
+ @block && dep.timestamp > @ts
388
+ end
389
+
390
+ def handle_non_node(dep, opt)
391
+ goto_task_home
392
+ unless File.exist? dep
393
+ err_msg @rac.pos_text(rantfile.path, line_number),
394
+ "in prerequisites: no such file or task: `#{dep}'"
395
+ self.fail
396
+ end
397
+ [dep, @block && File.mtime(dep) > @ts]
398
+ end
399
+
400
+ def run
401
+ @rac.sys.mkdir @name unless @isdir
402
+ if @block
403
+ @block.arity == 0 ? @block.call : @block[self]
404
+ goto_task_home
405
+ @rac.sys.touch @name
406
+ end
407
+ end
408
+
409
+ def each_target
410
+ goto_task_home
411
+ yield name
412
+ end
413
+ end # class DirTask
414
+
415
+ # A SourceNode describes dependencies between source files. Thus
416
+ # there is no action attached to a SourceNode. The target should
417
+ # be an existing file as well as all dependencies.
418
+ #
419
+ # An example would be a C source file which depends on other C
420
+ # source files because of <tt>#include</tt> statements.
421
+ #
422
+ # Rantfile usage:
423
+ # gen SourceNode, "myext.c" => %w(ruby.h myext.h)
424
+ class SourceNode
425
+ include Node
426
+ def initialize(rac, name, prerequisites = [])
427
+ super()
428
+ @rac = rac
429
+ @name = name or raise ArgumentError, "name not given"
430
+ @pre = prerequisites
431
+ @run = false
432
+ # The timestamp is the latest of this file and all
433
+ # dependencies:
434
+ @ts = nil
435
+ end
436
+ # Use this readonly!
437
+ def prerequisites
438
+ @pre
439
+ end
440
+ # Note: The timestamp will only be calculated once!
441
+ def timestamp
442
+ # Circular dependencies don't generate endless
443
+ # recursion/loops because before calling the timestamp
444
+ # method of any other node, we set @ts to some non-nil
445
+ # value.
446
+ return @ts if @ts
447
+ goto_task_home
448
+ if File.exist?(@name)
449
+ @ts = File.mtime @name
450
+ else
451
+ rac.abort(rac.pos_text(@rantfile, @line_number),
452
+ "SourceNode: no such file -- #@name")
453
+ end
454
+ sd = project_subdir
455
+ @pre.each { |f|
456
+ nodes = rac.resolve f, sd
457
+ if nodes.empty?
458
+ if File.exist? f
459
+ mtime = File.mtime f
460
+ @ts = mtime if mtime > @ts
461
+ else
462
+ rac.abort(rac.pos_text(@rantfile, @line_number),
463
+ "SourceNode: no such file -- #{f}")
464
+ end
465
+ else
466
+ nodes.each { |node|
467
+ if node.respond_to? :timestamp
468
+ node_ts = node.timestamp
469
+ goto_task_home
470
+ @ts = node_ts if node_ts > @ts
471
+ else
472
+ rac.abort(rac.pos_text(@rantfile, @line_number),
473
+ "SourceNode can't depend on #{node.name}")
474
+ end
475
+ }
476
+ end
477
+ }
478
+ @ts
479
+ end
480
+ def needed?
481
+ false
482
+ end
483
+ def invoke(opt = INVOKE_OPT)
484
+ false
485
+ end
486
+ def related_sources
487
+ @pre
488
+ end
489
+ end # class SourceNode
490
+ end # module Rant
@@ -0,0 +1,84 @@
1
+
2
+ # signed.rb - "Signed" node types for Rant.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ require 'rant/import/signedfile'
7
+
8
+ module Rant
9
+ def self.init_import_nodes__signed(rac, *rest)
10
+ rac.node_factory = SignedNodeFactory.new
11
+ end
12
+ class SignedNodeFactory < DefaultNodeFactory
13
+ def new_file(rac, name, pre, blk)
14
+ Generators::SignedFile.new(rac, name, pre, &blk)
15
+ end
16
+ def new_dir(rac, name, pre, blk)
17
+ Generators::SignedDirectory.new(rac, name, pre, &blk)
18
+ end
19
+ def new_auto_subfile(rac, name, pre, blk)
20
+ Generators::AutoSubSignedFile.new(rac, name, pre, &blk)
21
+ end
22
+ def new_source(rac, name, pre, blk)
23
+ SignedSourceNode.new(rac, name, pre, &blk)
24
+ end
25
+ end
26
+ class SignedSourceNode < SourceNode
27
+ def initialize(*args)
28
+ super
29
+ @signature = nil
30
+ end
31
+ # Invokes prerequisites and returns a signature of the source
32
+ # file and all related source files.
33
+ # Note: The signature will only be calculated once.
34
+ def signature(opt = INVOKE_OPT)
35
+ return circular_dep if @run
36
+ @run = true
37
+ begin
38
+ return @signature if @signature
39
+ goto_task_home
40
+ sig_list = []
41
+ sig = @rac.var._get("__signature__")
42
+ if test(?f, @name)
43
+ @signature = sig.signature_for_file(@name)
44
+ else
45
+ @rac.abort(rac.pos_text(@rantfile, @line_number),
46
+ "SourceNode: no such file -- #@name")
47
+ end
48
+ sd = project_subdir
49
+ handled = {@name => true}
50
+ @pre.each { |f|
51
+ f = f.to_rant_target
52
+ next if handled.include? f
53
+ nodes = rac.resolve f, sd
54
+ if nodes.empty?
55
+ if test(?f, f)
56
+ sig_list << sig.signature_for_file(f)
57
+ else
58
+ rac.abort(rac.pos_text(@rantfile, @line_number),
59
+ "SourceNode: no such file -- #{f}")
60
+ end
61
+ else
62
+ file_sig = nil
63
+ nodes.each { |node|
64
+ node.invoke(opt)
65
+ if node.respond_to? :signature
66
+ sig_list << node.signature
67
+ goto_task_home
68
+ else
69
+ rac.abort(rac.pos_text(@rantfile, @line_number),
70
+ "SourceNode can't depend on #{node.name}")
71
+ end
72
+ }
73
+ sig_list << file_sig if file_sig
74
+ end
75
+ handled[f] = true
76
+ }
77
+ sig_list.sort!
78
+ @signature << sig_list.join
79
+ ensure
80
+ @run = false
81
+ end
82
+ end
83
+ end # class SignedSourceNode
84
+ end # module Rant
@@ -12,6 +12,8 @@ module Rant::Generators::Package
12
12
  fn = @dist_dirname + (@extension ? @extension : "")
13
13
  old_pwd = Dir.pwd
14
14
  Dir.chdir @dist_root
15
+ # zip adds to existing archive
16
+ @rac.cx.sys.rm_f fn if test ?e, fn
15
17
  # zip options:
16
18
  # y: store symlinks instead of referenced files
17
19
  # r: recurse into directories
@@ -65,7 +65,11 @@ module Rant
65
65
  # We delay the require of the RDoc code until it is
66
66
  # actually needed, so it will be only loaded if the
67
67
  # rdoc task has to be run.
68
- require 'rdoc/rdoc'
68
+ begin
69
+ require 'rdoc/rdoc'
70
+ rescue LoadError
71
+ app.abort_at(ch, "RDoc not available.")
72
+ end
69
73
  args = self.rdoc_args
70
74
  app.cmd_msg "rdoc #{args.join(' ')}" if @verbose
71
75
  begin
@@ -201,7 +201,8 @@ class Rant::Generators::RubyPackage
201
201
 
202
202
  def pkg_dir_task
203
203
  return if @pkg_dir_task
204
- @pkg_dir_task = @app.gen(Rant::Generators::Directory, @pkg_dir)
204
+ @pkg_dir_task =
205
+ Rant::Generators::Directory.rant_gen(@app, @ch, [@pkg_dir])
205
206
  end
206
207
 
207
208
  # Create task for gem building. If tname is a true value, a
@@ -0,0 +1,38 @@
1
+
2
+ # md5.rb - Recognize file changes by md5 checksums.
3
+ #
4
+ # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
5
+
6
+ require 'digest/md5'
7
+
8
+ module Rant
9
+ def self.init_import_signature__md5(rac, *rest)
10
+ sig = Signature::MD5.new(rac)
11
+ rac.var._set("__signature_md5__", sig)
12
+ rac.var._init("__signature__", sig)
13
+ end
14
+ module Signature
15
+ class MD5
16
+ def initialize(rac)
17
+ #@rac = rac
18
+ end
19
+ def name
20
+ "md5"
21
+ end
22
+ def signature_for_file(filename)
23
+ signature_for_string(File.read(filename))
24
+ end
25
+ def signature_for_dir(dirname)
26
+ entries = Dir.entries(dirname)
27
+ entries.sort!
28
+ signature_for_string(entries.join << entries.size.to_s)
29
+ end
30
+ def signature_for_io(io)
31
+ signature_for_string(io.read)
32
+ end
33
+ def signature_for_string(str)
34
+ Digest::MD5.hexdigest(str)
35
+ end
36
+ end # class MD5
37
+ end # module Signature
38
+ end # module Rant