root / trunk / setup.rb

Revision 10, 33.9 kB (checked in by why, 3 years ago)

setup.rb: allow traditional installation.

Line 
1#
2# setup.rb
3#
4# Copyright (c) 2000-2005 Minero Aoki
5#
6# This program is free software.
7# You can distribute/modify this program under the terms of
8# the GNU LGPL, Lesser General Public License version 2.1.
9#
10
11unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
12  module Enumerable
13    alias map collect
14  end
15end
16
17unless File.respond_to?(:read)   # Ruby 1.6
18  def File.read(fname)
19    open(fname) {|f|
20      return f.read
21    }
22  end
23end
24
25unless Errno.const_defined?(:ENOTEMPTY)   # Windows?
26  module Errno
27    class ENOTEMPTY
28      # We do not raise this exception, implementation is not needed.
29    end
30  end
31end
32
33def File.binread(fname)
34  open(fname, 'rb') {|f|
35    return f.read
36  }
37end
38
39# for corrupted Windows' stat(2)
40def File.dir?(path)
41  File.directory?((path[-1,1] == '/') ? path : path + '/')
42end
43
44
45class ConfigTable
46
47  include Enumerable
48
49  def initialize(rbconfig)
50    @rbconfig = rbconfig
51    @items = []
52    @table = {}
53    # options
54    @install_prefix = nil
55    @config_opt = nil
56    @verbose = true
57    @no_harm = false
58    @libsrc_pattern = '*.rb'
59  end
60
61  attr_accessor :install_prefix
62  attr_accessor :config_opt
63
64  attr_writer :verbose
65
66  def verbose?
67    @verbose
68  end
69
70  attr_writer :no_harm
71
72  def no_harm?
73    @no_harm
74  end
75
76  attr_accessor :libsrc_pattern
77
78  def [](key)
79    lookup(key).resolve(self)
80  end
81
82  def []=(key, val)
83    lookup(key).set val
84  end
85
86  def names
87    @items.map {|i| i.name }
88  end
89
90  def each(&block)
91    @items.each(&block)
92  end
93
94  def key?(name)
95    @table.key?(name)
96  end
97
98  def lookup(name)
99    @table[name] or setup_rb_error "no such config item: #{name}"
100  end
101
102  def add(item)
103    @items.push item
104    @table[item.name] = item
105  end
106
107  def remove(name)
108    item = lookup(name)
109    @items.delete_if {|i| i.name == name }
110    @table.delete_if {|name, i| i.name == name }
111    item
112  end
113
114  def load_script(path, inst = nil)
115    if File.file?(path)
116      MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
117    end
118  end
119
120  def savefile
121    '.config'
122  end
123
124  def load_savefile
125    begin
126      File.foreach(savefile()) do |line|
127        k, v = *line.split(/=/, 2)
128        self[k] = v.strip
129      end
130    rescue Errno::ENOENT
131      setup_rb_error $!.message + "\n#{File.basename($0)} config first"
132    end
133  end
134
135  def save
136    @items.each {|i| i.value }
137    File.open(savefile(), 'w') {|f|
138      @items.each do |i|
139        f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
140      end
141    }
142  end
143
144  def load_standard_entries
145    standard_entries(@rbconfig).each do |ent|
146      add ent
147    end
148  end
149
150  def standard_entries(rbconfig)
151    c = rbconfig
152
153    rubypath = c['bindir'] + '/' + c['ruby_install_name']
154
155    major = c['MAJOR'].to_i
156    minor = c['MINOR'].to_i
157    teeny = c['TEENY'].to_i
158    version = "#{major}.#{minor}"
159
160    # ruby ver. >= 1.4.4?
161    newpath_p = ((major >= 2) or
162                 ((major == 1) and
163                  ((minor >= 5) or
164                   ((minor == 4) and (teeny >= 4)))))
165
166    if c['rubylibdir']
167      # V > 1.6.3
168      libruby         = "#{c['prefix']}/lib/ruby"
169      librubyver      = c['rubylibdir']
170      librubyverarch  = c['archdir']
171      siteruby        = c['sitedir']
172      siterubyver     = c['sitelibdir']
173      siterubyverarch = c['sitearchdir']
174    elsif newpath_p
175      # 1.4.4 <= V <= 1.6.3
176      libruby         = "#{c['prefix']}/lib/ruby"
177      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
178      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
179      siteruby        = c['sitedir']
180      siterubyver     = "$siteruby/#{version}"
181      siterubyverarch = "$siterubyver/#{c['arch']}"
182    else
183      # V < 1.4.4
184      libruby         = "#{c['prefix']}/lib/ruby"
185      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
186      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
187      siteruby        = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
188      siterubyver     = siteruby
189      siterubyverarch = "$siterubyver/#{c['arch']}"
190    end
191    parameterize = lambda {|path|
192      path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
193    }
194
195    if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
196      makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
197    else
198      makeprog = 'make'
199    end
200
201    [
202      ExecItem.new('installdirs', 'std/site/home',
203                   'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
204          {|val, table|
205            case val
206            when 'std'
207              table['rbdir'] = '$librubyver'
208              table['sodir'] = '$librubyverarch'
209            when 'site'
210              table['rbdir'] = '$siterubyver'
211              table['sodir'] = '$siterubyverarch'
212            when 'home'
213              setup_rb_error '$HOME was not set' unless ENV['HOME']
214              table['prefix'] = ENV['HOME']
215              table['rbdir'] = '$libdir/ruby'
216              table['sodir'] = '$libdir/ruby'
217            end
218          },
219      PathItem.new('prefix', 'path', c['prefix'],
220                   'path prefix of target environment'),
221      PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
222                   'the directory for commands'),
223      PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
224                   'the directory for libraries'),
225      PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
226                   'the directory for shared data'),
227      PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
228                   'the directory for man pages'),
229      PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
230                   'the directory for system configuration files'),
231      PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
232                   'the directory for local state data'),
233      PathItem.new('libruby', 'path', libruby,
234                   'the directory for ruby libraries'),
235      PathItem.new('librubyver', 'path', librubyver,
236                   'the directory for standard ruby libraries'),
237      PathItem.new('librubyverarch', 'path', librubyverarch,
238                   'the directory for standard ruby extensions'),
239      PathItem.new('siteruby', 'path', siteruby,
240          'the directory for version-independent aux ruby libraries'),
241      PathItem.new('siterubyver', 'path', siterubyver,
242                   'the directory for aux ruby libraries'),
243      PathItem.new('siterubyverarch', 'path', siterubyverarch,
244                   'the directory for aux ruby binaries'),
245      PathItem.new('rbdir', 'path', '$siterubyver',
246                   'the directory for ruby scripts'),
247      PathItem.new('sodir', 'path', '$siterubyverarch',
248                   'the directory for ruby extentions'),
249      PathItem.new('rubypath', 'path', rubypath,
250                   'the path to set to #! line'),
251      ProgramItem.new('rubyprog', 'name', rubypath,
252                      'the ruby program using for installation'),
253      ProgramItem.new('makeprog', 'name', makeprog,
254                      'the make program to compile ruby extentions'),
255      SelectItem.new('shebang', 'all/ruby/never', 'ruby',
256                     'shebang line (#!) editing mode'),
257      BoolItem.new('without-ext', 'yes/no', 'no',
258                   'does not compile/install ruby extentions')
259    ]
260  end
261  private :standard_entries
262
263  def load_multipackage_entries
264    multipackage_entries().each do |ent|
265      add ent
266    end
267  end
268
269  def multipackage_entries
270    [
271      PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
272                               'package names that you want to install'),
273      PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
274                               'package names that you do not want to install')
275    ]
276  end
277  private :multipackage_entries
278
279  ALIASES = {
280    'std-ruby'         => 'librubyver',
281    'stdruby'          => 'librubyver',
282    'rubylibdir'       => 'librubyver',
283    'archdir'          => 'librubyverarch',
284    'site-ruby-common' => 'siteruby',     # For backward compatibility
285    'site-ruby'        => 'siterubyver',  # For backward compatibility
286    'bin-dir'          => 'bindir',
287    'bin-dir'          => 'bindir',
288    'rb-dir'           => 'rbdir',
289    'so-dir'           => 'sodir',
290    'data-dir'         => 'datadir',
291    'ruby-path'        => 'rubypath',
292    'ruby-prog'        => 'rubyprog',
293    'ruby'             => 'rubyprog',
294    'make-prog'        => 'makeprog',
295    'make'             => 'makeprog'
296  }
297
298  def fixup
299    ALIASES.each do |ali, name|
300      @table[ali] = @table[name]
301    end
302    @items.freeze
303    @table.freeze
304    @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
305  end
306
307  def parse_opt(opt)
308    m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
309    m.to_a[1,2]
310  end
311
312  def dllext
313    @rbconfig['DLEXT']
314  end
315
316  def value_config?(name)
317    lookup(name).value?
318  end
319
320  class Item
321    def initialize(name, template, default, desc)
322      @name = name.freeze
323      @template = template
324      @value = default
325      @default = default
326      @description = desc
327    end
328
329    attr_reader :name
330    attr_reader :description
331
332    attr_accessor :default
333    alias help_default default
334
335    def help_opt
336      "--#{@name}=#{@template}"
337    end
338
339    def value?
340      true
341    end
342
343    def value
344      @value
345    end
346
347    def resolve(table)
348      @value.gsub(%r<\$([^/]+)>) { table[$1] }
349    end
350
351    def set(val)
352      @value = check(val)
353    end
354
355    private
356
357    def check(val)
358      setup_rb_error "config: --#{name} requires argument" unless val
359      val
360    end
361  end
362
363  class BoolItem < Item
364    def config_type
365      'bool'
366    end
367
368    def help_opt
369      "--#{@name}"
370    end
371
372    private
373
374    def check(val)
375      return 'yes' unless val
376      unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val
377        setup_rb_error "config: --#{@name} accepts only yes/no for argument"
378      end
379      (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
380    end
381  end
382
383  class PathItem < Item
384    def config_type
385      'path'
386    end
387
388    private
389
390    def check(path)
391      setup_rb_error "config: --#{@name} requires argument"  unless path
392      path[0,1] == '$' ? path : File.expand_path(path)
393    end
394  end
395
396  class ProgramItem < Item
397    def config_type
398      'program'
399    end
400  end
401
402  class SelectItem < Item
403    def initialize(name, selection, default, desc)
404      super
405      @ok = selection.split('/')
406    end
407
408    def config_type
409      'select'
410    end
411
412    private
413
414    def check(val)
415      unless @ok.include?(val.strip)
416        setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
417      end
418      val.strip
419    end
420  end
421
422  class ExecItem < Item
423    def initialize(name, selection, desc, &block)
424      super name, selection, nil, desc
425      @ok = selection.split('/')
426      @action = block
427    end
428
429    def config_type
430      'exec'
431    end
432
433    def value?
434      false
435    end
436
437    def resolve(table)
438      setup_rb_error "$#{name()} wrongly used as option value"
439    end
440
441    undef set
442
443    def evaluate(val, table)
444      v = val.strip.downcase
445      unless @ok.include?(v)
446        setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
447      end
448      @action.call v, table
449    end
450  end
451
452  class PackageSelectionItem < Item
453    def initialize(name, template, default, help_default, desc)
454      super name, template, default, desc
455      @help_default = help_default
456    end
457
458    attr_reader :help_default
459
460    def config_type
461      'package'
462    end
463
464    private
465
466    def check(val)
467      unless File.dir?("packages/#{val}")
468        setup_rb_error "config: no such package: #{val}"
469      end
470      val
471    end
472  end
473
474  class MetaConfigEnvironment
475    def intiailize(config, installer)
476      @config = config
477      @installer = installer
478    end
479
480    def config_names
481      @config.names
482    end
483
484    def config?(name)
485      @config.key?(name)
486    end
487
488    def bool_config?(name)
489      @config.lookup(name).config_type == 'bool'
490    end
491
492    def path_config?(name)
493      @config.lookup(name).config_type == 'path'
494    end
495
496    def value_config?(name)
497      @config.lookup(name).config_type != 'exec'
498    end
499
500    def add_config(item)
501      @config.add item
502    end
503
504    def add_bool_config(name, default, desc)
505      @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
506    end
507
508    def add_path_config(name, default, desc)
509      @config.add PathItem.new(name, 'path', default, desc)
510    end
511
512    def set_config_default(name, default)
513      @config.lookup(name).default = default
514    end
515
516    def remove_config(name)
517      @config.remove(name)
518    end
519
520    # For only multipackage
521    def packages
522      raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
523      @installer.packages
524    end
525
526    # For only multipackage
527    def declare_packages(list)
528      raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
529      @installer.packages = list
530    end
531  end
532
533end   # class ConfigTable
534
535
536# This module requires: #verbose?, #no_harm?
537module FileOperations
538
539  def mkdir_p(dirname, prefix = nil)
540    dirname = prefix + File.expand_path(dirname) if prefix
541    $stderr.puts "mkdir -p #{dirname}" if verbose?
542    return if no_harm?
543
544    # Does not check '/', it's too abnormal.
545    dirs = File.expand_path(dirname).split(%r<(?=/)>)
546    if /\A[a-z]:\z/i =~ dirs[0]
547      disk = dirs.shift
548      dirs[0] = disk + dirs[0]
549    end
550    dirs.each_index do |idx|
551      path = dirs[0..idx].join('')
552      Dir.mkdir path unless File.dir?(path)
553    end
554  end
555
556  def rm_f(path)
557    $stderr.puts "rm -f #{path}" if verbose?
558    return if no_harm?
559    force_remove_file path
560  end
561
562  def rm_rf(path)
563    $stderr.puts "rm -rf #{path}" if verbose?
564    return if no_harm?
565    remove_tree path
566  end
567
568  def remove_tree(path)
569    if File.symlink?(path)
570      remove_file path
571    elsif File.dir?(path)
572      remove_tree0 path
573    else
574      force_remove_file path
575    end
576  end
577
578  def remove_tree0(path)
579    Dir.foreach(path) do |ent|
580      next if ent == '.'
581      next if ent == '..'
582      entpath = "#{path}/#{ent}"
583      if File.symlink?(entpath)
584        remove_file entpath
585      elsif File.dir?(entpath)
586        remove_tree0 entpath
587      else
588        force_remove_file entpath
589      end
590    end
591    begin
592      Dir.rmdir path
593    rescue Errno::ENOTEMPTY
594      # directory may not be empty
595    end
596  end
597
598  def move_file(src, dest)
599    force_remove_file dest
600    begin
601      File.rename src, dest
602    rescue
603      File.open(dest, 'wb') {|f|
604        f.write File.binread(src)
605      }
606      File.chmod File.stat(src).mode, dest
607      File.unlink src
608    end
609  end
610
611  def force_remove_file(path)
612    begin
613      remove_file path
614    rescue
615    end
616  end
617
618  def remove_file(path)
619    File.chmod 0777, path
620    File.unlink path
621  end
622
623  def install(from, dest, mode, prefix = nil)
624    $stderr.puts "install #{from} #{dest}" if verbose?
625    return if no_harm?
626
627    realdest = prefix ? prefix + File.expand_path(dest) : dest
628    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
629    str = File.binread(from)
630    if diff?(str, realdest)
631      verbose_off {
632        rm_f realdest if File.exist?(realdest)
633      }
634      File.open(realdest, 'wb') {|f|
635        f.write str
636      }
637      File.chmod mode, realdest
638
639      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
640        if prefix
641          f.puts realdest.sub(prefix, '')
642        else
643          f.puts realdest
644        end
645      }
646    end
647  end
648
649  def diff?(new_content, path)
650    return true unless File.exist?(path)
651    new_content != File.binread(path)
652  end
653
654  def command(*args)
655    $stderr.puts args.join(' ') if verbose?
656    system(*args) or raise RuntimeError,
657        "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
658  end
659
660  def ruby(*args)
661    command config('rubyprog'), *args
662  end
663 
664  def make(task = nil)
665    command(*[config('makeprog'), task].compact)
666  end
667
668  def extdir?(dir)
669    File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
670  end
671
672  def files_of(dir)
673    Dir.open(dir) {|d|
674      return d.select {|ent| File.file?("#{dir}/#{ent}") }
675    }
676  end
677
678  DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
679
680  def directories_of(dir)
681    Dir.open(dir) {|d|
682      return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
683    }
684  end
685
686end
687
688
689# This module requires: #srcdir_root, #objdir_root, #relpath
690module HookScriptAPI
691
692  def get_config(key)
693    @config[key]
694  end
695
696  alias config get_config
697
698  # obsolete: use metaconfig to change configuration
699  def set_config(key, val)
700    @config[key] = val
701  end
702
703  #
704  # srcdir/objdir (works only in the package directory)
705  #
706
707  def curr_srcdir
708    "#{srcdir_root()}/#{relpath()}"
709  end
710
711  def curr_objdir
712    "#{objdir_root()}/#{relpath()}"
713  end
714
715  def srcfile(path)
716    "#{curr_srcdir()}/#{path}"
717  end
718
719  def srcexist?(path)
720    File.exist?(srcfile(path))
721  end
722
723  def srcdirectory?(path)
724    File.dir?(srcfile(path))
725  end
726 
727  def srcfile?(path)
728    File.file?(srcfile(path))
729  end
730
731  def srcentries(path = '.')
732    Dir.open("#{curr_srcdir()}/#{path}") {|d|
733      return d.to_a - %w(. ..)
734    }
735  end
736
737  def srcfiles(path = '.')
738    srcentries(path).select {|fname|
739      File.file?(File.join(curr_srcdir(), path, fname))
740    }
741  end
742
743  def srcdirectories(path = '.')
744    srcentries(path).select {|fname|
745      File.dir?(File.join(curr_srcdir(), path, fname))
746    }
747  end
748
749end
750
751
752class ToplevelInstaller
753
754  Version   = '3.4.0'
755  Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
756
757  TASKS = [
758    [ 'all',      'do config, setup, then install' ],
759    [ 'config',   'saves your configurations' ],
760    [ 'show',     'shows current configuration' ],
761    [ 'setup',    'compiles ruby extentions and others' ],
762    [ 'install',  'installs files' ],
763    [ 'test',     'run all tests in test/' ],
764    [ 'clean',    "does `make clean' for each extention" ],
765    [ 'distclean',"does `make distclean' for each extention" ]
766  ]
767
768  def ToplevelInstaller.invoke
769    config = ConfigTable.new(load_rbconfig())
770    config.load_standard_entries
771    config.load_multipackage_entries if multipackage?
772    config.fixup
773    klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
774    klass.new(File.dirname($0), config).invoke
775  end
776
777  def ToplevelInstaller.multipackage?
778    File.dir?(File.dirname($0) + '/packages')
779  end
780
781  def ToplevelInstaller.load_rbconfig
782    if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
783      ARGV.delete(arg)
784      load File.expand_path(arg.split(/=/, 2)[1])
785      $".push 'rbconfig.rb'
786    else
787      require 'rbconfig'
788    end
789    ::Config::CONFIG
790  end
791
792  def initialize(ardir_root, config)
793    @ardir = File.expand_path(ardir_root)
794    @config = config
795    # cache
796    @valid_task_re = nil
797  end
798
799  def config(key)
800    @config[key]
801  end
802
803  def inspect
804    "#<#{self.class} #{__id__()}>"
805  end
806
807  def invoke
808    run_metaconfigs
809    case task = parsearg_global()
810    when nil, 'all'
811      parsearg_config
812      init_installers
813      exec_config
814      exec_setup
815      exec_install
816    else
817      case task
818      when 'config', 'test'
819        ;
820      when 'clean', 'distclean'
821        @config.load_savefile if File.exist?(@config.savefile)
822      else
823        @config.load_savefile
824      end
825      __send__ "parsearg_#{task}"
826      init_installers
827      __send__ "exec_#{task}"
828    end
829  end
830 
831  def run_metaconfigs
832    @config.load_script "#{@ardir}/metaconfig"
833  end
834
835  def init_installers
836    @installer = Installer.new(@config, @ardir, File.expand_path('.'))
837  end
838
839  #
840  # Hook Script API bases
841  #
842
843  def srcdir_root
844    @ardir
845  end
846
847  def objdir_root
848    '.'
849  end
850
851  def relpath
852    '.'
853  end
854
855  #
856  # Option Parsing
857  #
858
859  def parsearg_global
860    while arg = ARGV.shift
861      case arg
862      when /\A\w+\z/
863        setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
864        return arg
865      when '-q', '--quiet'
866        @config.verbose = false
867      when '--verbose'
868        @config.verbose = true
869      when '--help'
870        print_usage $stdout
871        exit 0
872      when '--version'
873        puts "#{File.basename($0)} version #{Version}"
874        exit 0
875      when '--copyright'
876        puts Copyright
877        exit 0
878      else
879        setup_rb_error "unknown global option '#{arg}'"
880      end
881    end
882    nil
883  end
884
885  def valid_task?(t)
886    valid_task_re() =~ t
887  end
888
889  def valid_task_re
890    @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
891  end
892
893  def parsearg_no_options
894    unless ARGV.empty?
895      setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
896    end
897  end
898
899  alias parsearg_show       parsearg_no_options
900  alias parsearg_setup      parsearg_no_options
901  alias parsearg_test       parsearg_no_options
902  alias parsearg_clean      parsearg_no_options
903  alias parsearg_distclean  parsearg_no_options
904
905  def parsearg_config
906    evalopt = []
907    set = []
908    @config.config_opt = []
909    while i = ARGV.shift
910      if /\A--?\z/ =~ i
911        @config.config_opt = ARGV.dup
912        break
913      end
914      name, value = *@config.parse_opt(i)
915      if @config.value_config?(name)
916        @config[name] = value
917      else
918        evalopt.push [name, value]
919      end
920      set.push name
921    end
922    evalopt.each do |name, value|
923      @config.lookup(name).evaluate value, @config
924    end
925    # Check if configuration is valid
926    set.each do |n|
927      @config[n] if @config.value_config?(n)
928    end
929  end
930
931  def parsearg_install
932    @config.no_harm = false
933    @config.install_prefix = ''
934    while a = ARGV.shift
935      case a
936      when '--no-harm'
937        @config.no_harm = true
938      when /\A--prefix=/
939        path = a.split(/=/, 2)[1]
940        path = File.expand_path(path) unless path[0,1] == '/'
941        @config.install_prefix = path
942      else
943        setup_rb_error "install: unknown option #{a}"
944      end
945    end
946  end
947
948  def print_usage(out)
949    out.puts 'Typical Installation Procedure:'
950    out.puts "  $ ruby #{File.basename $0} config"
951    out.puts "  $ ruby #{File.basename $0} setup"
952    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
953    out.puts
954    out.puts 'Detailed Usage:'
955    out.puts "  ruby #{File.basename $0} <global option>"
956    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
957
958    fmt = "  %-24s %s\n"
959    out.puts
960    out.puts 'Global options:'
961    out.printf fmt, '-q,--quiet',   'suppress message outputs'
962    out.printf fmt, '   --verbose', 'output messages verbosely'
963    out.printf fmt, '   --help',    'print this message'
964    out.printf fmt, '   --version', 'print version and quit'
965    out.printf fmt, '   --copyright',  'print copyright and quit'
966    out.puts
967    out.puts 'Tasks:'
968    TASKS.each do |name, desc|
969      out.printf fmt, name, desc
970    end
971
972    fmt = "  %-24s %s [%s]\n"
973    out.puts
974    out.puts 'Options for CONFIG or ALL:'
975    @config.each do |item|
976      out.printf fmt, item.help_opt, item.description, item.help_default
977    end
978    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
979    out.puts
980    out.puts 'Options for INSTALL:'
981    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
982    out.printf fmt, '--prefix=path',  'install path prefix', ''
983    out.puts
984  end
985
986  #
987  # Task Handlers
988  #
989
990  def exec_config
991    @installer.exec_config
992    @config.save   # must be final
993  end
994
995  def exec_setup
996    @installer.exec_setup
997  end
998
999  def exec_install
1000    @installer.exec_install
1001  end
1002
1003  def exec_test
1004    @installer.exec_test
1005  end
1006
1007  def exec_show
1008    @config.each do |i|
1009      printf "%-20s %s\n", i.name, i.value if i.value?
1010    end
1011  end
1012
1013  def exec_clean
1014    @installer.exec_clean
1015  end
1016
1017  def exec_distclean
1018    @installer.exec_distclean
1019  end
1020
1021end   # class ToplevelInstaller
1022
1023
1024class ToplevelInstallerMulti < ToplevelInstaller
1025
1026  include FileOperations
1027
1028  def initialize(ardir_root, config)
1029    super
1030    @packages = directories_of("#{@ardir}/packages")
1031    raise 'no package exists' if @packages.empty?
1032    @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
1033  end
1034
1035  def run_metaconfigs
1036    @config.load_script "#{@ardir}/metaconfig", self
1037    @packages.each do |name|
1038      @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
1039    end
1040  end
1041
1042  attr_reader :packages
1043
1044  def packages=(list)
1045    raise 'package list is empty' if list.empty?
1046    list.each do |name|
1047      raise "directory packages/#{name} does not exist"\
1048              unless File.dir?("#{@ardir}/packages/#{name}")
1049    end
1050    @packages = list
1051  end
1052
1053  def init_installers
1054    @installers = {}
1055    @packages.each do |pack|
1056      @installers[pack] = Installer.new(@config,
1057                                       "#{@ardir}/packages/#{pack}",
1058                                       "packages/#{pack}")
1059    end
1060    with    = extract_selection(config('with'))
1061    without = extract_selection(config('without'))
1062    @selected = @installers.keys.select {|name|
1063                  (with.empty? or with.include?(name)) \
1064                      and not without.include?(name)
1065                }
1066  end
1067
1068  def extract_selection(list)
1069    a = list.split(/,/)
1070    a.each do |name|
1071      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
1072    end
1073    a
1074  end
1075
1076  def print_usage(f)
1077    super
1078    f.puts 'Inluded packages:'
1079    f.puts '  ' + @packages.sort.join(' ')
1080    f.puts
1081  end
1082
1083  #
1084  # Task Handlers
1085  #
1086
1087  def exec_config
1088    run_hook 'pre-config'
1089    each_selected_installers {|inst| inst.exec_config }
1090    run_hook 'post-config'
1091    @config.save   # must be final
1092  end
1093
1094  def exec_setup
1095    run_hook 'pre-setup'
1096    each_selected_installers {|inst| inst.exec_setup }
1097    run_hook 'post-setup'
1098  end
1099
1100  def exec_install
1101    run_hook 'pre-install'
1102    each_selected_installers {|inst| inst.exec_install }
1103    run_hook 'post-install'
1104  end
1105
1106  def exec_test
1107    run_hook 'pre-test'
1108    each_selected_installers {|inst| inst.exec_test }
1109    run_hook 'post-test'
1110  end
1111
1112  def exec_clean
1113    rm_f @config.savefile
1114    run_hook 'pre-clean'
1115    each_selected_installers {|inst| inst.exec_clean }
1116    run_hook 'post-clean'
1117  end
1118
1119  def exec_distclean
1120    rm_f @config.savefile
1121    run_hook 'pre-distclean'
1122    each_selected_installers {|inst| inst.exec_distclean }
1123    run_hook 'post-distclean'
1124  end
1125
1126  #
1127  # lib
1128  #
1129
1130  def each_selected_installers
1131    Dir.mkdir 'packages' unless File.dir?('packages')
1132    @selected.each do |pack|
1133      $stderr.puts "Processing the package `#{pack}' ..." if verbose?
1134      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
1135      Dir.chdir "packages/#{pack}"
1136      yield @installers[pack]
1137      Dir.chdir '../..'
1138    end
1139  end
1140
1141  def run_hook(id)
1142    @root_installer.run_hook id
1143  end
1144
1145  # module FileOperations requires this
1146  def verbose?
1147    @config.verbose?
1148  end
1149
1150  # module FileOperations requires this
1151  def no_harm?
1152    @config.no_harm?
1153  end
1154
1155end   # class ToplevelInstallerMulti
1156
1157
1158class Installer
1159
1160  FILETYPES = %w( bin lib ext data conf man )
1161
1162  include FileOperations
1163  include HookScriptAPI
1164
1165  def initialize(config, srcroot, objroot)
1166    @config = config
1167    @srcdir = File.expand_path(srcroot)
1168    @objdir = File.expand_path(objroot)
1169    @currdir = '.'
1170  end
1171
1172  def inspect
1173    "#<#{self.class} #{File.basename(@srcdir)}>"
1174  end
1175
1176  #
1177  # Hook Script API base methods
1178  #
1179
1180  def srcdir_root
1181    @srcdir
1182  end
1183
1184  def objdir_root
1185    @objdir
1186  end
1187
1188  def relpath
1189    @currdir
1190  end
1191
1192  #
1193  # Config Access
1194  #
1195
1196  # module FileOperations requires this
1197  def verbose?
1198    @config.verbose?
1199  end
1200
1201  # module FileOperations requires this
1202  def no_harm?
1203    @config.no_harm?
1204  end
1205
1206  def verbose_off
1207    begin
1208      save, @config.verbose = @config.verbose?, false
1209      yield
1210    ensure
1211      @config.verbose = save
1212    end
1213  end
1214
1215  #
1216  # TASK config
1217  #
1218
1219  def exec_config
1220    exec_task_traverse 'config'
1221  end
1222
1223  def config_dir_bin(rel)
1224  end
1225
1226  def config_dir_lib(rel)
1227  end
1228
1229  def config_dir_man(rel)
1230  end
1231
1232  def config_dir_ext(rel)
1233    extconf if extdir?(curr_srcdir())
1234  end
1235
1236  def extconf
1237    ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
1238  end
1239
1240  def config_dir_data(rel)
1241  end
1242
1243  def config_dir_conf(rel)
1244  end
1245
1246  #
1247  # TASK setup
1248  #
1249
1250  def exec_setup
1251    exec_task_traverse 'setup'
1252  end
1253
1254  def setup_dir_bin(rel)
1255    files_of(curr_srcdir()).each do |fname|
1256      adjust_shebang "#{curr_srcdir()}/#{fname}"
1257    end
1258  end
1259
1260  def adjust_shebang(path)
1261    return if no_harm?
1262    tmpfile = File.basename(path) + '.tmp'
1263    begin
1264      File.open(path, 'rb') {|r|
1265        first = r.gets
1266        return unless File.basename(first.sub(/\A\#!/, '').split[0].to_s) == 'ruby'
1267        $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose?
1268        File.open(tmpfile, 'wb') {|w|
1269          w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath'))
1270          w.write r.read
1271        }
1272      }
1273      move_file tmpfile, File.basename(path)
1274    ensure
1275      File.unlink tmpfile if File.exist?(tmpfile)
1276    end
1277  end
1278
1279  def setup_dir_lib(rel)
1280  end
1281
1282  def setup_dir_man(rel)
1283  end
1284
1285  def setup_dir_ext(rel)
1286    make if extdir?(curr_srcdir())
1287  end
1288
1289  def setup_dir_data(rel)
1290  end
1291
1292  def setup_dir_conf(rel)
1293  end
1294
1295  #
1296  # TASK install
1297  #
1298
1299  def exec_install
1300    rm_f 'InstalledFiles'
1301    exec_task_traverse 'install'
1302  end
1303
1304  def install_dir_bin(rel)
1305    install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
1306  end
1307
1308  def install_dir_lib(rel)
1309    install_files rubyscripts(), "#{config('rbdir')}/#{rel}", 0644
1310  end
1311
1312  def install_dir_ext(rel)
1313    return unless extdir?(curr_srcdir())
1314    install_files rubyextentions('.'),
1315                  "#{config('sodir')}/#{File.dirname(rel)}",
1316                  0555
1317  end
1318
1319  def install_dir_data(rel)
1320    install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
1321  end
1322
1323  def install_dir_conf(rel)
1324    # FIXME: should not remove current config files
1325    # (rename previous file to .old/.org)
1326    install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
1327  end
1328
1329  def install_dir_man(rel)
1330    install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
1331  end
1332
1333  def install_files(list, dest, mode)
1334    mkdir_p dest, @config.install_prefix
1335    list.each do |fname|
1336      install fname, dest, mode, @config.install_prefix
1337    end
1338  end
1339
1340  def rubyscripts
1341    glob_select(@config.libsrc_pattern, targetfiles())
1342  end
1343
1344  def rubyextentions(dir)
1345    ents = glob_select("*.#{@config.dllext}", targetfiles())
1346    if ents.empty?
1347      setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1348    end
1349    ents
1350  end
1351
1352  def targetfiles
1353    mapdir(existfiles() - hookfiles())
1354  end
1355
1356  def mapdir(ents)
1357    ents.map {|ent|
1358      if File.exist?(ent)
1359      then ent                         # objdir
1360      else "#{curr_srcdir()}/#{ent}"   # srcdir
1361      end
1362    }
1363  end
1364
1365  # picked up many entries from cvs-1.11.1/src/ignore.c
1366  JUNK_FILES = %w(
1367    core RCSLOG tags TAGS .make.state
1368    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1369    *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1370
1371    *.org *.in .*
1372  )
1373
1374  def existfiles
1375    glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
1376  end
1377
1378  def hookfiles
1379    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1380      %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1381    }.flatten
1382  end
1383
1384  def glob_select(pat, ents)
1385    re = globs2re([pat])
1386    ents.select {|ent| re =~ ent }
1387  end
1388
1389  def glob_reject(pats, ents)
1390    re = globs2re(pats)
1391    ents.reject {|ent| re =~ ent }
1392  end
1393
1394  GLOB2REGEX = {
1395    '.' => '\.',
1396    '$' => '\$',
1397    '#' => '\#',
1398    '*' => '.*'
1399  }
1400
1401  def globs2re(pats)
1402    /\A(?:#{
1403      pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
1404    })\z/
1405  end
1406
1407  #
1408  # TASK test
1409  #
1410
1411  TESTDIR = 'test'
1412
1413  def exec_test
1414    unless File.directory?('test')
1415      $stderr.puts 'no test in this package' if verbose?
1416      return
1417    end
1418    $stderr.puts 'Running tests...' if verbose?
1419    require 'test/unit'
1420    runner = Test::Unit::AutoRunner.new(true)
1421    runner.to_run << TESTDIR
1422    runner.run
1423  end
1424
1425  #
1426  # TASK clean
1427  #
1428
1429  def exec_clean
1430    exec_task_traverse 'clean'
1431    rm_f @config.savefile
1432    rm_f 'InstalledFiles'
1433  end
1434
1435  def clean_dir_bin(rel)
1436  end
1437
1438  def clean_dir_lib(rel)
1439  end
1440
1441  def clean_dir_ext(rel)
1442    return unless extdir?(curr_srcdir())
1443    make 'clean' if File.file?('Makefile')
1444  end
1445
1446  def clean_dir_data(rel)
1447  end
1448
1449  def clean_dir_conf(rel)
1450  end
1451
1452  #
1453  # TASK distclean
1454  #
1455
1456  def exec_distclean
1457    exec_task_traverse 'distclean'