Project

General

Profile

Download (35.3 KB) Statistics
| Branch: | Revision:

root / setup.rb @ master

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

    
11
unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
12
  module Enumerable
13
    alias map collect
14
  end
15
end
16

    
17
unless File.respond_to?(:read)   # Ruby 1.6
18
  def File.read(fname)
19
    open(fname) {|f|
20
      return f.read
21
    }
22
  end
23
end
24

    
25
unless 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
31
end
32

    
33
def File.binread(fname)
34
  open(fname, 'rb') {|f|
35
    return f.read
36
  }
37
end
38

    
39
# for corrupted Windows' stat(2)
40
def File.dir?(path)
41
  File.directory?((path[-1,1] == '/') ? path : path + '/')
42
end
43

    
44

    
45
class 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
  end
59

    
60
  attr_accessor :install_prefix
61
  attr_accessor :config_opt
62

    
63
  attr_writer :verbose
64

    
65
  def verbose?
66
    @verbose
67
  end
68

    
69
  attr_writer :no_harm
70

    
71
  def no_harm?
72
    @no_harm
73
  end
74

    
75
  def [](key)
76
    lookup(key).resolve(self)
77
  end
78

    
79
  def []=(key, val)
80
    lookup(key).set val
81
  end
82

    
83
  def names
84
    @items.map {|i| i.name }
85
  end
86

    
87
  def each(&block)
88
    @items.each(&block)
89
  end
90

    
91
  def key?(name)
92
    @table.key?(name)
93
  end
94

    
95
  def lookup(name)
96
    @table[name] or setup_rb_error "no such config item: #{name}"
97
  end
98

    
99
  def add(item)
100
    @items.push item
101
    @table[item.name] = item
102
  end
103

    
104
  def remove(name)
105
    item = lookup(name)
106
    @items.delete_if {|i| i.name == name }
107
    @table.delete_if {|name, i| i.name == name }
108
    item
109
  end
110

    
111
  def load_script(path, inst = nil)
112
    if File.file?(path)
113
      MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
114
    end
115
  end
116

    
117
  def savefile
118
    '.config'
119
  end
120

    
121
  def load_savefile
122
    begin
123
      File.foreach(savefile()) do |line|
124
        k, v = *line.split(/=/, 2)
125
        self[k] = v.strip
126
      end
127
    rescue Errno::ENOENT
128
      setup_rb_error $!.message + "\n#{File.basename($0)} config first"
129
    end
130
  end
131

    
132
  def save
133
    @items.each {|i| i.value }
134
    File.open(savefile(), 'w') {|f|
135
      @items.each do |i|
136
        f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
137
      end
138
    }
139
  end
140

    
141
  def load_standard_entries
142
    standard_entries(@rbconfig).each do |ent|
143
      add ent
144
    end
145
  end
146

    
147
  def standard_entries(rbconfig)
148
    c = rbconfig
149

    
150
    rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
151

    
152
    major = c['MAJOR'].to_i
153
    minor = c['MINOR'].to_i
154
    teeny = c['TEENY'].to_i
155
    version = "#{major}.#{minor}"
156

    
157
    # ruby ver. >= 1.4.4?
158
    newpath_p = ((major >= 2) or
159
                 ((major == 1) and
160
                  ((minor >= 5) or
161
                   ((minor == 4) and (teeny >= 4)))))
162

    
163
    if c['rubylibdir']
164
      # V > 1.6.3
165
      libruby         = "#{c['prefix']}/lib/ruby"
166
      librubyver      = c['rubylibdir']
167
      librubyverarch  = c['archdir']
168
      siteruby        = c['sitedir']
169
      siterubyver     = c['sitelibdir']
170
      siterubyverarch = c['sitearchdir']
171
    elsif newpath_p
172
      # 1.4.4 <= V <= 1.6.3
173
      libruby         = "#{c['prefix']}/lib/ruby"
174
      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
175
      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
176
      siteruby        = c['sitedir']
177
      siterubyver     = "$siteruby/#{version}"
178
      siterubyverarch = "$siterubyver/#{c['arch']}"
179
    else
180
      # V < 1.4.4
181
      libruby         = "#{c['prefix']}/lib/ruby"
182
      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
183
      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
184
      siteruby        = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
185
      siterubyver     = siteruby
186
      siterubyverarch = "$siterubyver/#{c['arch']}"
187
    end
188
    parameterize = lambda {|path|
189
      path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
190
    }
191

    
192
    if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
193
      makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
194
    else
195
      makeprog = 'make'
196
    end
197

    
198
    [
199
      ExecItem.new('installdirs', 'std/site/home',
200
                   'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
201
          {|val, table|
202
            case val
203
            when 'std'
204
              table['rbdir'] = '$librubyver'
205
              table['sodir'] = '$librubyverarch'
206
            when 'site'
207
              table['rbdir'] = '$siterubyver'
208
              table['sodir'] = '$siterubyverarch'
209
            when 'home'
210
              setup_rb_error '$HOME was not set' unless ENV['HOME']
211
              table['prefix'] = ENV['HOME']
212
              table['rbdir'] = '$libdir/ruby'
213
              table['sodir'] = '$libdir/ruby'
214
            end
215
          },
216
      PathItem.new('prefix', 'path', c['prefix'],
217
                   'path prefix of target environment'),
218
      PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
219
                   'the directory for commands'),
220
      PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
221
                   'the directory for libraries'),
222
      PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
223
                   'the directory for shared data'),
224
      PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
225
                   'the directory for man pages'),
226
      PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
227
                   'the directory for system configuration files'),
228
      PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
229
                   'the directory for local state data'),
230
      PathItem.new('libruby', 'path', libruby,
231
                   'the directory for ruby libraries'),
232
      PathItem.new('librubyver', 'path', librubyver,
233
                   'the directory for standard ruby libraries'),
234
      PathItem.new('librubyverarch', 'path', librubyverarch,
235
                   'the directory for standard ruby extensions'),
236
      PathItem.new('siteruby', 'path', siteruby,
237
          'the directory for version-independent aux ruby libraries'),
238
      PathItem.new('siterubyver', 'path', siterubyver,
239
                   'the directory for aux ruby libraries'),
240
      PathItem.new('siterubyverarch', 'path', siterubyverarch,
241
                   'the directory for aux ruby binaries'),
242
      PathItem.new('rbdir', 'path', '$siterubyver',
243
                   'the directory for ruby scripts'),
244
      PathItem.new('sodir', 'path', '$siterubyverarch',
245
                   'the directory for ruby extentions'),
246
      PathItem.new('rubypath', 'path', rubypath,
247
                   'the path to set to #! line'),
248
      ProgramItem.new('rubyprog', 'name', rubypath,
249
                      'the ruby program using for installation'),
250
      ProgramItem.new('makeprog', 'name', makeprog,
251
                      'the make program to compile ruby extentions'),
252
      SelectItem.new('shebang', 'all/ruby/never', 'ruby',
253
                     'shebang line (#!) editing mode'),
254
      BoolItem.new('without-ext', 'yes/no', 'no',
255
                   'does not compile/install ruby extentions')
256
    ]
257
  end
258
  private :standard_entries
259

    
260
  def load_multipackage_entries
261
    multipackage_entries().each do |ent|
262
      add ent
263
    end
264
  end
265

    
266
  def multipackage_entries
267
    [
268
      PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
269
                               'package names that you want to install'),
270
      PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
271
                               'package names that you do not want to install')
272
    ]
273
  end
274
  private :multipackage_entries
275

    
276
  ALIASES = {
277
    'std-ruby'         => 'librubyver',
278
    'stdruby'          => 'librubyver',
279
    'rubylibdir'       => 'librubyver',
280
    'archdir'          => 'librubyverarch',
281
    'site-ruby-common' => 'siteruby',     # For backward compatibility
282
    'site-ruby'        => 'siterubyver',  # For backward compatibility
283
    'bin-dir'          => 'bindir',
284
    'bin-dir'          => 'bindir',
285
    'rb-dir'           => 'rbdir',
286
    'so-dir'           => 'sodir',
287
    'data-dir'         => 'datadir',
288
    'ruby-path'        => 'rubypath',
289
    'ruby-prog'        => 'rubyprog',
290
    'ruby'             => 'rubyprog',
291
    'make-prog'        => 'makeprog',
292
    'make'             => 'makeprog'
293
  }
294

    
295
  def fixup
296
    ALIASES.each do |ali, name|
297
      @table[ali] = @table[name]
298
    end
299
  end
300

    
301
  def options_re
302
    /\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

    
533
end   # class ConfigTable
534

    
535

    
536
# This module requires: #verbose?, #no_harm?
537
module 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

    
686
end
687

    
688

    
689
# This module requires: #srcdir_root, #objdir_root, #relpath
690
module 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

    
749
end
750

    
751

    
752
class 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

    
1022
end   # class ToplevelInstaller
1023

    
1024

    
1025
class 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

    
1156
end   # class ToplevelInstallerMulti
1157

    
1158

    
1159
class 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

    
1568
end   # class Installer
1569

    
1570

    
1571
class SetupError < StandardError; end
1572

    
1573
def setup_rb_error(msg)
1574
  raise SetupError, msg
1575
end
1576

    
1577
if $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
1586
end
(2-2/2)