1# 2# setup.rb 3# 4# Copyright (c) 2000-2005 Minero Aoki 5# 6# This program is free software. 7# You can distribute/modify this program under the terms of 8# the GNU LGPL, Lesser General Public License version 2.1. 9# 10 11unless Enumerable.method_defined?(:map) # Ruby 1.4.6 12 module Enumerable 13 alias map collect 14 end 15end 16 17unless File.respond_to?(:read) # Ruby 1.6 18 def File.read(fname) 19 open(fname) {|f| 20 return f.read 21 } 22 end 23end 24 25unless Errno.const_defined?(:ENOTEMPTY) # Windows? 26 module Errno 27 class ENOTEMPTY 28 # We do not raise this exception, implementation is not needed. 29 end 30 end 31end 32 33def File.binread(fname) 34 open(fname, 'rb') {|f| 35 return f.read 36 } 37end 38 39# for corrupted Windows' stat(2) 40def File.dir?(path) 41 File.directory?((path[-1,1] == '/') ? path : path + '/') 42end 43 44 45class ConfigTable 46 47 include Enumerable 48 49 def initialize(rbconfig) 50 @rbconfig = rbconfig 51 @items = [] 52 @table = {} 53 # options 54 @install_prefix = nil 55 @config_opt = nil 56 @verbose = true 57 @no_harm = false 58 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 532end # class ConfigTable 533 534 535# This module requires: #verbose?, #no_harm? 536module 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 685end 686 687 688# This module requires: #srcdir_root, #objdir_root, #relpath 689module 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 748end 749 750 751class 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 1021end # class ToplevelInstaller 1022 1023 1024class ToplevelInstallerMulti < ToplevelInstaller 1025 1026 include FileOperations 1027 1028 def initialize(ardir_root, config) 1029 super 1030 @packages = directories_of("#{@ardir}/packages") 1031 raise 'no package exists' if @packages.empty? 1032 @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) 1033 end 1034 1035 def run_metaconfigs 1036 @config.load_script "#{@ardir}/metaconfig", self 1037 @packages.each do |name| 1038 @config.load_script "#{@ardir}/packages/#{name}/metaconfig" 1039 end 1040 end 1041 1042 attr_reader :packages 1043 1044 def packages=(list) 1045 raise 'package list is empty' if list.empty? 1046 list.each do |name| 1047 raise "directory packages/#{name} does not exist"\ 1048 unless File.dir?("#{@ardir}/packages/#{name}") 1049 end 1050 @packages = list 1051 end 1052 1053 def init_installers 1054 @installers = {} 1055 @packages.each do |pack| 1056 @installers[pack] = Installer.new(@config, 1057 "#{@ardir}/packages/#{pack}", 1058 "packages/#{pack}") 1059 end 1060 with = extract_selection(config('with')) 1061 without = extract_selection(config('without')) 1062 @selected = @installers.keys.select {|name| 1063 (with.empty? or with.include?(name)) \ 1064 and not without.include?(name) 1065 } 1066 end 1067 1068 def extract_selection(list) 1069 a = list.split(/,/) 1070 a.each do |name| 1071 setup_rb_error "no such package: #{name}" unless @installers.key?(name) 1072 end 1073 a 1074 end 1075 1076 def print_usage(f) 1077 super 1078 f.puts 'Inluded packages:' 1079 f.puts ' ' + @packages.sort.join(' ') 1080 f.puts 1081 end 1082 1083 # 1084 # Task Handlers 1085 # 1086 1087 def exec_config 1088 run_hook 'pre-config' 1089 each_selected_installers {|inst| inst.exec_config } 1090 run_hook 'post-config' 1091 @config.save # must be final 1092 end 1093 1094 def exec_setup 1095 run_hook 'pre-setup' 1096 each_selected_installers {|inst| inst.exec_setup } 1097 run_hook 'post-setup' 1098 end 1099 1100 def exec_install 1101 run_hook 'pre-install' 1102 each_selected_installers {|inst| inst.exec_install } 1103 run_hook 'post-install' 1104 end 1105 1106 def exec_test 1107 run_hook 'pre-test' 1108 each_selected_installers {|inst| inst.exec_test } 1109 run_hook 'post-test' 1110 end 1111 1112 def exec_clean 1113 rm_f @config.savefile 1114 run_hook 'pre-clean' 1115 each_selected_installers {|inst| inst.exec_clean } 1116 run_hook 'post-clean' 1117 end 1118 1119 def exec_distclean 1120 rm_f @config.savefile 1121 run_hook 'pre-distclean' 1122 each_selected_installers {|inst| inst.exec_distclean } 1123 run_hook 'post-distclean' 1124 end 1125 1126 # 1127 # lib 1128 # 1129 1130 def each_selected_installers 1131 Dir.mkdir 'packages' unless File.dir?('packages') 1132 @selected.each do |pack| 1133 $stderr.puts "Processing the package `#{pack}' ..." if verbose? 1134 Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") 1135 Dir.chdir "packages/#{pack}" 1136 yield @installers[pack] 1137 Dir.chdir '../..' 1138 end 1139 end 1140 1141 def run_hook(id) 1142 @root_installer.run_hook id 1143 end 1144 1145 # module FileOperations requires this 1146 def verbose? 1147 @config.verbose? 1148 end 1149 1150 # module FileOperations requires this 1151 def no_harm? 1152 @config.no_harm? 1153 end 1154 1155end # class ToplevelInstallerMulti 1156 1157 1158class Installer 1159 1160 FILETYPES = %w( bin lib ext data conf man ) 1161 1162 include FileOperations 1163 include HookScriptAPI 1164 1165 def initialize(config, srcroot, objroot) 1166 @config = config 1167 @srcdir = File.expand_path(srcroot) 1168 @objdir = File.expand_path(objroot) 1169 @currdir = '.' 1170 end 1171 1172 def inspect 1173 "#<#{self.class} #{File.basename(@srcdir)}>" 1174 end 1175 1176 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 1567end # class Installer 1568 1569 1570class SetupError < StandardError; end 1571 1572def setup_rb_error(msg) 1573 raise SetupError, msg 1574end 1575 1576if $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 1585end 1586