root/setup.rb

Revision 783ffa4235330029d661752b1023db635b26f2b3, 35.3 KB (checked in by Raine Virta <rane@…>, 17 months ago)

remove whitespace

  • Property mode set to 100755
Line 
1#!/usr/bin/env ruby
2#
3# setup.rb
4#
5# Copyright (c) 2000-2005 Minero Aoki
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
12unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
13  module Enumerable
14    alias map collect
15  end
16end
17
18unless File.respond_to?(:read)   # Ruby 1.6
19  def File.read(fname)
20    open(fname) {|f|
21      return f.read
22    }
23  end
24end
25
26unless Errno.const_defined?(:ENOTEMPTY)   # Windows?
27  module Errno
28    class ENOTEMPTY
29      # We do not raise this exception, implementation is not needed.
30    end
31  end
32end
33
34def File.binread(fname)
35  open(fname, 'rb') {|f|
36    return f.read
37  }
38end
39
40# for corrupted Windows' stat(2)
41def File.dir?(path)
42  File.directory?((path[-1,1] == '/') ? path : path + '/')
43end
44
45
46class ConfigTable
47
48  include Enumerable
49
50  def initialize(rbconfig)
51    @rbconfig = rbconfig
52    @items = []
53    @table = {}
54    # options
55    @install_prefix = nil
56    @config_opt = nil
57    @verbose = true
58    @no_harm = false
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  def [](key)
77    lookup(key).resolve(self)
78  end
79
80  def []=(key, val)
81    lookup(key).set val
82  end
83
84  def names
85    @items.map {|i| i.name }
86  end
87
88  def each(&block)
89    @items.each(&block)
90  end
91
92  def key?(name)
93    @table.key?(name)
94  end
95
96  def lookup(name)
97    @table[name] or setup_rb_error "no such config item: #{name}"
98  end
99
100  def add(item)
101    @items.push item
102    @table[item.name] = item
103  end
104
105  def remove(name)
106    item = lookup(name)
107    @items.delete_if {|i| i.name == name }
108    @table.delete_if {|name, i| i.name == name }
109    item
110  end
111
112  def load_script(path, inst = nil)
113    if File.file?(path)
114      MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
115    end
116  end
117
118  def savefile
119    '.config'
120  end
121
122  def load_savefile
123    begin
124      File.foreach(savefile()) do |line|
125        k, v = *line.split(/=/, 2)
126        self[k] = v.strip
127      end
128    rescue Errno::ENOENT
129      setup_rb_error $!.message + "\n#{File.basename($0)} config first"
130    end
131  end
132
133  def save
134    @items.each {|i| i.value }
135    File.open(savefile(), 'w') {|f|
136      @items.each do |i|
137        f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
138      end
139    }
140  end
141
142  def load_standard_entries
143    standard_entries(@rbconfig).each do |ent|
144      add ent
145    end
146  end
147
148  def standard_entries(rbconfig)
149    c = rbconfig
150
151    rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
152
153    major = c['MAJOR'].to_i
154    minor = c['MINOR'].to_i
155    teeny = c['TEENY'].to_i
156    version = "#{major}.#{minor}"
157
158    # ruby ver. >= 1.4.4?
159    newpath_p = ((major >= 2) or
160                 ((major == 1) and
161                  ((minor >= 5) or
162                   ((minor == 4) and (teeny >= 4)))))
163
164    if c['rubylibdir']
165      # V > 1.6.3
166      libruby         = "#{c['prefix']}/lib/ruby"
167      librubyver      = c['rubylibdir']
168      librubyverarch  = c['archdir']
169      siteruby        = c['sitedir']
170      siterubyver     = c['sitelibdir']
171      siterubyverarch = c['sitearchdir']
172    elsif newpath_p
173      # 1.4.4 <= V <= 1.6.3
174      libruby         = "#{c['prefix']}/lib/ruby"
175      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
176      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
177      siteruby        = c['sitedir']
178      siterubyver     = "$siteruby/#{version}"
179      siterubyverarch = "$siterubyver/#{c['arch']}"
180    else
181      # V < 1.4.4
182      libruby         = "#{c['prefix']}/lib/ruby"
183      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
184      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
185      siteruby        = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
186      siterubyver     = siteruby
187      siterubyverarch = "$siterubyver/#{c['arch']}"
188    end
189    parameterize = lambda {|path|
190      path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
191    }
192
193    if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
194      makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
195    else
196      makeprog = 'make'
197    end
198
199    [
200      ExecItem.new('installdirs', 'std/site/home',
201                   'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
202          {|val, table|
203            case val
204            when 'std'
205              table['rbdir'] = '$librubyver'
206              table['sodir'] = '$librubyverarch'
207            when 'site'
208              table['rbdir'] = '$siterubyver'
209              table['sodir'] = '$siterubyverarch'
210            when 'home'
211              setup_rb_error '$HOME was not set' unless ENV['HOME']
212              table['prefix'] = ENV['HOME']
213              table['rbdir'] = '$libdir/ruby'
214              table['sodir'] = '$libdir/ruby'
215            end
216          },
217      PathItem.new('prefix', 'path', c['prefix'],
218                   'path prefix of target environment'),
219      PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
220                   'the directory for commands'),
221      PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
222                   'the directory for libraries'),
223      PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
224                   'the directory for shared data'),
225      PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
226                   'the directory for man pages'),
227      PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
228                   'the directory for system configuration files'),
229      PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
230                   'the directory for local state data'),
231      PathItem.new('libruby', 'path', libruby,
232                   'the directory for ruby libraries'),
233      PathItem.new('librubyver', 'path', librubyver,
234                   'the directory for standard ruby libraries'),
235      PathItem.new('librubyverarch', 'path', librubyverarch,
236                   'the directory for standard ruby extensions'),
237      PathItem.new('siteruby', 'path', siteruby,
238          'the directory for version-independent aux ruby libraries'),
239      PathItem.new('siterubyver', 'path', siterubyver,
240                   'the directory for aux ruby libraries'),
241      PathItem.new('siterubyverarch', 'path', siterubyverarch,
242                   'the directory for aux ruby binaries'),
243      PathItem.new('rbdir', 'path', '$siterubyver',
244                   'the directory for ruby scripts'),
245      PathItem.new('sodir', 'path', '$siterubyverarch',
246                   'the directory for ruby extentions'),
247      PathItem.new('rubypath', 'path', rubypath,
248                   'the path to set to #! line'),
249      ProgramItem.new('rubyprog', 'name', rubypath,
250                      'the ruby program using for installation'),
251      ProgramItem.new('makeprog', 'name', makeprog,
252                      'the make program to compile ruby extentions'),
253      SelectItem.new('shebang', 'all/ruby/never', 'never',
254                     'shebang line (#!) editing mode'),
255      BoolItem.new('without-ext', 'yes/no', 'no',
256                   'does not compile/install ruby extentions')
257    ]
258  end
259  private :standard_entries
260
261  def load_multipackage_entries
262    multipackage_entries().each do |ent|
263      add ent
264    end
265  end
266
267  def multipackage_entries
268    [
269      PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
270                               'package names that you want to install'),
271      PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
272                               'package names that you do not want to install')
273    ]
274  end
275  private :multipackage_entries
276
277  ALIASES = {
278    'std-ruby'         => 'librubyver',
279    'stdruby'          => 'librubyver',
280    'rubylibdir'       => 'librubyver',
281    'archdir'          => 'librubyverarch',
282    'site-ruby-common' => 'siteruby',     # For backward compatibility
283    'site-ruby'        => 'siterubyver',  # For backward compatibility
284    'bin-dir'          => 'bindir',
285    'bin-dir'          => 'bindir',
286    'rb-dir'           => 'rbdir',
287    'so-dir'           => 'sodir',
288    'data-dir'         => 'datadir',
289    'ruby-path'        => 'rubypath',
290    'ruby-prog'        => 'rubyprog',
291    'ruby'             => 'rubyprog',
292    'make-prog'        => 'makeprog',
293    'make'             => 'makeprog'
294  }
295
296  def fixup
297    ALIASES.each do |ali, name|
298      @table[ali] = @table[name]
299    end
300    @items.freeze
301    @table.freeze
302    @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
303  end
304
305  def parse_opt(opt)
306    m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
307    m.to_a[1,2]
308  end
309
310  def dllext
311    @rbconfig['DLEXT']
312  end
313
314  def value_config?(name)
315    lookup(name).value?
316  end
317
318  class Item
319    def initialize(name, template, default, desc)
320      @name = name.freeze
321      @template = template
322      @value = default
323      @default = default
324      @description = desc
325    end
326
327    attr_reader :name
328    attr_reader :description
329
330    attr_accessor :default
331    alias help_default default
332
333    def help_opt
334      "--#{@name}=#{@template}"
335    end
336
337    def value?
338      true
339    end
340
341    def value
342      @value
343    end
344
345    def resolve(table)
346      @value.gsub(%r<\$([^/]+)>) { table[$1] }
347    end
348
349    def set(val)
350      @value = check(val)
351    end
352
353    private
354
355    def check(val)
356      setup_rb_error "config: --#{name} requires argument" unless val
357      val
358    end
359  end
360
361  class BoolItem < Item
362    def config_type
363      'bool'
364    end
365
366    def help_opt
367      "--#{@name}"
368    end
369
370    private
371
372    def check(val)
373      return 'yes' unless val
374      case val
375      when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
376      when /\An(o)?\z/i, /\Af(alse)\z/i  then 'no'
377      else
378        setup_rb_error "config: --#{@name} accepts only yes/no for argument"
379      end
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 initialize(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.1'
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      task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
896      setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
897    end
898  end
899
900  alias parsearg_show       parsearg_no_options
901  alias parsearg_setup      parsearg_no_options
902  alias parsearg_test       parsearg_no_options
903  alias parsearg_clean      parsearg_no_options
904  alias parsearg_distclean  parsearg_no_options
905
906  def parsearg_config
907    evalopt = []
908    set = []
909    @config.config_opt = []
910    while i = ARGV.shift
911      if /\A--?\z/ =~ i
912        @config.config_opt = ARGV.dup
913        break
914      end
915      name, value = *@config.parse_opt(i)
916      if @config.value_config?(name)
917        @config[name] = value
918      else
919        evalopt.push [name, value]
920      end
921      set.push name
922    end
923    evalopt.each do |name, value|
924      @config.lookup(name).evaluate value, @config
925    end
926    # Check if configuration is valid
927    set.each do |n|
928      @config[n] if @config.value_config?(n)
929    end
930  end
931
932  def parsearg_install
933    @config.no_harm = false
934    @config.install_prefix = ''
935    while a = ARGV.shift
936      case a
937      when '--no-harm'
938        @config.no_harm = true
939      when /\A--prefix=/
940        path = a.split(/=/, 2)[1]
941        path = File.expand_path(path) unless path[0,1] == '/'
942        @config.install_prefix = path
943      else
944        setup_rb_error "install: unknown option #{a}"
945      end
946    end
947  end
948
949  def print_usage(out)
950    out.puts 'Typical Installation Procedure:'
951    out.puts "  $ ruby #{File.basename $0} config"
952    out.puts "  $ ruby #{File.basename $0} setup"
953    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
954    out.puts
955    out.puts 'Detailed Usage:'
956    out.puts "  ruby #{File.basename $0} <global option>"
957    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
958
959    fmt = "  %-24s %s\n"
960    out.puts
961    out.puts 'Global options:'
962    out.printf fmt, '-q,--quiet',   'suppress message outputs'
963    out.printf fmt, '   --verbose', 'output messages verbosely'
964    out.printf fmt, '   --help',    'print this message'
965    out.printf fmt, '   --version', 'print version and quit'
966    out.printf fmt, '   --copyright',  'print copyright and quit'
967    out.puts
968    out.puts 'Tasks:'
969    TASKS.each do |name, desc|
970      out.printf fmt, name, desc
971    end
972
973    fmt = "  %-24s %s [%s]\n"
974    out.puts
975    out.puts 'Options for CONFIG or ALL:'
976    @config.each do |item|
977      out.printf fmt, item.help_opt, item.description, item.help_default
978    end
979    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
980    out.puts
981    out.puts 'Options for INSTALL:'
982    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
983    out.printf fmt, '--prefix=path',  'install path prefix', ''
984    out.puts
985  end
986
987  #
988  # Task Handlers
989  #
990
991  def exec_config
992    @installer.exec_config
993    @config.save   # must be final
994  end
995
996  def exec_setup
997    @installer.exec_setup
998  end
999
1000  def exec_install
1001    @installer.exec_install
1002  end
1003
1004  def exec_test
1005    @installer.exec_test
1006  end
1007
1008  def exec_show
1009    @config.each do |i|
1010      printf "%-20s %s\n", i.name, i.value if i.value?
1011    end
1012  end
1013
1014  def exec_clean
1015    @installer.exec_clean
1016  end
1017
1018  def exec_distclean
1019    @installer.exec_distclean
1020  end
1021
1022end   # class ToplevelInstaller
1023
1024
1025class ToplevelInstallerMulti < ToplevelInstaller
1026
1027  include FileOperations
1028
1029  def initialize(ardir_root, config)
1030    super
1031    @packages = directories_of("#{@ardir}/packages")
1032    raise 'no package exists' if @packages.empty?
1033    @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
1034  end
1035
1036  def run_metaconfigs
1037    @config.load_script "#{@ardir}/metaconfig", self
1038    @packages.each do |name|
1039      @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
1040    end
1041  end
1042
1043  attr_reader :packages
1044
1045  def packages=(list)
1046    raise 'package list is empty' if list.empty?
1047    list.each do |name|
1048      raise "directory packages/#{name} does not exist"\
1049              unless File.dir?("#{@ardir}/packages/#{name}")
1050    end
1051    @packages = list
1052  end
1053
1054  def init_installers
1055    @installers = {}
1056    @packages.each do |pack|
1057      @installers[pack] = Installer.new(@config,
1058                                       "#{@ardir}/packages/#{pack}",
1059                                       "packages/#{pack}")
1060    end
1061    with    = extract_selection(config('with'))
1062    without = extract_selection(config('without'))
1063    @selected = @installers.keys.select {|name|
1064                  (with.empty? or with.include?(name)) \
1065                      and not without.include?(name)
1066                }
1067  end
1068
1069  def extract_selection(list)
1070    a = list.split(/,/)
1071    a.each do |name|
1072      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
1073    end
1074    a
1075  end
1076
1077  def print_usage(f)
1078    super
1079    f.puts 'Inluded packages:'
1080    f.puts '  ' + @packages.sort.join(' ')
1081    f.puts
1082  end
1083
1084  #
1085  # Task Handlers
1086  #
1087
1088  def exec_config
1089    run_hook 'pre-config'
1090    each_selected_installers {|inst| inst.exec_config }
1091    run_hook 'post-config'
1092    @config.save   # must be final
1093  end
1094
1095  def exec_setup
1096    run_hook 'pre-setup'
1097    each_selected_installers {|inst| inst.exec_setup }
1098    run_hook 'post-setup'
1099  end
1100
1101  def exec_install
1102    run_hook 'pre-install'
1103    each_selected_installers {|inst| inst.exec_install }
1104    run_hook 'post-install'
1105  end
1106
1107  def exec_test
1108    run_hook 'pre-test'
1109    each_selected_installers {|inst| inst.exec_test }
1110    run_hook 'post-test'
1111  end
1112
1113  def exec_clean
1114    rm_f @config.savefile
1115    run_hook 'pre-clean'
1116    each_selected_installers {|inst| inst.exec_clean }
1117    run_hook 'post-clean'
1118  end
1119
1120  def exec_distclean
1121    rm_f @config.savefile
1122    run_hook 'pre-distclean'
1123    each_selected_installers {|inst| inst.exec_distclean }
1124    run_hook 'post-distclean'
1125  end
1126
1127  #
1128  # lib
1129  #
1130
1131  def each_selected_installers
1132    Dir.mkdir 'packages' unless File.dir?('packages')
1133    @selected.each do |pack|
1134      $stderr.puts "Processing the package `#{pack}' ..." if verbose?
1135      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
1136      Dir.chdir "packages/#{pack}"
1137      yield @installers[pack]
1138      Dir.chdir '../..'
1139    end
1140  end
1141
1142  def run_hook(id)
1143    @root_installer.run_hook id
1144  end
1145
1146  # module FileOperations requires this
1147  def verbose?
1148    @config.verbose?
1149  end
1150
1151  # module FileOperations requires this
1152  def no_harm?
1153    @config.no_harm?
1154  end
1155
1156end   # class ToplevelInstallerMulti
1157
1158
1159class Installer
1160
1161  FILETYPES = %w( bin lib ext data conf man )
1162
1163  include FileOperations
1164  include HookScriptAPI
1165
1166  def initialize(config, srcroot, objroot)
1167    @config = config
1168    @srcdir = File.expand_path(srcroot)
1169    @objdir = File.expand_path(objroot)
1170    @currdir = '.'
1171  end
1172
1173  def inspect
1174    "#<#{self.class} #{File.basename(@srcdir)}>"
1175  end
1176
1177  def noop(rel)
1178  end
1179
1180  #
1181  # Hook Script API base methods
1182  #
1183
1184  def srcdir_root
1185    @srcdir
1186  end
1187
1188  def objdir_root
1189    @objdir
1190  end
1191
1192  def relpath
1193    @currdir
1194  end
1195
1196  #
1197  # Config Access
1198  #
1199
1200  # module FileOperations requires this
1201  def verbose?
1202    @config.verbose?
1203  end
1204
1205  # module FileOperations requires this
1206  def no_harm?
1207    @config.no_harm?
1208  end
1209
1210  def verbose_off
1211    begin
1212      save, @config.verbose = @config.verbose?, false
1213      yield
1214    ensure
1215      @config.verbose = save
1216    end
1217  end
1218
1219  #
1220  # TASK config
1221  #
1222
1223  def exec_config
1224    exec_task_traverse 'config'
1225  end
1226
1227  alias config_dir_bin noop
1228  alias config_dir_lib noop
1229
1230  def config_dir_ext(rel)
1231    extconf if extdir?(curr_srcdir())
1232  end
1233
1234  alias config_dir_data noop
1235  alias config_dir_conf noop
1236  alias config_dir_man noop
1237
1238  def extconf
1239    ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
1240  end
1241
1242  #
1243  # TASK setup
1244  #
1245
1246  def exec_setup
1247    exec_task_traverse 'setup'
1248  end
1249
1250  def setup_dir_bin(rel)
1251    files_of(curr_srcdir()).each do |fname|
1252      update_shebang_line "#{curr_srcdir()}/#{fname}"
1253    end
1254  end
1255
1256  alias setup_dir_lib noop
1257
1258  def setup_dir_ext(rel)
1259    make if extdir?(curr_srcdir())
1260  end
1261
1262  alias setup_dir_data noop
1263  alias setup_dir_conf noop
1264  alias setup_dir_man noop
1265
1266  def update_shebang_line(path)
1267    return if no_harm?
1268    return if config('shebang') == 'never'
1269    old = Shebang.load(path)
1270    if old
1271      $stderr.puts "warning: #{path}: Shebang line includes too many args.  It is not portable and your program may not work." if old.args.size > 1
1272      new = new_shebang(old)
1273      return if new.to_s == old.to_s
1274    else
1275      return unless config('shebang') == 'all'
1276      new = Shebang.new(config('rubypath'))
1277    end
1278    $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
1279    open_atomic_writer(path) {|output|
1280      File.open(path, 'rb') {|f|
1281        f.gets if old   # discard
1282        output.puts new.to_s
1283        output.print f.read
1284      }
1285    }
1286  end
1287
1288  def new_shebang(old)
1289    if /\Aruby/ =~ File.basename(old.cmd)
1290      Shebang.new(config('rubypath'), old.args)
1291    elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
1292      Shebang.new(config('rubypath'), old.args[1..-1])
1293    else
1294      return old unless config('shebang') == 'all'
1295      Shebang.new(config('rubypath'))
1296    end
1297  end
1298
1299  def open_atomic_writer(path, &block)
1300    tmpfile = File.basename(path) + '.tmp'
1301    begin
1302      File.open(tmpfile, 'wb', &block)
1303      File.rename tmpfile, File.basename(path)
1304    ensure
1305      File.unlink tmpfile if File.exist?(tmpfile)
1306    end
1307  end
1308
1309  class Shebang
1310    def Shebang.load(path)
1311      line = nil
1312      File.open(path) {|f|
1313        line = f.gets
1314      }
1315      return nil unless /\A#!/ =~ line
1316      parse(line)
1317    end
1318
1319    def Shebang.parse(line)
1320      cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
1321      new(cmd, args)
1322    end
1323
1324    def initialize(cmd, args = [])
1325      @cmd = cmd
1326      @args = args
1327    end
1328
1329    attr_reader :cmd
1330    attr_reader :args
1331
1332    def to_s
1333      "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
1334    end
1335  end
1336
1337  #
1338  # TASK install
1339  #
1340
1341  def exec_install
1342    rm_f 'InstalledFiles'
1343    exec_task_traverse 'install'
1344  end
1345
1346  def install_dir_bin(rel)
1347    install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
1348  end
1349
1350  def install_dir_lib(rel)
1351    install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
1352  end
1353
1354  def install_dir_ext(rel)
1355    return unless extdir?(curr_srcdir())
1356    install_files rubyextentions('.'),
1357                  "#{config('sodir')}/#{File.dirname(rel)}",
1358                  0555
1359  end
1360
1361  def install_dir_data(rel)
1362    install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
1363  end
1364
1365  def install_dir_conf(rel)
1366    # FIXME: should not remove current config files
1367    # (rename previous file to .old/.org)
1368    install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
1369  end
1370
1371  def install_dir_man(rel)
1372    install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
1373  end
1374
1375  def install_files(list, dest, mode)
1376    mkdir_p dest, @config.install_prefix
1377    list.each do |fname|
1378      install fname, dest, mode, @config.install_prefix
1379    end
1380  end
1381
1382  def libfiles
1383    glob_reject(%w(*.y *.output), targetfiles())
1384  end
1385
1386  def rubyextentions(dir)
1387    ents = glob_select("*.#{@config.dllext}", targetfiles())
1388    if ents.empty?
1389      setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1390    end
1391    ents
1392  end
1393
1394  def targetfiles
1395    mapdir(existfiles() - hookfiles())
1396  end
1397
1398  def mapdir(ents)
1399    ents.map {|ent|
1400      if File.exist?(ent)
1401      then ent                         # objdir
1402      else "#{curr_srcdir()}/#{ent}"   # srcdir
1403      end
1404    }
1405  end
1406
1407  # picked up many entries from cvs-1.11.1/src/ignore.c
1408  JUNK_FILES = %w(
1409    core RCSLOG tags TAGS .make.state
1410    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1411    *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1412
1413    *.org *.in .*
1414  )
1415
1416  def existfiles
1417    glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
1418  end
1419
1420  def hookfiles
1421    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1422      %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1423    }.flatten
1424  end
1425
1426  def glob_select(pat, ents)
1427    re = globs2re([pat])
1428    ents.select {|ent| re =~ ent }
1429  end
1430
1431  def glob_reject(pats, ents)
1432    re = globs2re(pats)
1433    ents.reject {|ent| re =~ ent }
1434  end
1435
1436  GLOB2REGEX = {
1437    '.' => '\.',
1438    '$' => '\$',
1439    '#' => '\#',
1440    '*' => '.*'
1441  }
1442
1443  def globs2re(pats)
1444    /\A(?:#{
1445      pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
1446    })\z/
1447  end
1448
1449  #
1450  # TASK test
1451  #
1452
1453  TESTDIR = 'test'
1454
1455  def exec_test
1456    unless File.directory?('test')
1457      $stderr.puts 'no test in this package' if verbose?
1458      return
1459    end
1460    $stderr.puts 'Running tests...' if verbose?
1461    begin
1462      require 'test/unit'
1463    rescue LoadError
1464      setup_rb_error 'test/unit cannot loaded.  You need Ruby 1.8 or later to invoke this task.'
1465    end
1466    runner = Test::Unit::AutoRunner.new(true)
1467    runner.to_run << TESTDIR
1468    runner.run
1469  end
1470
1471  #
1472  # TASK clean
1473  #
1474
1475  def exec_clean
1476    exec_task_traverse 'clean'
1477    rm_f @config.savefile
1478    rm_f 'InstalledFiles'
1479  end
1480
1481  alias clean_dir_bin noop
1482  alias clean_dir_lib noop
1483  alias clean_dir_data noop
1484  alias clean_dir_conf noop
1485  alias clean_dir_man noop
1486
1487  def clean_dir_ext(rel)
1488    return unless extdir?(curr_srcdir())
1489    make 'clean' if File.file?('Makefile')
1490  end
1491
1492  #
1493  # TASK distclean
1494  #
1495
1496  def exec_distclean
1497    exec_task_traverse 'distclean'
1498    rm_f @config.savefile
1499    rm_f 'InstalledFiles'
1500  end
1501
1502  alias distclean_dir_bin noop
1503  alias distclean_dir_lib noop
1504
1505  def distclean_dir_ext(rel)
1506    return unless extdir?(curr_srcdir())
1507    make 'distclean' if File.file?('Makefile')
1508  end
1509
1510  alias distclean_dir_data noop
1511  alias distclean_dir_conf noop
1512  alias distclean_dir_man noop
1513
1514  #
1515  # Traversing
1516  #
1517
1518  def exec_task_traverse(task)
1519    run_hook "pre-#{task}"
1520    FILETYPES.each do |type|
1521      if type == 'ext' and config('without-ext') == 'yes'
1522        $stderr.puts 'skipping ext/* by user option' if verbose?
1523        next
1524      end
1525      traverse task, type, "#{task}_dir_#{type}"
1526    end
1527    run_hook "post-#{task}"
1528  end
1529
1530  def traverse(task, rel, mid)
1531    dive_into(rel) {
1532      run_hook "pre-#{task}"
1533      __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1534      directories_of(curr_srcdir()).each do |d|
1535        traverse task, "#{rel}/#{d}", mid
1536      end
1537      run_hook "post-#{task}"
1538    }
1539  end
1540
1541  def dive_into(rel)
1542    return unless File.dir?("#{@srcdir}/#{rel}")
1543
1544    dir = File.basename(rel)
1545    Dir.mkdir dir unless File.dir?(dir)
1546    prevdir = Dir.pwd
1547    Dir.chdir dir
1548    $stderr.puts '---> ' + rel if verbose?
1549    @currdir = rel
1550    yield
1551    Dir.chdir prevdir
1552    $stderr.puts '<--- ' + rel if verbose?
1553    @currdir = File.dirname(rel)
1554  end
1555
1556  def run_hook(id)
1557    path = [ "#{curr_srcdir()}/#{id}",
1558             "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
1559    return unless path
1560    begin
1561      instance_eval File.read(path), path, 1
1562    rescue
1563      raise if $DEBUG
1564      setup_rb_error "hook #{path} failed:\n" + $!.message
1565    end
1566  end
1567
1568end   # class Installer
1569
1570
1571class SetupError < StandardError; end
1572
1573def setup_rb_error(msg)
1574  raise SetupError, msg
1575end
1576
1577if $0 == __FILE__
1578  begin
1579    ToplevelInstaller.invoke
1580  rescue SetupError
1581    raise if $DEBUG
1582    $stderr.puts $!.message
1583    $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1584    exit 1
1585  end
1586end
Note: See TracBrowser for help on using the browser.