Prefixing NEWS.Debian with package name for consistency reasons.
[software/sisu] / sisu-install
1 #!/usr/bin/env ruby
2
3 # sisu-install - Monolithic rant script, autogenerated by rant-import 0.5.8.
4 #
5 # Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
6 #
7 # This program is free software.
8 # You can distribute/modify this program under the terms of
9 # the GNU LGPL, Lesser General Public License version 2.1.
10
11
12 require 'getoptlong'
13
14
15 require 'rbconfig'
16
17 unless Process::Status.method_defined?(:success?) # new in 1.8.2
18 class Process::Status
19 def success?; exitstatus == 0; end
20 end
21 end
22 unless Regexp.respond_to? :union # new in 1.8.1
23 def Regexp.union(*patterns)
24 return /(?!)/ if patterns.empty?
25 Regexp.new(patterns.join("|"))
26 end
27 end
28 if RUBY_VERSION < "1.8.2"
29 class Array
30 undef_method :flatten, :flatten!
31 def flatten
32 cp = self.dup
33 cp.flatten!
34 cp
35 end
36 def flatten!
37 res = []
38 flattened = false
39 self.each { |e|
40 if e.respond_to? :to_ary
41 res.concat(e.to_ary)
42 flattened = true
43 else
44 res << e
45 end
46 }
47 if flattened
48 replace(res)
49 flatten!
50 self
51 end
52 end
53 end
54 end
55
56 class String
57 def _rant_sub_ext(ext, new_ext = nil)
58 if new_ext
59 self.sub(/#{Regexp.escape ext}$/, new_ext)
60 else
61 self.sub(/(\.[^.]*$)|$/, ".#{ext}")
62 end
63 end
64 end
65
66 module Rant
67 VERSION = '0.5.8'
68
69 @__rant_no_value__ = Object.new.freeze
70 def self.__rant_no_value__
71 @__rant_no_value__
72 end
73
74 module Env
75 OS = ::Config::CONFIG['target']
76 RUBY = ::Config::CONFIG['ruby_install_name']
77 RUBY_BINDIR = ::Config::CONFIG['bindir']
78 RUBY_EXE = File.join(RUBY_BINDIR, RUBY + ::Config::CONFIG["EXEEXT"])
79
80 @@zip_bin = false
81 @@tar_bin = false
82
83 if OS =~ /mswin/i
84 def on_windows?; true; end
85 else
86 def on_windows?; false; end
87 end
88
89 def have_zip?
90 if @@zip_bin == false
91 @@zip_bin = find_bin "zip"
92 end
93 !@@zip_bin.nil?
94 end
95 def have_tar?
96 if @@tar_bin == false
97 @@tar_bin = find_bin "tar"
98 end
99 !@@tar_bin.nil?
100 end
101 def pathes
102 path = ENV[on_windows? ? "Path" : "PATH"]
103 return [] unless path
104 path.split(on_windows? ? ";" : ":")
105 end
106 def find_bin bin_name
107 if on_windows?
108 bin_name_exe = nil
109 if bin_name !~ /\.[^\.]{1,3}$/i
110 bin_name_exe = bin_name + ".exe"
111 end
112 pathes.each { |dir|
113 file = File.join(dir, bin_name)
114 return file if test(?f, file)
115 if bin_name_exe
116 file = File.join(dir, bin_name_exe)
117 return file if test(?f, file)
118 end
119 }
120 else
121 pathes.each { |dir|
122 file = File.join(dir, bin_name)
123 return file if test(?x, file)
124 }
125 end
126 nil
127 end
128 def shell_path path
129 if on_windows?
130 path = path.tr("/", "\\")
131 if path.include? ' '
132 '"' + path + '"'
133 else
134 path
135 end
136 else
137 if path.include? ' '
138 "'" + path + "'"
139 else
140 path
141 end
142 end
143 end
144 extend self
145 end # module Env
146
147 module Sys
148 def sp(arg)
149 if arg.respond_to? :to_ary
150 arg.to_ary.map{ |e| sp e }.join(' ')
151 else
152 _escaped_path arg
153 end
154 end
155 def escape(arg)
156 if arg.respond_to? :to_ary
157 arg.to_ary.map{ |e| escape e }.join(' ')
158 else
159 _escaped arg
160 end
161 end
162 if Env.on_windows?
163 def _escaped_path(path)
164 _escaped(path.to_s.tr("/", "\\"))
165 end
166 def _escaped(arg)
167 sarg = arg.to_s
168 return sarg unless sarg.include?(" ")
169 sarg << "\\" if sarg[-1].chr == "\\"
170 "\"#{sarg}\""
171 end
172 def regular_filename(fn)
173 fn.to_str.tr("\\", "/").gsub(%r{/{2,}}, "/")
174 end
175 else
176 def _escaped_path(path)
177 path.to_s.gsub(/(?=\s)/, "\\")
178 end
179 alias _escaped _escaped_path
180 def regular_filename(fn)
181 fn.to_str.gsub(%r{/{2,}}, "/")
182 end
183 end
184 private :_escaped_path
185 private :_escaped
186 def split_all(path)
187 names = regular_filename(path).split(%r{/})
188 names[0] = "/" if names[0] && names[0].empty?
189 names
190 end
191 extend self
192 end # module Sys
193
194
195 ROOT_RANTFILE = "root.rant"
196 SUB_RANTFILE = "sub.rant"
197 RANTFILES = [ "Rantfile", "rantfile", ROOT_RANTFILE ]
198
199 CODE_IMPORTS = []
200
201 class RantAbortException < StandardError
202 end
203
204 class RantDoneException < StandardError
205 end
206
207 class Error < StandardError
208 end
209
210 module Generators
211 end
212
213 module RantVar
214
215 class Error < Rant::Error
216 end
217
218 class ConstraintError < Error
219
220 attr_reader :constraint, :val
221
222 def initialize(constraint, val, msg = nil)
223 @msg = msg
224 @constraint = constraint
225 @val = val
226 end
227
228 def message
229 val_desc = @val.inspect
230 val_desc[7..-1] = "..." if val_desc.length > 10
231 "#{val_desc} doesn't match constraint: #@constraint"
232 end
233 end
234
235 class NotAConstraintFactoryError < Error
236 attr_reader :obj
237 def initialize(obj, msg = nil)
238 @msg = msg
239 @obj = obj
240 end
241 def message
242 obj_desc = @obj.inspect
243 obj_desc[7..-1] = "..." if obj_desc.length > 10
244 "#{obj_desc} is not a valid constraint factory"
245 end
246 end
247
248 class InvalidVidError < Error
249 def initialize(vid, msg = nil)
250 @msg = msg
251 @vid = vid
252 end
253 def message
254 vid_desc = @vid.inspect
255 vid_desc[7..-1] = "..." if vid_desc.length > 10
256 "#{vid_desc} is not a valid var identifier"
257 end
258 end
259
260 class InvalidConstraintError < Error
261 end
262
263 class QueryError < Error
264 end
265
266 class Space
267
268 @@env_ref = Object.new
269
270 def initialize
271 @store = {}
272 @constraints = {}
273 end
274
275 def query(*args, &block)
276 case args.size
277 when 0
278 raise QueryError, "no arguments", caller
279 when 1
280 arg = args.first
281 if Hash === arg
282 if arg.size == 1
283 arg.each { |k,v|
284 self[k] = v if self[k].nil?
285 }
286 self
287 else
288 init_all arg
289 end
290 else
291 self[arg]
292 end
293 when 2, 3
294 vid, cf, val = *args
295 constrain vid,
296 get_factory(cf).rant_constraint
297 self[vid] = val if val
298 else
299 raise QueryError, "too many arguments"
300 end
301 end
302
303 def restrict vid, ct, *ct_args
304 if vid.respond_to? :to_ary
305 vid.to_ary.each { |v| restrict(v, ct, *ct_args) }
306 else
307 constrain vid,
308 get_factory(ct).rant_constraint(*ct_args)
309 end
310 self
311 end
312
313 def get_factory id
314 if String === id || Symbol === id
315 id = Constraints.const_get(id) rescue nil
316 end
317 unless id.respond_to? :rant_constraint
318 raise NotAConstraintFactoryError.new(id), caller
319 end
320 id
321 end
322 private :get_factory
323
324 def [](vid)
325 vid = RantVar.valid_vid vid
326 val = @store[vid]
327 val.equal?(@@env_ref) ? ENV[vid] : val
328 end
329
330 def []=(vid, val)
331 vid = RantVar.valid_vid(vid)
332 c = @constraints[vid]
333 if @store[vid] == @@env_ref
334 ENV[vid] = c ? c.filter(val) : val
335 else
336 @store[vid] = c ? c.filter(val) : val
337 end
338 end
339
340 def env(*vars)
341 vars.flatten.each { |var|
342 vid = RantVar.valid_vid(var)
343 cur_val = @store[vid]
344 next if cur_val == @@env_ref
345 ENV[vid] = cur_val unless cur_val.nil?
346 @store[vid] = @@env_ref
347 }
348 nil
349 end
350
351 def set_all hash
352 unless Hash === hash
353 raise QueryError,
354 "set_all argument has to be a hash"
355 end
356 hash.each_pair { |k, v|
357 self[k] = v
358 }
359 end
360
361 def init_all hash
362 unless Hash === hash
363 raise QueryError,
364 "init_all argument has to be a hash"
365 end
366 hash.each_pair { |k, v|
367 self[k] = v if self[k].nil?
368 }
369 end
370
371 def constrain vid, constraint
372 vid = RantVar.valid_vid(vid)
373 unless RantVar.valid_constraint? constraint
374 raise InvalidConstraintError, constraint
375 end
376 @constraints[vid] = constraint
377 if @store.member? vid
378 begin
379 val = @store[vid]
380 @store[vid] = constraint.filter(@store[vid])
381 rescue
382 @store[vid] = constraint.default
383 raise ConstraintError.new(constraint, val)
384 end
385 else
386 @store[vid] = constraint.default
387 end
388 end
389
390 def has_var?(vid)
391 !self[vid].nil?
392 end
393
394 def _set(vid, val) #:nodoc:
395 @store[vid] = val
396 end
397
398 def _get(vid) #:nodoc:
399 @store[vid]
400 end
401
402 def _init(vid, val) #:nodoc:
403 @store[vid] ||= val
404 end
405
406 end # class Space
407
408 module Constraint
409 def matches? val
410 filter val
411 true
412 rescue
413 return false
414 end
415 end
416
417 def valid_vid(obj)
418 case obj
419 when String; obj
420 when Symbol; obj.to_s
421 else
422 if obj.respond_to? :to_str
423 obj.to_str
424 else
425 raise InvalidVidError.new(obj)
426 end
427 end
428 end
429
430 def valid_constraint?(obj)
431 obj.respond_to?(:filter) &&
432 obj.respond_to?(:matches?) &&
433 obj.respond_to?(:default)
434 end
435
436 module_function :valid_constraint?, :valid_vid
437
438 module Constraints
439 class AutoList
440 include Constraint
441 class << self
442 alias rant_constraint new
443 end
444 def filter(val)
445 if val.respond_to? :to_ary
446 val.to_ary
447 elsif val.nil?
448 raise ConstraintError.new(self, val)
449 else
450 [val]
451 end
452 end
453 def default
454 []
455 end
456 def to_s
457 "list or single, non-nil value"
458 end
459 end
460 end # module Constraints
461 end # module RantVar
462 end # module Rant
463
464
465 require 'fileutils'
466
467
468 module Rant
469 def FileList(arg)
470 if arg.respond_to?(:to_rant_filelist)
471 arg.to_rant_filelist
472 elsif arg.respond_to?(:to_ary)
473 FileList.new(arg.to_ary)
474 else
475 raise TypeError,
476 "cannot convert #{arg.class} into Rant::FileList"
477 end
478 end
479 module_function :FileList
480 class FileList
481 include Enumerable
482
483 ESC_SEPARATOR = Regexp.escape(File::SEPARATOR)
484 ESC_ALT_SEPARATOR = File::ALT_SEPARATOR ?
485 Regexp.escape(File::ALT_SEPARATOR) : nil
486
487 class << self
488 def [](*patterns)
489 new.hide_dotfiles.include(*patterns)
490 end
491 def glob(*patterns)
492 fl = new.hide_dotfiles.ignore(".", "..").include(*patterns)
493 if block_given? then yield fl else fl end
494 end
495 def glob_all(*patterns)
496 fl = new.ignore(".", "..").include(*patterns)
497 if block_given? then yield fl else fl end
498 end
499 end
500
501 def initialize(store = [])
502 @pending = false
503 @def_glob_dotfiles = true
504 @items = store
505 @ignore_rx = nil
506 @keep = {}
507 @actions = []
508 end
509 alias _object_dup dup
510 private :_object_dup
511 def dup
512 c = _object_dup
513 c.items = @items.dup
514 c.actions = @actions.dup
515 c.ignore_rx = @ignore_rx.dup if @ignore_rx
516 c.instance_variable_set(:@keep, @keep.dup)
517 c
518 end
519 def copy
520 c = _object_dup
521 c.items = @items.map { |entry| entry.dup }
522 c.actions = @actions.dup
523 c.ignore_rx = @ignore_rx.dup if @ignore_rx
524 h_keep = {}
525 @keep.each_key { |entry| h_keep[entry] = true }
526 c.instance_variable_set(:@keep, h_keep)
527 c
528 end
529 def glob_dotfiles?
530 @def_glob_dotfiles
531 end
532 def glob_dotfiles=(flag)
533 @def_glob_dotfiles = flag ? true : false
534 end
535 def hide_dotfiles
536 @def_glob_dotfiles = false
537 self
538 end
539 def glob_dotfiles
540 @def_glob_dotfiles = true
541 self
542 end
543
544 protected
545 attr_accessor :actions, :items
546 attr_accessor :pending
547 attr_accessor :ignore_rx
548
549 public
550 def each(&block)
551 resolve if @pending
552 @items.each(&block)
553 self
554 end
555 def to_ary
556 resolve if @pending
557 @items
558 end
559 alias to_a to_ary
560 alias entries to_ary # entries: defined in Enumerable
561 def to_rant_filelist
562 self
563 end
564 def +(other)
565 if other.respond_to? :to_rant_filelist
566 c = other.to_rant_filelist.dup
567 c.actions.concat(@actions)
568 c.items.concat(@items)
569 c.pending = !c.actions.empty?
570 c
571 elsif other.respond_to? :to_ary
572 c = dup
573 c.actions <<
574 [:apply_ary_method_1, :concat, other.to_ary.dup]
575 c.pending = true
576 c
577 else
578 raise TypeError,
579 "cannot add #{other.class} to Rant::FileList"
580 end
581 end
582 def <<(file)
583 @actions << [:apply_ary_method_1, :push, file]
584 @keep[file] = true
585 @pending = true
586 self
587 end
588 def keep(entry)
589 @keep[entry] = true
590 @items << entry
591 self
592 end
593 def concat(ary)
594 if @pending
595 ary = ary.to_ary.dup
596 @actions << [:apply_ary_method_1, :concat, ary]
597 else
598 ix = ignore_rx and ary = ary.to_ary.reject { |f| f =~ ix }
599 @items.concat(ary)
600 end
601 self
602 end
603 def size
604 resolve if @pending
605 @items.size
606 end
607 alias length size
608 def empty?
609 resolve if @pending
610 @items.empty?
611 end
612 def join(sep = ' ')
613 resolve if @pending
614 @items.join(sep)
615 end
616 def pop
617 resolve if @pending
618 @items.pop
619 end
620 def push(entry)
621 resolve if @pending
622 @items.push(entry) if entry !~ ignore_rx
623 self
624 end
625 def shift
626 resolve if @pending
627 @items.shift
628 end
629 def unshift(entry)
630 resolve if @pending
631 @items.unshift(entry) if entry !~ ignore_rx
632 self
633 end
634 if Object.method_defined?(:fcall) || Object.method_defined?(:funcall) # in Ruby 1.9 like __send__
635 @@__send_private__ = Object.method_defined?(:fcall) ? :fcall : :funcall
636 def resolve
637 @pending = false
638 @actions.each{ |action| self.__send__(@@__send_private__, *action) }.clear
639 ix = ignore_rx
640 if ix
641 @items.reject! { |f| f =~ ix && !@keep[f] }
642 end
643 self
644 end
645 else
646 def resolve
647 @pending = false
648 @actions.each{ |action| self.__send__(*action) }.clear
649 ix = ignore_rx
650 if ix
651 @items.reject! { |f| f =~ ix && !@keep[f] }
652 end
653 self
654 end
655 end
656 def include(*pats)
657 @def_glob_dotfiles ? glob_all(*pats) : glob_unix(*pats)
658 end
659 alias glob include
660 def glob_unix(*patterns)
661 patterns.flatten.each { |pat|
662 @actions << [:apply_glob_unix, pat]
663 }
664 @pending = true
665 self
666 end
667 def glob_all(*patterns)
668 patterns.flatten.each { |pat|
669 @actions << [:apply_glob_all, pat]
670 }
671 @pending = true
672 self
673 end
674 if RUBY_VERSION < "1.8.2"
675 FN_DOTFILE_RX_ = ESC_ALT_SEPARATOR ?
676 /(^|(#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+)\..*
677 ((#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+|$)/x :
678 /(^|#{ESC_SEPARATOR}+)\..* (#{ESC_SEPARATOR}+|$)/x
679 def apply_glob_unix(pattern)
680 inc_files = Dir.glob(pattern)
681 unless pattern =~ /(^|\/)\./
682 inc_files.reject! { |fn| fn =~ FN_DOTFILE_RX_ }
683 end
684 @items.concat(inc_files)
685 end
686 else
687 def apply_glob_unix(pattern)
688 @items.concat(Dir.glob(pattern))
689 end
690 end
691 private :apply_glob_unix
692 def apply_glob_all(pattern)
693 @items.concat(Dir.glob(pattern, File::FNM_DOTMATCH))
694 end
695 private :apply_glob_all
696 def exclude(*patterns)
697 patterns.each { |pat|
698 if Regexp === pat
699 @actions << [:apply_exclude_rx, pat]
700 else
701 @actions << [:apply_exclude, pat]
702 end
703 }
704 @pending = true
705 self
706 end
707 def ignore(*patterns)
708 patterns.each { |pat|
709 add_ignore_rx(Regexp === pat ? pat : mk_all_rx(pat))
710 }
711 @pending = true
712 self
713 end
714 def add_ignore_rx(rx)
715 @ignore_rx =
716 if @ignore_rx
717 Regexp.union(@ignore_rx, rx)
718 else
719 rx
720 end
721 end
722 private :add_ignore_rx
723 def apply_exclude(pattern)
724 @items.reject! { |elem|
725 File.fnmatch?(pattern, elem, File::FNM_DOTMATCH) && !@keep[elem]
726 }
727 end
728 private :apply_exclude
729 def apply_exclude_rx(rx)
730 @items.reject! { |elem|
731 elem =~ rx && !@keep[elem]
732 }
733 end
734 private :apply_exclude_rx
735 def exclude_name(*names)
736 names.each { |name|
737 @actions << [:apply_exclude_rx, mk_all_rx(name)]
738 }
739 @pending = true
740 self
741 end
742 alias shun exclude_name
743 if File::ALT_SEPARATOR
744 def mk_all_rx(file)
745 /(^|(#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+)#{Regexp.escape(file)}
746 ((#{ESC_SEPARATOR}|#{ESC_ALT_SEPARATOR})+|$)/x
747 end
748 else
749 def mk_all_rx(file)
750 /(^|#{ESC_SEPARATOR}+)#{Regexp.escape(file)}
751 (#{ESC_SEPARATOR}+|$)/x
752 end
753 end
754 private :mk_all_rx
755 def exclude_path(*patterns)
756 patterns.each { |pat|
757 @actions << [:apply_exclude_path, pat]
758 }
759 @pending = true
760 self
761 end
762 def apply_exclude_path(pattern)
763 flags = File::FNM_DOTMATCH|File::FNM_PATHNAME
764 @items.reject! { |elem|
765 File.fnmatch?(pattern, elem, flags) && !@keep[elem]
766 }
767 end
768 private :apply_exclude
769 def select(&block)
770 d = dup
771 d.actions << [:apply_select, block]
772 d.pending = true
773 d
774 end
775 alias find_all select
776 def apply_select blk
777 @items = @items.select(&blk)
778 end
779 private :apply_select
780 def map(&block)
781 d = dup
782 d.actions << [:apply_ary_method, :map!, block]
783 d.pending = true
784 d
785 end
786 alias collect map
787 def sub_ext(ext, new_ext=nil)
788 map { |f| f._rant_sub_ext ext, new_ext }
789 end
790 def ext(ext_str)
791 sub_ext(ext_str)
792 end
793 def arglist
794 Rant::Sys.sp to_ary
795 end
796 alias to_s arglist
797 alias object_inspect inspect
798 def uniq!
799 @actions << [:apply_ary_method, :uniq!]
800 @pending = true
801 self
802 end
803 def sort!
804 @actions << [:apply_ary_method, :sort!]
805 @pending = true
806 self
807 end
808 def map!(&block)
809 @actions << [:apply_ary_method, :map!, block]
810 @pending = true
811 self
812 end
813 def reject!(&block)
814 @actions << [:apply_ary_method, :reject!, block]
815 @pending = true
816 self
817 end
818 private
819 def apply_ary_method(meth, block=nil)
820 @items.send meth, &block
821 end
822 def apply_ary_method_1(meth, arg1, block=nil)
823 @items.send meth, arg1, &block
824 end
825 end # class FileList
826 end # module Rant
827
828 if RUBY_VERSION == "1.8.3"
829 module FileUtils
830 METHODS = singleton_methods - %w(private_module_function
831 commands options have_option? options_of collect_method)
832 module Verbose
833 class << self
834 public(*::FileUtils::METHODS)
835 end
836 public(*::FileUtils::METHODS)
837 end
838 end
839 end
840
841 if RUBY_VERSION < "1.8.1"
842 module FileUtils
843 undef_method :fu_list
844 def fu_list(arg)
845 arg.respond_to?(:to_ary) ? arg.to_ary : [arg]
846 end
847 end
848 end
849
850 module Rant
851 class RacFileList < FileList
852
853 attr_reader :subdir
854 attr_reader :basedir
855
856 def initialize(rac, store = [])
857 super(store)
858 @rac = rac
859 @subdir = @rac.current_subdir
860 @basedir = Dir.pwd
861 @ignore_hash = nil
862 @add_ignore_args = []
863 update_ignore_rx
864 end
865 def dup
866 c = super
867 c.instance_variable_set(
868 :@add_ignore_args, @add_ignore_args.dup)
869 c
870 end
871 def copy
872 c = super
873 c.instance_variable_set(
874 :@add_ignore_args, @add_ignore_args.map { |e| e.dup })
875 c
876 end
877 alias filelist_ignore ignore
878 def ignore(*patterns)
879 @add_ignore_args.concat patterns
880 self
881 end
882 def ignore_rx
883 update_ignore_rx
884 @ignore_rx
885 end
886 alias filelist_resolve resolve
887 def resolve
888 Sys.cd(@basedir) { filelist_resolve }
889 end
890 def each_cd(&block)
891 old_pwd = Dir.pwd
892 Sys.cd(@basedir)
893 filelist_resolve if @pending
894 @items.each(&block)
895 ensure
896 Sys.cd(old_pwd)
897 end
898 private
899 def update_ignore_rx
900 ri = @rac.var[:ignore]
901 ri = ri ? (ri + @add_ignore_args) : @add_ignore_args
902 rh = ri.hash
903 unless rh == @ignore_hash
904 @ignore_rx = nil
905 filelist_ignore(*ri)
906 @ignore_hash = rh
907 end
908 end
909 end # class RacFileList
910
911 class MultiFileList
912
913 attr_reader :cur_list
914
915 def initialize(rac)
916 @rac = rac
917 @cur_list = RacFileList.new(@rac)
918 @lists = [@cur_list]
919 end
920
921 def each_entry(&block)
922 @lists.each { |list|
923 list.each_cd(&block)
924 }
925 end
926
927 def add(filelist)
928 @cur_list = filelist
929 @lists << filelist
930 self
931 end
932
933 def method_missing(sym, *args, &block)
934 if @cur_list && @cur_list.respond_to?(sym)
935 if @cur_list.subdir == @rac.current_subdir
936 @cur_list.send(sym, *args, &block)
937 else
938 add(RacFileList.new(@rac))
939 @cur_list.send(sym, *args, &block)
940 end
941 else
942 super
943 end
944 end
945 end # class MultiFileList
946
947 class CommandError < StandardError
948 attr_reader :cmd
949 attr_reader :status
950 def initialize(cmd, status=nil, msg=nil)
951 @msg = msg
952 @cmd = cmd
953 @status = status
954 end
955 def message
956 if !@msg && cmd
957 if status
958 "Command failed with status #{status.exitstatus}:\n" +
959 "[#{cmd}]"
960 else
961 "Command failed:\n[#{cmd}]"
962 end
963 else
964 @msg
965 end
966 end
967 end
968
969 module Sys
970 include ::FileUtils::Verbose
971
972 @symlink_supported = true
973 class << self
974 attr_accessor :symlink_supported
975 end
976
977 def fu_output_message(msg) #:nodoc:
978 end
979 private :fu_output_message
980
981 def fu_each_src_dest(src, *rest)
982 src = src.to_ary if src.respond_to? :to_ary
983 super(src, *rest)
984 end
985 private :fu_each_src_dest
986
987 def sh(*cmd_args, &block)
988 cmd_args.flatten!
989 cmd = cmd_args.join(" ")
990 fu_output_message cmd
991 success = system(*cmd_args)
992 if block_given?
993 block[$?]
994 elsif !success
995 raise CommandError.new(cmd, $?)
996 end
997 end
998
999 def ruby(*args, &block)
1000 if args.empty?
1001 sh(Env::RUBY_EXE, '', &block)
1002 else
1003 sh(args.unshift(Env::RUBY_EXE), &block)
1004 end
1005 end
1006 def cd(dir, &block)
1007 fu_output_message "cd #{dir}"
1008 orig_pwd = Dir.pwd
1009 Dir.chdir dir
1010 if block
1011 begin
1012 block.arity == 0 ? block.call : block.call(Dir.pwd)
1013 ensure
1014 fu_output_message "cd -"
1015 Dir.chdir orig_pwd
1016 end
1017 else
1018 self
1019 end
1020 end
1021
1022 def safe_ln(src, dest)
1023 dest = dest.to_str
1024 src = src.respond_to?(:to_ary) ? src.to_ary : src.to_str
1025 unless Sys.symlink_supported
1026 cp(src, dest)
1027 else
1028 begin
1029 ln(src, dest)
1030 rescue Exception # SystemCallError # Errno::EOPNOTSUPP
1031 Sys.symlink_supported = false
1032 cp(src, dest)
1033 end
1034 end
1035 end
1036
1037 def ln_f(src, dest)
1038 ln(src, dest, :force => true)
1039 end
1040
1041 def split_path(str)
1042 str.split(Env.on_windows? ? ";" : ":")
1043 end
1044
1045 if Env.on_windows?
1046 def root_dir?(path)
1047 path == "/" || path == "\\" ||
1048 path =~ %r{\A[a-zA-Z]+:(\\|/)\Z}
1049 end
1050 def absolute_path?(path)
1051 path =~ %r{\A([a-zA-Z]+:)?(/|\\)}
1052 end
1053 else
1054 def root_dir?(path)
1055 path == "/"
1056 end
1057 def absolute_path?(path)
1058 path =~ %r{\A/}
1059 end
1060 end
1061
1062 extend self
1063
1064 if RUBY_VERSION >= "1.8.4" # needed by 1.9.0, too
1065 class << self
1066 public(*::FileUtils::METHODS)
1067 end
1068 public(*::FileUtils::METHODS)
1069 end
1070
1071 end # module Sys
1072
1073 class SysObject
1074 include Sys
1075 def initialize(rant)
1076 @rant = rant or
1077 raise ArgumentError, "rant application required"
1078 end
1079 def ignore(*patterns)
1080 @rant.var[:ignore].concat(patterns)
1081 nil
1082 end
1083 def filelist(arg = Rant.__rant_no_value__)
1084 if Rant.__rant_no_value__.equal?(arg)
1085 RacFileList.new(@rant)
1086 elsif arg.respond_to?(:to_rant_filelist)
1087 arg.to_rant_filelist
1088 elsif arg.respond_to?(:to_ary)
1089 RacFileList.new(@rant, arg.to_ary)
1090 else
1091 raise TypeError,
1092 "cannot convert #{arg.class} into Rant::FileList"
1093 end
1094 end
1095 def [](*patterns)
1096 RacFileList.new(@rant).hide_dotfiles.include(*patterns)
1097 end
1098 def glob(*patterns, &block)
1099 fl = RacFileList.new(@rant).hide_dotfiles.include(*patterns)
1100 fl.ignore(".", "..")
1101 if block_given? then yield fl else fl end
1102 end
1103 def glob_all(*patterns, &block)
1104 fl = RacFileList.new(@rant).include(*patterns)
1105 fl.ignore(".", "..") # use case: "*.*" as pattern
1106 if block_given? then yield fl else fl end
1107 end
1108 def expand_path(path)
1109 File.expand_path(@rant.project_to_fs_path(path))
1110 end
1111 private
1112 def fu_output_message(cmd)
1113 @rant.cmd_msg cmd
1114 end
1115 end
1116
1117
1118 class TaskFail < StandardError
1119 def initialize(task, orig, msg)
1120 @task = task
1121 @orig = orig
1122 @msg = msg
1123 end
1124 def exception
1125 self
1126 end
1127 def task
1128 @task
1129 end
1130 def tname
1131 @task ? @task.name : nil
1132 end
1133 def orig
1134 @orig
1135 end
1136 def msg
1137 @msg
1138 end
1139 end
1140
1141 class Rantfile
1142 attr_reader :tasks, :path
1143 attr_accessor :project_subdir
1144 def initialize(path)
1145 @path = path or raise ArgumentError, "path required"
1146 @tasks = []
1147 @project_subdir = nil
1148 end
1149 alias to_s path
1150 alias to_str path
1151 end # class Rantfile
1152
1153 module Node
1154
1155 INVOKE_OPT = {}.freeze
1156
1157 T0 = Time.at(0).freeze
1158
1159 attr_reader :name
1160 attr_reader :rac
1161 attr_accessor :description
1162 attr_accessor :rantfile
1163 attr_accessor :line_number
1164 attr_accessor :project_subdir
1165
1166 def initialize
1167 @description = nil
1168 @rantfile = nil
1169 @line_number = nil
1170 @run = false
1171 @project_subdir = ""
1172 @success = nil
1173 end
1174
1175 def reference_name
1176 sd = rac.current_subdir
1177 case sd
1178 when ""; full_name
1179 when project_subdir; name
1180 else "@#{full_name}".sub(/^@#{Regexp.escape sd}\//, '')
1181 end
1182 end
1183
1184 alias to_s reference_name
1185 alias to_rant_target name
1186
1187 def full_name
1188 sd = project_subdir
1189 sd.empty? ? name : File.join(sd, name)
1190 end
1191
1192 def ch
1193 {:file => rantfile.to_str, :ln => line_number}
1194 end
1195
1196 def goto_task_home
1197 @rac.goto_project_dir project_subdir
1198 end
1199
1200 def file_target?
1201 false
1202 end
1203
1204 def done?
1205 @success
1206 end
1207
1208 def needed?
1209 invoke(:needed? => true)
1210 end
1211
1212 def run?
1213 @run
1214 end
1215
1216 def invoke(opt = INVOKE_OPT)
1217 return circular_dep if run?
1218 @run = true
1219 begin
1220 return !done? if opt[:needed?]
1221 self.run if !done?
1222 @success = true
1223 ensure
1224 @run = false
1225 end
1226 end
1227
1228 def fail msg = nil, orig = nil
1229 raise TaskFail.new(self, orig, msg)
1230 end
1231
1232 def each_target
1233 end
1234
1235 def has_actions?
1236 defined? @block and @block
1237 end
1238
1239 def dry_run
1240 text = "Executing #{name.dump}"
1241 text << " [NOOP]" unless has_actions?
1242 @rac.cmd_msg text
1243 action_descs.each { |ad|
1244 @rac.cmd_print " - "
1245 @rac.cmd_msg ad.sub(/\n$/, '').gsub(/\n/, "\n ")
1246 }
1247 end
1248
1249 private
1250 def run
1251 goto_task_home
1252 return if @rac.running_task(self)
1253 return unless has_actions?
1254 @receiver.pre_run(self) if defined? @receiver and @receiver
1255 @block.arity == 0 ? @block.call : @block[self] if @block
1256 end
1257
1258 def action_descs
1259 descs = []
1260 if defined? @receiver and @receiver
1261 descs.concat(@receiver.pre_action_descs)
1262 end
1263 @block ? descs << action_block_desc : descs
1264 end
1265
1266 def action_block_desc
1267 @block.inspect =~ /^#<Proc:[\da-z]+@(.+):(\d+)>$/i
1268 fn, ln = $1, $2
1269 "Ruby Proc at #{fn.sub(/^#{Regexp.escape @rac.rootdir}\//, '')}:#{ln}"
1270 end
1271
1272 def circular_dep
1273 rac.warn_msg "Circular dependency on task `#{full_name}'."
1274 false
1275 end
1276 end # module Node
1277
1278
1279 def self.init_import_nodes__default(rac, *rest)
1280 rac.node_factory = DefaultNodeFactory.new
1281 end
1282
1283 class DefaultNodeFactory
1284 def new_task(rac, name, pre, blk)
1285 Task.new(rac, name, pre, &blk)
1286 end
1287 def new_file(rac, name, pre, blk)
1288 FileTask.new(rac, name, pre, &blk)
1289 end
1290 def new_dir(rac, name, pre, blk)
1291 DirTask.new(rac, name, pre, &blk)
1292 end
1293 def new_source(rac, name, pre, blk)
1294 SourceNode.new(rac, name, pre, &blk)
1295 end
1296 def new_custom(rac, name, pre, blk)
1297 UserTask.new(rac, name, pre, &blk)
1298 end
1299 def new_auto_subfile(rac, name, pre, blk)
1300 AutoSubFileTask.new(rac, name, pre, &blk)
1301 end
1302 end
1303
1304 class Task
1305 include Node
1306
1307 attr_accessor :receiver
1308
1309 def initialize(rac, name, prerequisites = [], &block)
1310 super()
1311 @rac = rac or raise ArgumentError, "rac not given"
1312 @name = name or raise ArgumentError, "name not given"
1313 @pre = prerequisites || []
1314 @pre_resolved = false
1315 @block = block
1316 @run = false
1317 @receiver = nil
1318 end
1319
1320 def prerequisites
1321 @pre.collect { |pre| pre.to_s }
1322 end
1323 alias deps prerequisites
1324
1325 def source
1326 @pre.first.to_s
1327 end
1328
1329 def has_actions?
1330 @block or @receiver && @receiver.has_pre_action?
1331 end
1332
1333 def <<(pre)
1334 @pre_resolved = false
1335 @pre << pre
1336 end
1337
1338 def invoked?
1339 !@success.nil?
1340 end
1341
1342 def fail?
1343 @success == false
1344 end
1345
1346 def enhance(deps = nil, &blk)
1347 if deps
1348 @pre_resolved = false
1349 @pre.concat deps
1350 end
1351 if @block
1352 if blk
1353 first_block = @block
1354 @block = lambda { |t|
1355 first_block[t]
1356 blk[t]
1357 }
1358 end
1359 else
1360 @block = blk
1361 end
1362 end
1363
1364 def invoke(opt = INVOKE_OPT)
1365 return circular_dep if @run
1366 @run = true
1367 begin
1368 return if done?
1369 internal_invoke opt
1370 ensure
1371 @run = false
1372 end
1373 end
1374
1375 def internal_invoke(opt, ud_init = true)
1376 goto_task_home
1377 update = ud_init || opt[:force]
1378 dep = nil
1379 uf = false
1380 each_dep { |dep|
1381 if dep.respond_to? :timestamp
1382 handle_timestamped(dep, opt) && update = true
1383 elsif Node === dep
1384 handle_node(dep, opt) && update = true
1385 else
1386 dep, uf = handle_non_node(dep, opt)
1387 uf && update = true
1388 dep
1389 end
1390 }
1391 if @receiver
1392 goto_task_home
1393 update = true if @receiver.update?(self)
1394 end
1395 return update if opt[:needed?]
1396 run if update
1397 @success = true
1398 update
1399 rescue StandardError => e
1400 @success = false
1401 self.fail(nil, e)
1402 end
1403 private :internal_invoke
1404
1405 def handle_node(dep, opt)
1406 dep.invoke opt
1407 end
1408
1409 def handle_timestamped(dep, opt)
1410 dep.invoke opt
1411 end
1412
1413 def handle_non_node(dep, opt)
1414 @rac.err_msg "Unknown task `#{dep}',",
1415 "referenced in `#{rantfile.path}', line #{@line_number}!"
1416 self.fail
1417 end
1418
1419 def each_dep
1420 t = nil
1421 if @pre_resolved
1422 return @pre.each { |t| yield(t) }
1423 end
1424 my_full_name = full_name
1425 my_project_subdir = project_subdir
1426 @pre.map! { |t|
1427 if Node === t
1428 if t.full_name == my_full_name
1429 nil
1430 else
1431 yield(t)
1432 t
1433 end
1434 else
1435 t = t.to_s if Symbol === t
1436 if t == my_full_name #TODO
1437 nil
1438 else
1439 selection = @rac.resolve t,
1440 my_project_subdir
1441 if selection.empty?
1442 yield(t)
1443 else
1444 selection.each { |st| yield(st) }
1445 selection
1446 end
1447 end
1448 end
1449 }
1450 if @pre.kind_of? Rant::FileList
1451 @pre.resolve
1452 else
1453 @pre.flatten!
1454 @pre.compact!
1455 end
1456 @pre_resolved = true
1457 end
1458 end # class Task
1459
1460 class UserTask < Task
1461
1462 def initialize(*args)
1463 super
1464 @block = nil
1465 @needed = nil
1466 @target_files = nil
1467 yield self if block_given?
1468 end
1469
1470 def act(&block)
1471 @block = block
1472 end
1473
1474 def needed(&block)
1475 @needed = block
1476 end
1477
1478 def file_target?
1479 @target_files and @target_files.include? @name
1480 end
1481
1482 def each_target(&block)
1483 goto_task_home
1484 @target_files.each(&block) if @target_files
1485 end
1486
1487 def file_target(*args)
1488 args.flatten!
1489 args << @name if args.empty?
1490 if @target_files
1491 @target_files.concat(args)
1492 else
1493 @target_files = args
1494 end
1495 end
1496
1497 def invoke(opt = INVOKE_OPT)
1498 return circular_dep if @run
1499 @run = true
1500 begin
1501 return if done?
1502 internal_invoke(opt, ud_init_by_needed)
1503 ensure
1504 @run = false
1505 end
1506 end
1507
1508 private
1509 def ud_init_by_needed
1510 if @needed
1511 goto_task_home
1512 @needed.arity == 0 ? @needed.call : @needed[self]
1513 end
1514 end
1515 end # class UserTask
1516
1517 class FileTask < Task
1518
1519 def initialize(*args)
1520 super
1521 @ts = T0
1522 end
1523
1524 def file_target?
1525 true
1526 end
1527
1528 def invoke(opt = INVOKE_OPT)
1529 return circular_dep if @run
1530 @run = true
1531 begin
1532 return if done?
1533 goto_task_home
1534 if File.exist? @name
1535 @ts = File.mtime @name
1536 internal_invoke opt, false
1537 else
1538 @ts = T0
1539 internal_invoke opt, true
1540 end
1541 ensure
1542 @run = false
1543 end
1544 end
1545
1546 def timestamp(opt = INVOKE_OPT)
1547 File.exist?(@name) ? File.mtime(@name) : T0
1548 end
1549
1550 def handle_node(dep, opt)
1551 return true if dep.file_target? && dep.invoke(opt)
1552 if File.exist? dep.name
1553 File.mtime(dep.name) > @ts
1554 elsif !dep.file_target?
1555 @rac.err_msg @rac.pos_text(rantfile.path, line_number),
1556 "in prerequisites: no such file: `#{dep.full_name}'"
1557 self.fail
1558 end
1559 end
1560
1561 def handle_timestamped(dep, opt)
1562 return true if dep.invoke opt
1563 dep.timestamp(opt) > @ts
1564 end
1565
1566 def handle_non_node(dep, opt)
1567 goto_task_home # !!??
1568 unless File.exist? dep
1569 @rac.err_msg @rac.pos_text(rantfile.path, line_number),
1570 "in prerequisites: no such file or task: `#{dep}'"
1571 self.fail
1572 end
1573 [dep, File.mtime(dep) > @ts]
1574 end
1575
1576 def each_target
1577 goto_task_home
1578 yield name
1579 end
1580 end # class FileTask
1581
1582 module AutoInvokeDirNode
1583 private
1584 def run
1585 goto_task_home
1586 return if @rac.running_task(self)
1587 dir = File.dirname(name)
1588 @rac.build dir unless dir == "." || dir == "/"
1589 return unless @block
1590 @block.arity == 0 ? @block.call : @block[self]
1591 end
1592 end
1593
1594 class AutoSubFileTask < FileTask
1595 include AutoInvokeDirNode
1596 end
1597
1598 class DirTask < Task
1599
1600 def initialize(*args)
1601 super
1602 @ts = T0
1603 @isdir = nil
1604 end
1605
1606 def invoke(opt = INVOKE_OPT)
1607 return circular_dep if @run
1608 @run = true
1609 begin
1610 return if done?
1611 goto_task_home
1612 @isdir = test(?d, @name)
1613 if @isdir
1614 @ts = @block ? test(?M, @name) : Time.now
1615 internal_invoke opt, false
1616 else
1617 @ts = T0
1618 internal_invoke opt, true
1619 end
1620 ensure
1621 @run = false
1622 end
1623 end
1624
1625 def file_target?
1626 true
1627 end
1628
1629 def handle_node(dep, opt)
1630 return true if dep.file_target? && dep.invoke(opt)
1631 if File.exist? dep.name
1632 File.mtime(dep.name) > @ts
1633 elsif !dep.file_target?
1634 @rac.err_msg @rac.pos_text(rantfile.path, line_number),
1635 "in prerequisites: no such file: `#{dep.full_name}'"
1636 self.fail
1637 end
1638 end
1639
1640 def handle_timestamped(dep, opt)
1641 return @block if dep.invoke opt
1642 @block && dep.timestamp(opt) > @ts
1643 end
1644
1645 def handle_non_node(dep, opt)
1646 goto_task_home
1647 unless File.exist? dep
1648 @rac.err_msg @rac.pos_text(rantfile.path, line_number),
1649 "in prerequisites: no such file or task: `#{dep}'"
1650 self.fail
1651 end
1652 [dep, @block && File.mtime(dep) > @ts]
1653 end
1654
1655 def run
1656 return if @rac.running_task(self)
1657 @rac.sys.mkdir @name unless @isdir
1658 if @block
1659 @block.arity == 0 ? @block.call : @block[self]
1660 goto_task_home
1661 @rac.sys.touch @name
1662 end
1663 end
1664
1665 def each_target
1666 goto_task_home
1667 yield name
1668 end
1669 end # class DirTask
1670
1671 class SourceNode
1672 include Node
1673 def initialize(rac, name, prerequisites = [])
1674 super()
1675 @rac = rac
1676 @name = name or raise ArgumentError, "name not given"
1677 @pre = prerequisites
1678 @run = false
1679 @ts = nil
1680 end
1681 def prerequisites
1682 @pre
1683 end
1684 def timestamp(opt = INVOKE_OPT)
1685 return @ts if @ts
1686 goto_task_home
1687 if File.exist?(@name)
1688 @ts = File.mtime @name
1689 else
1690 rac.abort_at(ch, "SourceNode: no such file -- #@name")
1691 end
1692 sd = project_subdir
1693 @pre.each { |f|
1694 nodes = rac.resolve f, sd
1695 if nodes.empty?
1696 if File.exist? f
1697 mtime = File.mtime f
1698 @ts = mtime if mtime > @ts
1699 else
1700 rac.abort_at(ch,
1701 "SourceNode: no such file -- #{f}")
1702 end
1703 else
1704 nodes.each { |node|
1705 node.invoke(opt)
1706 if node.respond_to? :timestamp
1707 node_ts = node.timestamp(opt)
1708 goto_task_home
1709 @ts = node_ts if node_ts > @ts
1710 else
1711 rac.abort_at(ch,
1712 "SourceNode can't depend on #{node.name}")
1713 end
1714 }
1715 end
1716 }
1717 @ts
1718 end
1719 def invoke(opt = INVOKE_OPT)
1720 false
1721 end
1722 def related_sources
1723 @pre
1724 end
1725 end # class SourceNode
1726
1727 module Generators
1728 class Task
1729 def self.rant_gen(rac, ch, args, &block)
1730 unless args.size == 1
1731 rac.abort("Task takes only one argument " +
1732 "which has to be like one given to the " +
1733 "`task' function")
1734 end
1735 rac.prepare_task(args.first, nil, ch) { |name,pre,blk|
1736 rac.node_factory.new_custom(rac, name, pre, block)
1737 }
1738 end
1739 end
1740 class Directory
1741 def self.rant_gen(rac, ch, args, &block)
1742 case args.size
1743 when 1
1744 name, pre = rac.normalize_task_arg(args.first, ch)
1745 self.task(rac, ch, name, pre, &block)
1746 when 2
1747 basedir = args.shift
1748 if basedir.respond_to? :to_str
1749 basedir = basedir.to_str
1750 else
1751 rac.abort_at(ch,
1752 "Directory: basedir argument has to be a string.")
1753 end
1754 name, pre = rac.normalize_task_arg(args.first, ch)
1755 self.task(rac, ch, name, pre, basedir, &block)
1756 else
1757 rac.abort_at(ch, "Directory takes one argument, " +
1758 "which should be like one given to the `task' command.")
1759 end
1760 end
1761
1762 def self.task(rac, ch, name, prerequisites=[], basedir=nil, &block)
1763 dirs = ::Rant::Sys.split_all(name)
1764 if dirs.empty?
1765 rac.abort_at(ch,
1766 "Not a valid directory name: `#{name}'")
1767 end
1768 path = basedir
1769 last_task = nil
1770 task_block = nil
1771 desc_for_last = rac.pop_desc
1772 dirs.each { |dir|
1773 pre = [path]
1774 pre.compact!
1775 if dir.equal?(dirs.last)
1776 rac.cx.desc desc_for_last
1777
1778 dp = prerequisites.dup
1779 pre.each { |elem| dp << elem }
1780 pre = dp
1781
1782 task_block = block
1783 end
1784 path = path.nil? ? dir : File.join(path, dir)
1785 last_task = rac.prepare_task({:__caller__ => ch,
1786 path => pre}, task_block) { |name,pre,blk|
1787 rac.node_factory.new_dir(rac, name, pre, blk)
1788 }
1789 }
1790 last_task
1791 end
1792 end # class Directory
1793 class SourceNode
1794 def self.rant_gen(rac, ch, args)
1795 unless args.size == 1
1796 rac.abort_at(ch, "SourceNode takes one argument.")
1797 end
1798 if block_given?
1799 rac.abort_at(ch, "SourceNode doesn't take a block.")
1800 end
1801 rac.prepare_task(args.first, nil, ch) { |name, pre, blk|
1802 rac.node_factory.new_source(rac, name, pre, blk)
1803 }
1804 end
1805 end
1806 class Rule
1807 def self.rant_gen(rac, ch, args, &block)
1808 unless args.size == 1
1809 rac.abort_at(ch, "Rule takes only one argument.")
1810 end
1811 rac.abort_at(ch, "Rule: block required.") unless block
1812 arg = args.first
1813 target = nil
1814 src_arg = nil
1815 if Symbol === arg
1816 target = ".#{arg}"
1817 elsif arg.respond_to? :to_str
1818 target = arg.to_str
1819 elsif Regexp === arg
1820 target = arg
1821 elsif Hash === arg && arg.size == 1
1822 arg.each_pair { |target, src_arg| }
1823 src_arg = src_arg.to_str if src_arg.respond_to? :to_str
1824 target = target.to_str if target.respond_to? :to_str
1825 src_arg = ".#{src_arg}" if Symbol === src_arg
1826 target = ".#{target}" if Symbol === target
1827 else
1828 rac.abort_at(ch, "Rule argument " +
1829 "has to be a hash with one key-value pair.")
1830 end
1831 esc_target = nil
1832 target_rx = case target
1833 when String
1834 esc_target = Regexp.escape(target)
1835 /#{esc_target}$/
1836 when Regexp
1837 target
1838 else
1839 rac.abort_at(ch, "rule target has " +
1840 "to be a string or regular expression")
1841 end
1842 src_proc = case src_arg
1843 when String, Array
1844 unless String === target
1845 rac.abort(ch, "rule target has to be " +
1846 "a string if source is a string")
1847 end
1848 if src_arg.kind_of? String
1849 lambda { |name|
1850 name.sub(/#{esc_target}$/, src_arg)
1851 }
1852 else
1853 lambda { |name|
1854 src_arg.collect { |s_src|
1855 s_src = ".#{s_src}" if Symbol === s_src
1856 name.sub(/#{esc_target}$/, s_src)
1857 }
1858 }
1859 end
1860 when Proc; src_arg
1861 when nil; lambda { |name| [] }
1862 else
1863 rac.abort_at(ch, "rule source has to be a " +
1864 "String, Array or Proc")
1865 end
1866 rac.resolve_hooks <<
1867 (block.arity == 2 ? Hook : FileHook).new(
1868 rac, ch, target_rx, src_proc, block)
1869 nil
1870 end
1871 class Hook
1872 attr_accessor :target_rx
1873 def initialize(rant, ch, target_rx, src_proc, block)
1874 @rant = rant
1875 @ch = ch
1876 @target_rx = target_rx
1877 @src_proc = src_proc
1878 @block = block
1879 end
1880 def call(target, rel_project_dir)
1881 if @target_rx =~ target
1882 have_src = true
1883 src = @src_proc[target]
1884 if src.respond_to? :to_ary
1885 have_src = src.to_ary.all? { |s|
1886 have_src?(rel_project_dir, s)
1887 }
1888 else
1889 have_src = have_src?(rel_project_dir, src)
1890 end
1891 if have_src
1892 create_nodes(rel_project_dir, target, src)
1893 end
1894 end
1895 end
1896 alias [] call
1897 private
1898 def have_src?(rel_project_dir, name)
1899 return true unless
1900 @rant.rec_save_resolve(name, self, rel_project_dir).empty?
1901 test(?e, @rant.abs_path(rel_project_dir, name))
1902 end
1903 def create_nodes(rel_project_dir, target, deps)
1904 @rant.goto_project_dir rel_project_dir
1905 case nodes = @block[target, deps]
1906 when Array; nodes
1907 when Node; [nodes]
1908 else
1909 @rant.abort_at(@ch, "Block has to " +
1910 "return Node or array of Nodes.")
1911 end
1912 end
1913 end
1914 class FileHook < Hook
1915 private
1916 def have_src?(rel_project_dir, name)
1917 test(?e, @rant.abs_path(rel_project_dir, name)) or
1918 @rant.rec_save_resolve(name, self, rel_project_dir
1919 ).any? { |t| t.file_target? }
1920 end
1921 def create_nodes(rel_project_dir, target, deps)
1922 @rant.goto_project_dir rel_project_dir
1923 t = @rant.file(:__caller__ => @ch,
1924 target => deps, &@block)
1925 [t]
1926 end
1927 end
1928 end # class Rule
1929 class Action
1930 def self.rant_gen(rac, ch, args, &block)
1931 case args.size
1932 when 0
1933 unless (rac[:tasks] || rac[:stop_after_load])
1934 yield
1935 end
1936 when 1
1937 rx = args.first
1938 unless rx.kind_of? Regexp
1939 rac.abort_at(ch, "Action: argument has " +
1940 "to be a regular expression.")
1941 end
1942 rac.resolve_hooks << self.new(rac, block, rx)
1943 nil
1944 else
1945 rac.abort_at(ch, "Action: too many arguments.")
1946 end
1947 end
1948 def initialize(rant, block, rx)
1949 @rant = rant
1950 @subdir = @rant.current_subdir
1951 @block = block
1952 @rx = rx
1953 end
1954 def call(target, rel_project_dir)
1955 if target =~ @rx
1956 @rant.resolve_hooks.delete(self)
1957 @rant.goto_project_dir @subdir
1958 @block.call
1959 @rant.resolve(target, rel_project_dir)
1960 end
1961 end
1962 alias [] call
1963 end
1964 end # module Generators
1965 end # module Rant
1966
1967 Rant::MAIN_OBJECT = self
1968
1969 class String
1970 alias sub_ext _rant_sub_ext
1971 def to_rant_target
1972 self
1973 end
1974 end
1975
1976 module Rant::Lib
1977 def parse_caller_elem(elem)
1978 return { :file => "", :ln => 0 } unless elem
1979 if elem =~ /^(.+):(\d+)(?::|$)/
1980 { :file => $1, :ln => $2.to_i }
1981 else
1982 $stderr.puts "parse_caller_elem: #{elem.inspect}"
1983 { :file => elem, :ln => 0 }
1984 end
1985
1986 end
1987 module_function :parse_caller_elem
1988 end # module Lib
1989
1990 module Rant::Console
1991 RANT_PREFIX = "rant: "
1992 ERROR_PREFIX = "[ERROR] "
1993 WARN_PREFIX = "[WARNING] "
1994 def msg_prefix
1995 if defined? @msg_prefix and @msg_prefix
1996 @msg_prefix
1997 else
1998 RANT_PREFIX
1999 end
2000 end
2001 def msg(*text)
2002 pre = msg_prefix
2003 $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
2004 end
2005 def vmsg(importance, *text)
2006 msg(*text) if verbose >= importance
2007 end
2008 def err_msg(*text)
2009 pre = msg_prefix + ERROR_PREFIX
2010 $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
2011 end
2012 def warn_msg(*text)
2013 pre = msg_prefix + WARN_PREFIX
2014 $stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
2015 end
2016 def ask_yes_no text
2017 $stderr.print msg_prefix + text + " [y|n] "
2018 case $stdin.readline
2019 when /y|yes/i; true
2020 when /n|no/i; false
2021 else
2022 $stderr.puts(' ' * msg_prefix.length +
2023 "Please answer with `yes' or `no'")
2024 ask_yes_no text
2025 end
2026 end
2027 def prompt text
2028 $stderr.print msg_prefix + text
2029 input = $stdin.readline
2030 input ? input.chomp : input
2031 end
2032 def option_listing opts
2033 rs = ""
2034 opts.each { |lopt, *opt_a|
2035 if opt_a.size == 2
2036 mode, desc = opt_a
2037 else
2038 sopt, mode, desc = opt_a
2039 end
2040 next unless desc # "private" option
2041 optstr = ""
2042 arg = nil
2043 if mode != GetoptLong::NO_ARGUMENT
2044 if desc =~ /(\b[A-Z_]{2,}\b)/
2045 arg = $1
2046 end
2047 end
2048 if lopt
2049 optstr << lopt
2050 if arg
2051 optstr << " " << arg
2052 end
2053 optstr = optstr.ljust(30)
2054 end
2055 if sopt
2056 optstr << " " unless optstr.empty?
2057 optstr << sopt
2058 if arg
2059 optstr << " " << arg
2060 end
2061 end
2062 rs << " #{optstr}\n"
2063 rs << " #{desc.split("\n").join("\n ")}\n"
2064 }
2065 rs
2066 end
2067 extend self
2068 end # module Rant::Console
2069
2070 module RantContext
2071 include Rant::Generators
2072
2073 Env = Rant::Env
2074 FileList = Rant::FileList
2075
2076 def task(targ, &block)
2077 rant.task(targ, &block)
2078 end
2079
2080 def file(targ, &block)
2081 rant.file(targ, &block)
2082 end
2083
2084 def enhance(targ, &block)
2085 rant.enhance(targ, &block)
2086 end
2087
2088 def desc(*args)
2089 rant.desc(*args)
2090 end
2091
2092 def gen(*args, &block)
2093 rant.gen(*args, &block)
2094 end
2095
2096 def import(*args, &block)
2097 rant.import(*args, &block)
2098 end
2099
2100 def plugin(*args, &block)
2101 rant.plugin(*args, &block)
2102 end
2103
2104 def subdirs(*args)
2105 rant.subdirs(*args)
2106 end
2107
2108 def source(opt, rantfile = nil)
2109 rant.source(opt, rantfile)
2110 end
2111
2112 def sys(*args, &block)
2113 rant.sys(*args, &block)
2114 end
2115
2116 def var(*args, &block)
2117 rant.var(*args, &block)
2118 end
2119
2120 def make(*args, &block)
2121 rant.make(*args, &block)
2122 end
2123
2124 end # module RantContext
2125
2126 class RantAppContext
2127 include RantContext
2128
2129 def initialize(app)
2130 @__rant__ = app
2131 end
2132
2133 def rant
2134 @__rant__
2135 end
2136
2137 def method_missing(sym, *args)
2138 Rant::MAIN_OBJECT.send(sym, *args)
2139 rescue NoMethodError
2140 raise NameError, "NameError: undefined local " +
2141 "variable or method `#{sym}' for main:Object", caller
2142 end
2143 end
2144
2145 module Rant
2146
2147 @__rant__ = nil
2148 class << self
2149
2150 def run(first_arg=nil, *other_args)
2151 other_args = other_args.flatten
2152 args = first_arg.nil? ? ARGV.dup : ([first_arg] + other_args)
2153 if rant && !rant.run?
2154 rant.run(args.flatten)
2155 else
2156 @__rant__ = Rant::RantApp.new
2157 rant.run(args)
2158 end
2159 end
2160
2161 def rant
2162 @__rant__
2163 end
2164 end
2165
2166 end # module Rant
2167
2168 class Rant::RantApp
2169 include Rant::Console
2170
2171 class AutoLoadNodeFactory
2172 def initialize(rant)
2173 @rant = rant
2174 end
2175 def method_missing(sym, *args, &block)
2176 @rant.import "nodes/default"
2177 @rant.node_factory.send(sym, *args, &block)
2178 end
2179 end
2180
2181
2182
2183 OPTIONS = [
2184 [ "--help", "-h", GetoptLong::NO_ARGUMENT,
2185 "Print this help and exit." ],
2186 [ "--version", "-V", GetoptLong::NO_ARGUMENT,
2187 "Print version of Rant and exit." ],
2188 [ "--verbose", "-v", GetoptLong::NO_ARGUMENT,
2189 "Print more messages to stderr." ],
2190 [ "--quiet", "-q", GetoptLong::NO_ARGUMENT,
2191 "Don't print commands." ],
2192 [ "--err-commands", GetoptLong::NO_ARGUMENT,
2193 "Print failed commands and their exit status." ],
2194 [ "--directory","-C", GetoptLong::REQUIRED_ARGUMENT,
2195 "Run rant in DIRECTORY." ],
2196 [ "--cd-parent","-c", GetoptLong::NO_ARGUMENT,
2197 "Run rant in parent directory with Rantfile." ],
2198 [ "--look-up", "-u", GetoptLong::NO_ARGUMENT,
2199 "Look in parent directories for root Rantfile." ],
2200 [ "--rantfile", "-f", GetoptLong::REQUIRED_ARGUMENT,
2201 "Process RANTFILE instead of standard rantfiles.\n" +
2202 "Multiple files may be specified with this option." ],
2203 [ "--force-run","-a", GetoptLong::REQUIRED_ARGUMENT,
2204 "Force rebuild of TARGET and all dependencies." ],
2205 [ "--dry-run", "-n", GetoptLong::NO_ARGUMENT,
2206 "Print info instead of actually executing actions." ],
2207 [ "--tasks", "-T", GetoptLong::NO_ARGUMENT,
2208 "Show a list of all described tasks and exit." ],
2209
2210
2211 [ "--import", "-i", GetoptLong::REQUIRED_ARGUMENT, nil ],
2212 [ "--stop-after-load", GetoptLong::NO_ARGUMENT, nil ],
2213 [ "--trace-abort", GetoptLong::NO_ARGUMENT, nil ],
2214 ]
2215
2216 ROOT_DIR_ID = "@"
2217 ESCAPE_ID = "\\"
2218
2219 attr_reader :args
2220 attr_reader :rantfiles
2221 attr_reader :force_targets
2222 attr_reader :plugins
2223 attr_reader :context
2224 alias cx context
2225 attr_reader :tasks
2226 attr_reader :imports
2227 attr_reader :current_subdir
2228 attr_reader :resolve_hooks
2229 attr_reader :rootdir
2230
2231 attr_accessor :node_factory
2232
2233 def initialize
2234 @args = []
2235 @context = RantAppContext.new(self)
2236 @sys = ::Rant::SysObject.new(self)
2237 @rantfiles = []
2238 @tasks = {}
2239 @opts = {
2240 :verbose => 0,
2241 :quiet => false,
2242 }
2243 @rootdir = Dir.pwd # root directory of project
2244 @arg_rantfiles = [] # rantfiles given in args
2245 @arg_targets = [] # targets given in args
2246 @force_targets = [] # targets given with -a option
2247 @run = false # run method was called at least once
2248 @done = false # run method was successful
2249 @plugins = []
2250 @var = Rant::RantVar::Space.new
2251 @var.query :ignore, :AutoList, []
2252 @imports = []
2253
2254 @task_desc = nil
2255 @last_build_subdir = ""
2256
2257 @current_subdir = ""
2258 @resolve_hooks = []
2259
2260 @node_factory = AutoLoadNodeFactory.new(self)
2261 end
2262
2263 def [](opt)
2264 @opts[opt]
2265 end
2266
2267 def []=(opt, val)
2268 @opts[opt] = val
2269 end
2270
2271 def expand_path(subdir, path)
2272 case path
2273 when nil; subdir.dup
2274 when ""; subdir.dup
2275 when /^@/; path.sub(/^@/, '')
2276 else
2277 path = path.sub(/^\\(?=@)/, '')
2278 if subdir.empty?
2279 path
2280 else
2281 File.join(subdir, path)
2282 end
2283 end
2284 end
2285 def resolve_root_ref(path)
2286 return File.join(@rootdir, path[1..-1]) if path =~ /^@/
2287 path.sub(/^\\(?=@)/, '')
2288 end
2289 def project_to_fs_path(path)
2290 sub = expand_path(@current_subdir, path)
2291 sub.empty? ? @rootdir : File.join(@rootdir, sub)
2292 end
2293 def abs_path(subdir, fn)
2294 return fn if Rant::Sys.absolute_path?(fn)
2295 path = File.join(@rootdir, subdir, fn)
2296 path.gsub!(%r{/+}, "/")
2297 path.sub!(%r{/$}, "") if path.length > 1
2298 path
2299 end
2300 def goto(dir)
2301 goto_project_dir(expand_path(@current_subdir, dir))
2302 end
2303 def goto_project_dir(dir='')
2304 @current_subdir = dir
2305 abs_path = @current_subdir.empty? ?
2306 @rootdir : File.join(@rootdir, @current_subdir)
2307 unless Dir.pwd == abs_path
2308 Dir.chdir abs_path
2309 vmsg 1, "in #{abs_path}"
2310 end
2311 end
2312
2313 def run?
2314 @run
2315 end
2316
2317 def done?
2318 @done
2319 end
2320
2321 def run(*args)
2322 @run = true
2323 @args.concat(args.flatten)
2324 orig_pwd = @rootdir = Dir.pwd
2325 process_args
2326 Dir.chdir(@rootdir) rescue abort $!.message
2327 load_rantfiles
2328
2329 raise Rant::RantDoneException if @opts[:stop_after_load]
2330
2331 @plugins.each { |plugin| plugin.rant_start }
2332 if @opts[:tasks]
2333 show_descriptions
2334 raise Rant::RantDoneException
2335 end
2336 run_tasks
2337 raise Rant::RantDoneException
2338 rescue Rant::RantDoneException
2339 @done = true
2340 @plugins.each { |plugin| plugin.rant_done }
2341 return 0
2342 rescue Rant::RantAbortException
2343 $stderr.puts "rant aborted!"
2344 return 1
2345 rescue Exception => e
2346 ch = get_ch_from_backtrace(e.backtrace)
2347 if ch && !@opts[:trace_abort]
2348 err_msg(pos_text(ch[:file], ch[:ln]), e.message)
2349 else
2350 err_msg e.message, e.backtrace[0..4]
2351 end
2352 $stderr.puts "rant aborted!"
2353 return 1
2354 ensure
2355 Dir.chdir @rootdir if test ?d, @rootdir
2356 hooks = var._get("__at_return__")
2357 hooks.each { |hook| hook.call } if hooks
2358 @plugins.each { |plugin| plugin.rant_plugin_stop }
2359 @plugins.each { |plugin| plugin.rant_quit }
2360 Dir.chdir orig_pwd
2361 end
2362
2363
2364 def desc(*args)
2365 if args.empty? || (args.size == 1 && args.first.nil?)
2366 @task_desc = nil
2367 else
2368 @task_desc = args.join("\n")
2369 end
2370 end
2371
2372 def task(targ, &block)
2373 prepare_task(targ, block) { |name,pre,blk|
2374 @node_factory.new_task(self, name, pre, blk)
2375 }
2376 end
2377
2378 def file(targ, &block)
2379 prepare_task(targ, block) { |name,pre,blk|
2380 @node_factory.new_file(self, name, pre, blk)
2381 }
2382 end
2383
2384 def gen(*args, &block)
2385 ch = Rant::Lib::parse_caller_elem(caller[1])
2386 generator = args.shift
2387 unless generator.respond_to? :rant_gen
2388 abort_at(ch,
2389 "gen: First argument has to be a task-generator.")
2390 end
2391 generator.rant_gen(self, ch, args, &block)
2392 end
2393
2394 def import(*args, &block)
2395 ch = Rant::Lib::parse_caller_elem(caller[1])
2396 if block
2397 warn_msg pos_text(ch[:file], ch[:ln]),
2398 "import: ignoring block"
2399 end
2400 args.flatten.each { |arg|
2401 unless String === arg
2402 abort_at(ch, "import: only strings allowed as arguments")
2403 end
2404 unless @imports.include? arg
2405 unless Rant::CODE_IMPORTS.include? arg
2406 begin
2407 vmsg 2, "import #{arg}"
2408 require "rant/import/#{arg}"
2409 rescue LoadError => e
2410 abort_at(ch, "No such import - #{arg}")
2411 end
2412 Rant::CODE_IMPORTS << arg.dup
2413 end
2414 init_msg = "init_import_#{arg.gsub(/[^\w]/, '__')}"
2415 Rant.send init_msg, self if Rant.respond_to? init_msg
2416 @imports << arg.dup
2417 end
2418 }
2419 end
2420
2421 def plugin(*args, &block)
2422 clr = caller[1]
2423 ch = Rant::Lib::parse_caller_elem(clr)
2424 name = nil
2425 pre = []
2426 ln = ch[:ln] || 0
2427 file = ch[:file]
2428
2429 pl_name = args.shift
2430 pl_name = pl_name.to_str if pl_name.respond_to? :to_str
2431 pl_name = pl_name.to_s if pl_name.is_a? Symbol
2432 unless pl_name.is_a? String
2433 abort(pos_text(file, ln),
2434 "Plugin name has to be a string or symbol.")
2435 end
2436 lc_pl_name = pl_name.downcase
2437 import_name = "plugin/#{lc_pl_name}"
2438 unless Rant::CODE_IMPORTS.include? import_name
2439 begin
2440 require "rant/plugin/#{lc_pl_name}"
2441 Rant::CODE_IMPORTS << import_name
2442 rescue LoadError
2443 abort(pos_text(file, ln),
2444 "no such plugin library -- #{lc_pl_name}")
2445 end
2446 end
2447 pl_class = nil
2448 begin
2449 pl_class = ::Rant::Plugin.const_get(pl_name)
2450 rescue NameError, ArgumentError
2451 abort(pos_text(file, ln),
2452 "no such plugin -- #{pl_name}")
2453 end
2454
2455 plugin = pl_class.rant_plugin_new(self, ch, *args, &block)
2456 @plugins << plugin
2457 vmsg 2, "Plugin `#{plugin.rant_plugin_name}' registered."
2458 plugin.rant_plugin_init
2459 plugin
2460 end
2461
2462 def enhance(targ, &block)
2463 prepare_task(targ, block) { |name,pre,blk|
2464 t = resolve(name).last
2465 if t
2466 unless t.respond_to? :enhance
2467 abort("Can't enhance task `#{name}'")
2468 end
2469 t.enhance(pre, &blk)
2470 return t
2471 end
2472 warn_msg "enhance \"#{name}\": no such task",
2473 "Generating a new file task with the given name."
2474 @node_factory.new_file(self, name, pre, blk)
2475 }
2476 end
2477
2478 def source(opt, rantfile = nil)
2479 unless rantfile
2480 rantfile = opt
2481 opt = nil
2482 end
2483 make_rf = opt != :n && opt != :now
2484 rf, is_new = rantfile_for_path(rantfile)
2485 return false unless is_new
2486 make rantfile if make_rf
2487 unless File.exist? rf.path
2488 abort("source: No such file -- #{rantfile}")
2489 end
2490
2491 load_file rf
2492 end
2493
2494 def subdirs(*args)
2495 args.flatten!
2496 ch = Rant::Lib::parse_caller_elem(caller[1])
2497 args.each { |arg|
2498 if arg.respond_to? :to_str
2499 arg = arg.to_str
2500 else
2501 abort_at(ch, "subdirs: arguments must be strings")
2502 end
2503 loaded = false
2504 prev_subdir = @current_subdir
2505 begin
2506 goto arg
2507 if test(?f, Rant::SUB_RANTFILE)
2508 path = Rant::SUB_RANTFILE
2509 else
2510 path = rantfile_in_dir
2511 end
2512 if path
2513 if defined? @initial_subdir and
2514 @initial_subdir == @current_subdir
2515 rf, is_new = rantfile_for_path(path, false)
2516 @rantfiles.unshift rf if is_new
2517 else
2518 rf, is_new = rantfile_for_path(path)
2519 end
2520 load_file rf if is_new
2521 elsif !@opts[:no_warn_subdir]
2522 warn_msg(pos_text(ch[:file], ch[:ln]),
2523 "subdirs: No Rantfile in subdir `#{arg}'.")
2524 end
2525 ensure
2526 goto_project_dir prev_subdir
2527 end
2528 }
2529 rescue SystemCallError => e
2530 abort_at(ch, "subdirs: " + e.message)
2531 end
2532
2533 def sys(*args, &block)
2534 args.empty? ? @sys : @sys.sh(*args, &block)
2535 end
2536
2537 def var(*args, &block)
2538 args.empty? ? @var : @var.query(*args, &block)
2539 end
2540
2541 def pop_desc
2542 td = @task_desc
2543 @task_desc = nil
2544 td
2545 end
2546
2547 def abort(*msg)
2548 err_msg(msg) unless msg.empty?
2549 $stderr.puts caller if @opts[:trace_abort]
2550 raise Rant::RantAbortException
2551 end
2552
2553 def abort_at(ch, *msg)
2554 err_msg(pos_text(ch[:file], ch[:ln]), msg)
2555 $stderr.puts caller if @opts[:trace_abort]
2556 raise Rant::RantAbortException
2557 end
2558
2559 def show_help
2560 puts "rant [-f Rantfile] [Options] [targets]"
2561 puts
2562 puts "Options are:"
2563 print option_listing(OPTIONS)
2564 end
2565
2566 def show_descriptions
2567 tlist = select_tasks { |t| t.description }
2568 def_target = target_list.first
2569 if tlist.empty?
2570 puts "rant # => " + list_task_names(
2571 resolve(def_target)).join(', ')
2572 msg "No described tasks."
2573 return
2574 end
2575 prefix = "rant "
2576 infix = " # "
2577 name_length = (tlist.map{ |t| t.to_s.length } << 7).max
2578 cmd_length = prefix.length + name_length
2579 unless tlist.first.to_s == def_target
2580 defaults = list_task_names(
2581 resolve(def_target)).join(', ')
2582 puts "#{prefix}#{' ' * name_length}#{infix}=> #{defaults}"
2583 end
2584 tlist.each { |t|
2585 print(prefix + t.to_s.ljust(name_length) + infix)
2586 dt = t.description.sub(/\s+$/, "")
2587 puts dt.gsub(/\n/, "\n" + ' ' * cmd_length + infix + " ")
2588 }
2589 true
2590 end
2591
2592 def list_task_names(*tasks)
2593 rsl = []
2594 tasks.flatten.each { |t|
2595 if t.respond_to?(:has_actions?) && t.has_actions?
2596 rsl << t
2597 elsif t.respond_to? :prerequisites
2598 if t.prerequisites.empty?
2599 rsl << t
2600 else
2601 t.prerequisites.each { |pre|
2602 rsl.concat(list_task_names(
2603 resolve(pre, t.project_subdir)))
2604 }
2605 end
2606 else
2607 rsl << t
2608 end
2609 }
2610 rsl
2611 end
2612 private :list_task_names
2613
2614 def verbose
2615 @opts[:verbose]
2616 end
2617
2618 def quiet?
2619 @opts[:quiet]
2620 end
2621
2622 def pos_text(file, ln)
2623 t = "in file `#{file}'"
2624 t << ", line #{ln}" if ln && ln > 0
2625 t << ": "
2626 end
2627
2628 def cmd_msg(cmd)
2629 puts cmd unless quiet?
2630 end
2631
2632 def cmd_print(text)
2633 print text unless quiet?
2634 $stdout.flush
2635 end
2636
2637 def cmd_targets
2638 @force_targets + @arg_targets
2639 end
2640
2641 def running_task(task)
2642 if @current_subdir != @last_build_subdir
2643 cmd_msg "(in #{@current_subdir.empty? ?
2644 @rootdir : @current_subdir})"
2645 @last_build_subdir = @current_subdir
2646 end
2647 if @opts[:dry_run]
2648 task.dry_run
2649 true
2650 end
2651 end
2652
2653 private
2654 def have_any_task?
2655 !@tasks.empty?
2656 end
2657
2658 def target_list
2659 if !have_any_task? && @resolve_hooks.empty?
2660 abort("No tasks defined for this rant application!")
2661 end
2662
2663 target_list = @force_targets + @arg_targets
2664 if target_list.empty?
2665 def_tasks = resolve "default"
2666 unless def_tasks.empty?
2667 target_list << "default"
2668 else
2669 @rantfiles.each { |f|
2670 first = f.tasks.first
2671 if first
2672 target_list << first.reference_name
2673 break
2674 end
2675 }
2676 end
2677 end
2678 target_list
2679 end
2680
2681 def run_tasks
2682 target_list.each { |target|
2683 if build(target) == 0
2684 abort("Don't know how to make `#{target}'.")
2685 end
2686 }
2687 end
2688
2689 def make(target, *args, &block)
2690 ch = nil
2691 if target.respond_to? :to_hash
2692 targ = target.to_hash
2693 ch = Rant::Lib.parse_caller_elem(caller[1])
2694 abort_at(ch, "make: too many arguments") unless args.empty?
2695 tn = nil
2696 prepare_task(targ, block, ch) { |name,pre,blk|
2697 tn = name
2698 @node_factory.new_file(self, name, pre, blk)
2699 }
2700 build(tn)
2701 elsif target.respond_to? :to_rant_target
2702 rt = target.to_rant_target
2703 opt = args.shift
2704 unless args.empty?
2705 ch ||= Rant::Lib.parse_caller_elem(caller[1])
2706 abort_at(ch, "make: too many arguments")
2707 end
2708 if block
2709 ch ||= Rant::Lib.parse_caller_elem(caller[1])
2710 prepare_task(rt, block, ch) { |name,pre,blk|
2711 @node_factory.new_file(self, name, pre, blk)
2712 }
2713 build(rt)
2714 else
2715 build(rt, opt||{})
2716 end
2717 elsif target.respond_to? :rant_gen
2718 ch = Rant::Lib.parse_caller_elem(caller[1])
2719 rv = target.rant_gen(self, ch, args, &block)
2720 unless rv.respond_to? :to_rant_target
2721 abort_at(ch, "make: invalid generator return value")
2722 end
2723 build(rv.to_rant_target)
2724 rv
2725 else
2726 ch = Rant::Lib.parse_caller_elem(caller[1])
2727 abort_at(ch,
2728 "make: generator or target as first argument required.")
2729 end
2730 end
2731 public :make
2732
2733 def build(target, opt = {})
2734 opt[:force] = true if @force_targets.delete(target)
2735 opt[:dry_run] = @opts[:dry_run]
2736 matching_tasks = 0
2737 old_subdir = @current_subdir
2738 old_pwd = Dir.pwd
2739 resolve(target).each { |t|
2740 unless opt[:type] == :file && !t.file_target?
2741 matching_tasks += 1
2742 begin
2743 t.invoke(opt)
2744 rescue Rant::TaskFail => e
2745 err_task_fail(e)
2746 abort
2747 end
2748 end
2749 }
2750 @current_subdir = old_subdir
2751 Dir.chdir old_pwd
2752 matching_tasks
2753 end
2754 public :build
2755
2756 def resolve(task_name, rel_project_dir = @current_subdir)
2757 s = @tasks[expand_path(rel_project_dir, task_name)]
2758 case s
2759 when nil
2760 @resolve_hooks.each { |s|
2761 s = s[task_name, rel_project_dir]
2762 return s if s
2763 }
2764 []
2765 when Rant::Node; [s]
2766 else # assuming list of tasks
2767 s
2768 end
2769 end
2770 public :resolve
2771
2772 def rec_save_resolve(task_name, excl_hook, rel_project_dir = @current_subdir)
2773 s = @tasks[expand_path(rel_project_dir, task_name)]
2774 case s
2775 when nil
2776 @resolve_hooks.each { |s|
2777 next if s == excl_hook
2778 s = s[task_name, rel_project_dir]
2779 return s if s
2780 }
2781 []
2782 when Rant::Node; [s]
2783 else
2784 s
2785 end
2786 end
2787 public :rec_save_resolve
2788
2789 def at_resolve(&block)
2790 @resolve_hooks << block if block
2791 end
2792 public :at_resolve
2793
2794 def at_return(&block)
2795 hooks = var._get("__at_return__")
2796 if hooks
2797 hooks << block
2798 else
2799 var._set("__at_return__", [block])
2800 end
2801 end
2802 public :at_return
2803
2804 def select_tasks
2805 selection = []
2806 @rantfiles.each { |rf|
2807 rf.tasks.each { |t|
2808 selection << t if yield t
2809 }
2810 }
2811 selection
2812 end
2813 public :select_tasks
2814
2815 def load_rantfiles
2816 unless @arg_rantfiles.empty?
2817 @arg_rantfiles.each { |fn|
2818 if test(?f, fn)
2819 rf, is_new = rantfile_for_path(fn)
2820 load_file rf if is_new
2821 else
2822 abort "No such file -- #{fn}"
2823 end
2824 }
2825 return
2826 end
2827 return if have_any_task?
2828 fn = rantfile_in_dir
2829 if @opts[:cd_parent]
2830 old_root = @rootdir
2831 until fn or @rootdir == "/"
2832 @rootdir = File.dirname(@rootdir)
2833 fn = rantfile_in_dir(@rootdir)
2834 end
2835 if @rootdir != old_root and fn
2836 Dir.chdir @rootdir
2837 cmd_msg "(in #@rootdir)"
2838 end
2839 end
2840 if fn
2841 rf, is_new = rantfile_for_path(fn)
2842 load_file rf if is_new
2843 return
2844 end
2845 have_sub_rantfile = test(?f, Rant::SUB_RANTFILE)
2846 if have_sub_rantfile || @opts[:look_up]
2847 cur_dir = Dir.pwd
2848 until cur_dir == "/"
2849 cur_dir = File.dirname(cur_dir)
2850 Dir.chdir cur_dir
2851 fn = rantfile_in_dir
2852 if fn
2853 @initial_subdir = @rootdir.sub(
2854 /^#{Regexp.escape cur_dir}\//, '')
2855 @rootdir = cur_dir
2856 cmd_msg "(root is #@rootdir, in #@initial_subdir)"
2857 @last_build_subdir = @initial_subdir
2858 rf, is_new = rantfile_for_path(fn)
2859 load_file rf if is_new
2860 goto_project_dir @initial_subdir
2861 if have_sub_rantfile
2862 rf, is_new = rantfile_for_path(
2863 Rant::SUB_RANTFILE, false)
2864 if is_new
2865 @rantfiles.unshift rf
2866 load_file rf
2867 end
2868 end
2869 break
2870 end
2871 end
2872 end
2873 if @rantfiles.empty?
2874 abort("No Rantfile found, looking for:",
2875 Rant::RANTFILES.join(", "))
2876 end
2877 end
2878
2879 def load_file(rantfile)
2880 vmsg 1, "source #{rantfile}"
2881 @context.instance_eval(File.read(rantfile), rantfile)
2882 end
2883 private :load_file
2884
2885 def rantfile_in_dir(dir=nil)
2886 ::Rant::RANTFILES.each { |rfn|
2887 path = dir ? File.join(dir, rfn) : rfn
2888 return path if test ?f, path
2889 }
2890 nil
2891 end
2892
2893 def process_args
2894 old_argv = ARGV.dup
2895 ARGV.replace(@args.dup)
2896 cmd_opts = GetoptLong.new(*OPTIONS.collect { |lst| lst[0..-2] })
2897 cmd_opts.quiet = true
2898 cmd_opts.each { |opt, value|
2899 case opt
2900 when "--verbose"; @opts[:verbose] += 1
2901 when "--version"
2902 puts "rant #{Rant::VERSION}"
2903 raise Rant::RantDoneException
2904 when "--help"
2905 show_help
2906 raise Rant::RantDoneException
2907 when "--directory"
2908 @rootdir = File.expand_path(value)
2909 when "--rantfile"
2910 @arg_rantfiles << value
2911 when "--force-run"
2912 @force_targets << value
2913 when "--import"
2914 import value
2915 else
2916 @opts[opt.sub(/^--/, '').tr('-', "_").to_sym] = true
2917 end
2918 }
2919 rescue GetoptLong::Error => e
2920 abort(e.message)
2921 ensure
2922 rem_args = ARGV.dup
2923 ARGV.replace(old_argv)
2924 rem_args.each { |ra|
2925 if ra =~ /(^[^=]+)=([^=]+)$/
2926 vmsg 2, "var: #$1=#$2"
2927 @var[$1] = $2
2928 else
2929 @arg_targets << ra
2930 end
2931 }
2932 end
2933
2934 def prepare_task(targ, block, clr = caller[2])
2935
2936 if targ.is_a? Hash
2937 targ.reject! { |k, v| clr = v if k == :__caller__ }
2938 end
2939 ch = Hash === clr ? clr : Rant::Lib::parse_caller_elem(clr)
2940
2941 name, pre = normalize_task_arg(targ, ch)
2942
2943 file, is_new = rantfile_for_path(ch[:file])
2944 nt = yield(name, pre, block)
2945 nt.rantfile = file
2946 nt.project_subdir = @current_subdir
2947 nt.line_number = ch[:ln]
2948 nt.description = @task_desc
2949 @task_desc = nil
2950 file.tasks << nt
2951 hash_task nt
2952 nt
2953 end
2954 public :prepare_task
2955
2956 def hash_task(task)
2957 n = task.full_name
2958 et = @tasks[n]
2959 case et
2960 when nil
2961 @tasks[n] = task
2962 when Rant::Node
2963 mt = [et, task]
2964 @tasks[n] = mt
2965 else # assuming list of tasks
2966 et << task
2967 end
2968 end
2969
2970 def normalize_task_arg(targ, ch)
2971 name = nil
2972 pre = []
2973
2974 if targ.is_a? Hash
2975 if targ.empty?
2976 abort_at(ch, "Empty hash as task argument, " +
2977 "task name required.")
2978 end
2979 if targ.size > 1
2980 abort_at(ch, "Too many hash elements, " +
2981 "should only be one.")
2982 end
2983 targ.each_pair { |k,v|
2984 name = normalize_task_name(k, ch)
2985 pre = v
2986 }
2987 unless ::Rant::FileList === pre
2988 if pre.respond_to? :to_ary
2989 pre = pre.to_ary.dup
2990 pre.map! { |elem|
2991 normalize_task_name(elem, ch)
2992 }
2993 else
2994 pre = [normalize_task_name(pre, ch)]
2995 end
2996 end
2997 else
2998 name = normalize_task_name(targ, ch)
2999 end
3000
3001 [name, pre]
3002 end
3003 public :normalize_task_arg
3004
3005 def normalize_task_name(arg, ch)
3006 return arg if arg.is_a? String
3007 if Symbol === arg
3008 arg.to_s
3009 elsif arg.respond_to? :to_str
3010 arg.to_str
3011 else
3012 abort_at(ch, "Task name has to be a string or symbol.")
3013 end
3014 end
3015
3016 def rantfile_for_path(path, register=true)
3017 abs_path = File.expand_path(path)
3018 file = @rantfiles.find { |rf| rf.path == abs_path }
3019 if file
3020 [file, false]
3021 else
3022 file = Rant::Rantfile.new abs_path
3023 file.project_subdir = @current_subdir
3024 @rantfiles << file if register
3025 [file, true]
3026 end
3027 end
3028
3029 def get_ch_from_backtrace(backtrace)
3030 backtrace.each { |clr|
3031 ch = ::Rant::Lib.parse_caller_elem(clr)
3032 if ::Rant::Env.on_windows?
3033 return ch if @rantfiles.any? { |rf|
3034 rf.path.tr("\\", "/").sub(/^\w\:/, '') ==
3035 ch[:file].tr("\\", "/").sub(/^\w\:/, '')
3036 }
3037 else
3038 return ch if @rantfiles.any? { |rf|
3039 rf.path == ch[:file]
3040 }
3041 end
3042 }
3043 nil
3044 end
3045
3046 def err_task_fail(e)
3047 msg = []
3048 t_msg = ["Task `#{e.tname}' fail."]
3049 orig = e
3050 loop { orig = orig.orig; break unless Rant::TaskFail === orig }
3051 if orig && orig != e && !(Rant::RantAbortException === orig)
3052 ch = get_ch_from_backtrace(orig.backtrace)
3053 msg << pos_text(ch[:file], ch[:ln]) if ch
3054 unless Rant::CommandError === orig && !@opts[:err_commands]
3055 msg << orig.message
3056 msg << orig.backtrace[0..4] unless ch
3057 end
3058 end
3059 if e.msg && !e.msg.empty?
3060 ch = get_ch_from_backtrace(e.backtrace)
3061 t_msg.unshift(e.msg)
3062 t_msg.unshift(pos_text(ch[:file], ch[:ln])) if ch
3063 end
3064 err_msg msg unless msg.empty?
3065 err_msg t_msg
3066 end
3067 end # class Rant::RantApp
3068
3069 $".concat(['rant/rantlib.rb', 'rant/init.rb', 'rant/rantvar.rb', 'rant/rantsys.rb', 'rant/import/filelist/core.rb', 'rant/node.rb', 'rant/import/nodes/default.rb', 'rant/coregen.rb'])
3070 Rant::CODE_IMPORTS.concat %w(nodes/default
3071 )
3072
3073 # Catch a `require "rant"', sad...
3074 alias require_backup_by_rant require
3075 def require libf
3076 if libf == "rant"
3077 # TODO: needs rework! look at lib/rant.rb
3078 self.class.instance_eval { include Rant }
3079 else
3080 begin
3081 require_backup_by_rant libf
3082 rescue
3083 raise $!, caller
3084 end
3085 end
3086 end
3087
3088 exit Rant.run