To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / .svn / pristine / 1c / 1c5257cafbf7d7508949bf6ef73182b08b1fa87d.svn-base @ 1297:0a574315af3e

History | View | Annotate | Download (35.3 KB)

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
    @items.freeze
300
    @table.freeze
301
    @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
302
  end
303

    
304
  def parse_opt(opt)
305
    m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
306
    m.to_a[1,2]
307
  end
308

    
309
  def dllext
310
    @rbconfig['DLEXT']
311
  end
312

    
313
  def value_config?(name)
314
    lookup(name).value?
315
  end
316

    
317
  class Item
318
    def initialize(name, template, default, desc)
319
      @name = name.freeze
320
      @template = template
321
      @value = default
322
      @default = default
323
      @description = desc
324
    end
325

    
326
    attr_reader :name
327
    attr_reader :description
328

    
329
    attr_accessor :default
330
    alias help_default default
331

    
332
    def help_opt
333
      "--#{@name}=#{@template}"
334
    end
335

    
336
    def value?
337
      true
338
    end
339

    
340
    def value
341
      @value
342
    end
343

    
344
    def resolve(table)
345
      @value.gsub(%r<\$([^/]+)>) { table[$1] }
346
    end
347

    
348
    def set(val)
349
      @value = check(val)
350
    end
351

    
352
    private
353

    
354
    def check(val)
355
      setup_rb_error "config: --#{name} requires argument" unless val
356
      val
357
    end
358
  end
359

    
360
  class BoolItem < Item
361
    def config_type
362
      'bool'
363
    end
364

    
365
    def help_opt
366
      "--#{@name}"
367
    end
368

    
369
    private
370

    
371
    def check(val)
372
      return 'yes' unless val
373
      case val
374
      when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
375
      when /\An(o)?\z/i, /\Af(alse)\z/i  then 'no'
376
      else
377
        setup_rb_error "config: --#{@name} accepts only yes/no for argument"
378
      end
379
    end
380
  end
381

    
382
  class PathItem < Item
383
    def config_type
384
      'path'
385
    end
386

    
387
    private
388

    
389
    def check(path)
390
      setup_rb_error "config: --#{@name} requires argument"  unless path
391
      path[0,1] == '$' ? path : File.expand_path(path)
392
    end
393
  end
394

    
395
  class ProgramItem < Item
396
    def config_type
397
      'program'
398
    end
399
  end
400

    
401
  class SelectItem < Item
402
    def initialize(name, selection, default, desc)
403
      super
404
      @ok = selection.split('/')
405
    end
406

    
407
    def config_type
408
      'select'
409
    end
410

    
411
    private
412

    
413
    def check(val)
414
      unless @ok.include?(val.strip)
415
        setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
416
      end
417
      val.strip
418
    end
419
  end
420

    
421
  class ExecItem < Item
422
    def initialize(name, selection, desc, &block)
423
      super name, selection, nil, desc
424
      @ok = selection.split('/')
425
      @action = block
426
    end
427

    
428
    def config_type
429
      'exec'
430
    end
431

    
432
    def value?
433
      false
434
    end
435

    
436
    def resolve(table)
437
      setup_rb_error "$#{name()} wrongly used as option value"
438
    end
439

    
440
    undef set
441

    
442
    def evaluate(val, table)
443
      v = val.strip.downcase
444
      unless @ok.include?(v)
445
        setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
446
      end
447
      @action.call v, table
448
    end
449
  end
450

    
451
  class PackageSelectionItem < Item
452
    def initialize(name, template, default, help_default, desc)
453
      super name, template, default, desc
454
      @help_default = help_default
455
    end
456

    
457
    attr_reader :help_default
458

    
459
    def config_type
460
      'package'
461
    end
462

    
463
    private
464

    
465
    def check(val)
466
      unless File.dir?("packages/#{val}")
467
        setup_rb_error "config: no such package: #{val}"
468
      end
469
      val
470
    end
471
  end
472

    
473
  class MetaConfigEnvironment
474
    def initialize(config, installer)
475
      @config = config
476
      @installer = installer
477
    end
478

    
479
    def config_names
480
      @config.names
481
    end
482

    
483
    def config?(name)
484
      @config.key?(name)
485
    end
486

    
487
    def bool_config?(name)
488
      @config.lookup(name).config_type == 'bool'
489
    end
490

    
491
    def path_config?(name)
492
      @config.lookup(name).config_type == 'path'
493
    end
494

    
495
    def value_config?(name)
496
      @config.lookup(name).config_type != 'exec'
497
    end
498

    
499
    def add_config(item)
500
      @config.add item
501
    end
502

    
503
    def add_bool_config(name, default, desc)
504
      @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
505
    end
506

    
507
    def add_path_config(name, default, desc)
508
      @config.add PathItem.new(name, 'path', default, desc)
509
    end
510

    
511
    def set_config_default(name, default)
512
      @config.lookup(name).default = default
513
    end
514

    
515
    def remove_config(name)
516
      @config.remove(name)
517
    end
518

    
519
    # For only multipackage
520
    def packages
521
      raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
522
      @installer.packages
523
    end
524

    
525
    # For only multipackage
526
    def declare_packages(list)
527
      raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
528
      @installer.packages = list
529
    end
530
  end
531

    
532
end   # class ConfigTable
533

    
534

    
535
# This module requires: #verbose?, #no_harm?
536
module FileOperations
537

    
538
  def mkdir_p(dirname, prefix = nil)
539
    dirname = prefix + File.expand_path(dirname) if prefix
540
    $stderr.puts "mkdir -p #{dirname}" if verbose?
541
    return if no_harm?
542

    
543
    # Does not check '/', it's too abnormal.
544
    dirs = File.expand_path(dirname).split(%r<(?=/)>)
545
    if /\A[a-z]:\z/i =~ dirs[0]
546
      disk = dirs.shift
547
      dirs[0] = disk + dirs[0]
548
    end
549
    dirs.each_index do |idx|
550
      path = dirs[0..idx].join('')
551
      Dir.mkdir path unless File.dir?(path)
552
    end
553
  end
554

    
555
  def rm_f(path)
556
    $stderr.puts "rm -f #{path}" if verbose?
557
    return if no_harm?
558
    force_remove_file path
559
  end
560

    
561
  def rm_rf(path)
562
    $stderr.puts "rm -rf #{path}" if verbose?
563
    return if no_harm?
564
    remove_tree path
565
  end
566

    
567
  def remove_tree(path)
568
    if File.symlink?(path)
569
      remove_file path
570
    elsif File.dir?(path)
571
      remove_tree0 path
572
    else
573
      force_remove_file path
574
    end
575
  end
576

    
577
  def remove_tree0(path)
578
    Dir.foreach(path) do |ent|
579
      next if ent == '.'
580
      next if ent == '..'
581
      entpath = "#{path}/#{ent}"
582
      if File.symlink?(entpath)
583
        remove_file entpath
584
      elsif File.dir?(entpath)
585
        remove_tree0 entpath
586
      else
587
        force_remove_file entpath
588
      end
589
    end
590
    begin
591
      Dir.rmdir path
592
    rescue Errno::ENOTEMPTY
593
      # directory may not be empty
594
    end
595
  end
596

    
597
  def move_file(src, dest)
598
    force_remove_file dest
599
    begin
600
      File.rename src, dest
601
    rescue
602
      File.open(dest, 'wb') {|f|
603
        f.write File.binread(src)
604
      }
605
      File.chmod File.stat(src).mode, dest
606
      File.unlink src
607
    end
608
  end
609

    
610
  def force_remove_file(path)
611
    begin
612
      remove_file path
613
    rescue
614
    end
615
  end
616

    
617
  def remove_file(path)
618
    File.chmod 0777, path
619
    File.unlink path
620
  end
621

    
622
  def install(from, dest, mode, prefix = nil)
623
    $stderr.puts "install #{from} #{dest}" if verbose?
624
    return if no_harm?
625

    
626
    realdest = prefix ? prefix + File.expand_path(dest) : dest
627
    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
628
    str = File.binread(from)
629
    if diff?(str, realdest)
630
      verbose_off {
631
        rm_f realdest if File.exist?(realdest)
632
      }
633
      File.open(realdest, 'wb') {|f|
634
        f.write str
635
      }
636
      File.chmod mode, realdest
637

    
638
      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
639
        if prefix
640
          f.puts realdest.sub(prefix, '')
641
        else
642
          f.puts realdest
643
        end
644
      }
645
    end
646
  end
647

    
648
  def diff?(new_content, path)
649
    return true unless File.exist?(path)
650
    new_content != File.binread(path)
651
  end
652

    
653
  def command(*args)
654
    $stderr.puts args.join(' ') if verbose?
655
    system(*args) or raise RuntimeError,
656
        "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
657
  end
658

    
659
  def ruby(*args)
660
    command config('rubyprog'), *args
661
  end
662
  
663
  def make(task = nil)
664
    command(*[config('makeprog'), task].compact)
665
  end
666

    
667
  def extdir?(dir)
668
    File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
669
  end
670

    
671
  def files_of(dir)
672
    Dir.open(dir) {|d|
673
      return d.select {|ent| File.file?("#{dir}/#{ent}") }
674
    }
675
  end
676

    
677
  DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
678

    
679
  def directories_of(dir)
680
    Dir.open(dir) {|d|
681
      return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
682
    }
683
  end
684

    
685
end
686

    
687

    
688
# This module requires: #srcdir_root, #objdir_root, #relpath
689
module HookScriptAPI
690

    
691
  def get_config(key)
692
    @config[key]
693
  end
694

    
695
  alias config get_config
696

    
697
  # obsolete: use metaconfig to change configuration
698
  def set_config(key, val)
699
    @config[key] = val
700
  end
701

    
702
  #
703
  # srcdir/objdir (works only in the package directory)
704
  #
705

    
706
  def curr_srcdir
707
    "#{srcdir_root()}/#{relpath()}"
708
  end
709

    
710
  def curr_objdir
711
    "#{objdir_root()}/#{relpath()}"
712
  end
713

    
714
  def srcfile(path)
715
    "#{curr_srcdir()}/#{path}"
716
  end
717

    
718
  def srcexist?(path)
719
    File.exist?(srcfile(path))
720
  end
721

    
722
  def srcdirectory?(path)
723
    File.dir?(srcfile(path))
724
  end
725
  
726
  def srcfile?(path)
727
    File.file?(srcfile(path))
728
  end
729

    
730
  def srcentries(path = '.')
731
    Dir.open("#{curr_srcdir()}/#{path}") {|d|
732
      return d.to_a - %w(. ..)
733
    }
734
  end
735

    
736
  def srcfiles(path = '.')
737
    srcentries(path).select {|fname|
738
      File.file?(File.join(curr_srcdir(), path, fname))
739
    }
740
  end
741

    
742
  def srcdirectories(path = '.')
743
    srcentries(path).select {|fname|
744
      File.dir?(File.join(curr_srcdir(), path, fname))
745
    }
746
  end
747

    
748
end
749

    
750

    
751
class ToplevelInstaller
752

    
753
  Version   = '3.4.1'
754
  Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
755

    
756
  TASKS = [
757
    [ 'all',      'do config, setup, then install' ],
758
    [ 'config',   'saves your configurations' ],
759
    [ 'show',     'shows current configuration' ],
760
    [ 'setup',    'compiles ruby extentions and others' ],
761
    [ 'install',  'installs files' ],
762
    [ 'test',     'run all tests in test/' ],
763
    [ 'clean',    "does `make clean' for each extention" ],
764
    [ 'distclean',"does `make distclean' for each extention" ]
765
  ]
766

    
767
  def ToplevelInstaller.invoke
768
    config = ConfigTable.new(load_rbconfig())
769
    config.load_standard_entries
770
    config.load_multipackage_entries if multipackage?
771
    config.fixup
772
    klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
773
    klass.new(File.dirname($0), config).invoke
774
  end
775

    
776
  def ToplevelInstaller.multipackage?
777
    File.dir?(File.dirname($0) + '/packages')
778
  end
779

    
780
  def ToplevelInstaller.load_rbconfig
781
    if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
782
      ARGV.delete(arg)
783
      load File.expand_path(arg.split(/=/, 2)[1])
784
      $".push 'rbconfig.rb'
785
    else
786
      require 'rbconfig'
787
    end
788
    ::Config::CONFIG
789
  end
790

    
791
  def initialize(ardir_root, config)
792
    @ardir = File.expand_path(ardir_root)
793
    @config = config
794
    # cache
795
    @valid_task_re = nil
796
  end
797

    
798
  def config(key)
799
    @config[key]
800
  end
801

    
802
  def inspect
803
    "#<#{self.class} #{__id__()}>"
804
  end
805

    
806
  def invoke
807
    run_metaconfigs
808
    case task = parsearg_global()
809
    when nil, 'all'
810
      parsearg_config
811
      init_installers
812
      exec_config
813
      exec_setup
814
      exec_install
815
    else
816
      case task
817
      when 'config', 'test'
818
        ;
819
      when 'clean', 'distclean'
820
        @config.load_savefile if File.exist?(@config.savefile)
821
      else
822
        @config.load_savefile
823
      end
824
      __send__ "parsearg_#{task}"
825
      init_installers
826
      __send__ "exec_#{task}"
827
    end
828
  end
829
  
830
  def run_metaconfigs
831
    @config.load_script "#{@ardir}/metaconfig"
832
  end
833

    
834
  def init_installers
835
    @installer = Installer.new(@config, @ardir, File.expand_path('.'))
836
  end
837

    
838
  #
839
  # Hook Script API bases
840
  #
841

    
842
  def srcdir_root
843
    @ardir
844
  end
845

    
846
  def objdir_root
847
    '.'
848
  end
849

    
850
  def relpath
851
    '.'
852
  end
853

    
854
  #
855
  # Option Parsing
856
  #
857

    
858
  def parsearg_global
859
    while arg = ARGV.shift
860
      case arg
861
      when /\A\w+\z/
862
        setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
863
        return arg
864
      when '-q', '--quiet'
865
        @config.verbose = false
866
      when '--verbose'
867
        @config.verbose = true
868
      when '--help'
869
        print_usage $stdout
870
        exit 0
871
      when '--version'
872
        puts "#{File.basename($0)} version #{Version}"
873
        exit 0
874
      when '--copyright'
875
        puts Copyright
876
        exit 0
877
      else
878
        setup_rb_error "unknown global option '#{arg}'"
879
      end
880
    end
881
    nil
882
  end
883

    
884
  def valid_task?(t)
885
    valid_task_re() =~ t
886
  end
887

    
888
  def valid_task_re
889
    @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
890
  end
891

    
892
  def parsearg_no_options
893
    unless ARGV.empty?
894
      task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
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

    
1021
end   # class ToplevelInstaller
1022

    
1023

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

    
1155
end   # class ToplevelInstallerMulti
1156

    
1157

    
1158
class 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
  def noop(rel)
1177
  end
1178

    
1179
  #
1180
  # Hook Script API base methods
1181
  #
1182

    
1183
  def srcdir_root
1184
    @srcdir
1185
  end
1186

    
1187
  def objdir_root
1188
    @objdir
1189
  end
1190

    
1191
  def relpath
1192
    @currdir
1193
  end
1194

    
1195
  #
1196
  # Config Access
1197
  #
1198

    
1199
  # module FileOperations requires this
1200
  def verbose?
1201
    @config.verbose?
1202
  end
1203

    
1204
  # module FileOperations requires this
1205
  def no_harm?
1206
    @config.no_harm?
1207
  end
1208

    
1209
  def verbose_off
1210
    begin
1211
      save, @config.verbose = @config.verbose?, false
1212
      yield
1213
    ensure
1214
      @config.verbose = save
1215
    end
1216
  end
1217

    
1218
  #
1219
  # TASK config
1220
  #
1221

    
1222
  def exec_config
1223
    exec_task_traverse 'config'
1224
  end
1225

    
1226
  alias config_dir_bin noop
1227
  alias config_dir_lib noop
1228

    
1229
  def config_dir_ext(rel)
1230
    extconf if extdir?(curr_srcdir())
1231
  end
1232

    
1233
  alias config_dir_data noop
1234
  alias config_dir_conf noop
1235
  alias config_dir_man noop
1236

    
1237
  def extconf
1238
    ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
1239
  end
1240

    
1241
  #
1242
  # TASK setup
1243
  #
1244

    
1245
  def exec_setup
1246
    exec_task_traverse 'setup'
1247
  end
1248

    
1249
  def setup_dir_bin(rel)
1250
    files_of(curr_srcdir()).each do |fname|
1251
      update_shebang_line "#{curr_srcdir()}/#{fname}"
1252
    end
1253
  end
1254

    
1255
  alias setup_dir_lib noop
1256

    
1257
  def setup_dir_ext(rel)
1258
    make if extdir?(curr_srcdir())
1259
  end
1260

    
1261
  alias setup_dir_data noop
1262
  alias setup_dir_conf noop
1263
  alias setup_dir_man noop
1264

    
1265
  def update_shebang_line(path)
1266
    return if no_harm?
1267
    return if config('shebang') == 'never'
1268
    old = Shebang.load(path)
1269
    if old
1270
      $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
1271
      new = new_shebang(old)
1272
      return if new.to_s == old.to_s
1273
    else
1274
      return unless config('shebang') == 'all'
1275
      new = Shebang.new(config('rubypath'))
1276
    end
1277
    $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
1278
    open_atomic_writer(path) {|output|
1279
      File.open(path, 'rb') {|f|
1280
        f.gets if old   # discard
1281
        output.puts new.to_s
1282
        output.print f.read
1283
      }
1284
    }
1285
  end
1286

    
1287
  def new_shebang(old)
1288
    if /\Aruby/ =~ File.basename(old.cmd)
1289
      Shebang.new(config('rubypath'), old.args)
1290
    elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
1291
      Shebang.new(config('rubypath'), old.args[1..-1])
1292
    else
1293
      return old unless config('shebang') == 'all'
1294
      Shebang.new(config('rubypath'))
1295
    end
1296
  end
1297

    
1298
  def open_atomic_writer(path, &block)
1299
    tmpfile = File.basename(path) + '.tmp'
1300
    begin
1301
      File.open(tmpfile, 'wb', &block)
1302
      File.rename tmpfile, File.basename(path)
1303
    ensure
1304
      File.unlink tmpfile if File.exist?(tmpfile)
1305
    end
1306
  end
1307

    
1308
  class Shebang
1309
    def Shebang.load(path)
1310
      line = nil
1311
      File.open(path) {|f|
1312
        line = f.gets
1313
      }
1314
      return nil unless /\A#!/ =~ line
1315
      parse(line)
1316
    end
1317

    
1318
    def Shebang.parse(line)
1319
      cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
1320
      new(cmd, args)
1321
    end
1322

    
1323
    def initialize(cmd, args = [])
1324
      @cmd = cmd
1325
      @args = args
1326
    end
1327

    
1328
    attr_reader :cmd
1329
    attr_reader :args
1330

    
1331
    def to_s
1332
      "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
1333
    end
1334
  end
1335

    
1336
  #
1337
  # TASK install
1338
  #
1339

    
1340
  def exec_install
1341
    rm_f 'InstalledFiles'
1342
    exec_task_traverse 'install'
1343
  end
1344

    
1345
  def install_dir_bin(rel)
1346
    install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
1347
  end
1348

    
1349
  def install_dir_lib(rel)
1350
    install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
1351
  end
1352

    
1353
  def install_dir_ext(rel)
1354
    return unless extdir?(curr_srcdir())
1355
    install_files rubyextentions('.'),
1356
                  "#{config('sodir')}/#{File.dirname(rel)}",
1357
                  0555
1358
  end
1359

    
1360
  def install_dir_data(rel)
1361
    install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
1362
  end
1363

    
1364
  def install_dir_conf(rel)
1365
    # FIXME: should not remove current config files
1366
    # (rename previous file to .old/.org)
1367
    install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
1368
  end
1369

    
1370
  def install_dir_man(rel)
1371
    install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
1372
  end
1373

    
1374
  def install_files(list, dest, mode)
1375
    mkdir_p dest, @config.install_prefix
1376
    list.each do |fname|
1377
      install fname, dest, mode, @config.install_prefix
1378
    end
1379
  end
1380

    
1381
  def libfiles
1382
    glob_reject(%w(*.y *.output), targetfiles())
1383
  end
1384

    
1385
  def rubyextentions(dir)
1386
    ents = glob_select("*.#{@config.dllext}", targetfiles())
1387
    if ents.empty?
1388
      setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
1389
    end
1390
    ents
1391
  end
1392

    
1393
  def targetfiles
1394
    mapdir(existfiles() - hookfiles())
1395
  end
1396

    
1397
  def mapdir(ents)
1398
    ents.map {|ent|
1399
      if File.exist?(ent)
1400
      then ent                         # objdir
1401
      else "#{curr_srcdir()}/#{ent}"   # srcdir
1402
      end
1403
    }
1404
  end
1405

    
1406
  # picked up many entries from cvs-1.11.1/src/ignore.c
1407
  JUNK_FILES = %w( 
1408
    core RCSLOG tags TAGS .make.state
1409
    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1410
    *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1411

    
1412
    *.org *.in .*
1413
  )
1414

    
1415
  def existfiles
1416
    glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
1417
  end
1418

    
1419
  def hookfiles
1420
    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1421
      %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1422
    }.flatten
1423
  end
1424

    
1425
  def glob_select(pat, ents)
1426
    re = globs2re([pat])
1427
    ents.select {|ent| re =~ ent }
1428
  end
1429

    
1430
  def glob_reject(pats, ents)
1431
    re = globs2re(pats)
1432
    ents.reject {|ent| re =~ ent }
1433
  end
1434

    
1435
  GLOB2REGEX = {
1436
    '.' => '\.',
1437
    '$' => '\$',
1438
    '#' => '\#',
1439
    '*' => '.*'
1440
  }
1441

    
1442
  def globs2re(pats)
1443
    /\A(?:#{
1444
      pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
1445
    })\z/
1446
  end
1447

    
1448
  #
1449
  # TASK test
1450
  #
1451

    
1452
  TESTDIR = 'test'
1453

    
1454
  def exec_test
1455
    unless File.directory?('test')
1456
      $stderr.puts 'no test in this package' if verbose?
1457
      return
1458
    end
1459
    $stderr.puts 'Running tests...' if verbose?
1460
    begin
1461
      require 'test/unit'
1462
    rescue LoadError
1463
      setup_rb_error 'test/unit cannot loaded.  You need Ruby 1.8 or later to invoke this task.'
1464
    end
1465
    runner = Test::Unit::AutoRunner.new(true)
1466
    runner.to_run << TESTDIR
1467
    runner.run
1468
  end
1469

    
1470
  #
1471
  # TASK clean
1472
  #
1473

    
1474
  def exec_clean
1475
    exec_task_traverse 'clean'
1476
    rm_f @config.savefile
1477
    rm_f 'InstalledFiles'
1478
  end
1479

    
1480
  alias clean_dir_bin noop
1481
  alias clean_dir_lib noop
1482
  alias clean_dir_data noop
1483
  alias clean_dir_conf noop
1484
  alias clean_dir_man noop
1485

    
1486
  def clean_dir_ext(rel)
1487
    return unless extdir?(curr_srcdir())
1488
    make 'clean' if File.file?('Makefile')
1489
  end
1490

    
1491
  #
1492
  # TASK distclean
1493
  #
1494

    
1495
  def exec_distclean
1496
    exec_task_traverse 'distclean'
1497
    rm_f @config.savefile
1498
    rm_f 'InstalledFiles'
1499
  end
1500

    
1501
  alias distclean_dir_bin noop
1502
  alias distclean_dir_lib noop
1503

    
1504
  def distclean_dir_ext(rel)
1505
    return unless extdir?(curr_srcdir())
1506
    make 'distclean' if File.file?('Makefile')
1507
  end
1508

    
1509
  alias distclean_dir_data noop
1510
  alias distclean_dir_conf noop
1511
  alias distclean_dir_man noop
1512

    
1513
  #
1514
  # Traversing
1515
  #
1516

    
1517
  def exec_task_traverse(task)
1518
    run_hook "pre-#{task}"
1519
    FILETYPES.each do |type|
1520
      if type == 'ext' and config('without-ext') == 'yes'
1521
        $stderr.puts 'skipping ext/* by user option' if verbose?
1522
        next
1523
      end
1524
      traverse task, type, "#{task}_dir_#{type}"
1525
    end
1526
    run_hook "post-#{task}"
1527
  end
1528

    
1529
  def traverse(task, rel, mid)
1530
    dive_into(rel) {
1531
      run_hook "pre-#{task}"
1532
      __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1533
      directories_of(curr_srcdir()).each do |d|
1534
        traverse task, "#{rel}/#{d}", mid
1535
      end
1536
      run_hook "post-#{task}"
1537
    }
1538
  end
1539

    
1540
  def dive_into(rel)
1541
    return unless File.dir?("#{@srcdir}/#{rel}")
1542

    
1543
    dir = File.basename(rel)
1544
    Dir.mkdir dir unless File.dir?(dir)
1545
    prevdir = Dir.pwd
1546
    Dir.chdir dir
1547
    $stderr.puts '---> ' + rel if verbose?
1548
    @currdir = rel
1549
    yield
1550
    Dir.chdir prevdir
1551
    $stderr.puts '<--- ' + rel if verbose?
1552
    @currdir = File.dirname(rel)
1553
  end
1554

    
1555
  def run_hook(id)
1556
    path = [ "#{curr_srcdir()}/#{id}",
1557
             "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
1558
    return unless path
1559
    begin
1560
      instance_eval File.read(path), path, 1
1561
    rescue
1562
      raise if $DEBUG
1563
      setup_rb_error "hook #{path} failed:\n" + $!.message
1564
    end
1565
  end
1566

    
1567
end   # class Installer
1568

    
1569

    
1570
class SetupError < StandardError; end
1571

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

    
1576
if $0 == __FILE__
1577
  begin
1578
    ToplevelInstaller.invoke
1579
  rescue SetupError
1580
    raise if $DEBUG
1581
    $stderr.puts $!.message
1582
    $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
1583
    exit 1
1584
  end
1585
end