Subversion Repositories SmartDukaan

Rev

Rev 30 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
30 ashish 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