From 894f4a0bd3407d99b779e739ef3c8c42c64bd4b1 Mon Sep 17 00:00:00 2001 From: why Date: Mon, 10 Apr 2006 18:56:16 +0000 Subject: Update setup.rb to silence errors on shebang replacement. Camping handler now allows passing back of an IO object, to allow large file streaming. Simpler underscore replacement on Tepee wiki words. git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@154 19e92222-5c0b-0410-8929-a290d50e31e9 --- setup.rb | 1373 ++++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 799 insertions(+), 574 deletions(-) (limited to 'setup.rb') diff --git a/setup.rb b/setup.rb index 0807023..424a5f3 100644 --- a/setup.rb +++ b/setup.rb @@ -1,7 +1,7 @@ # # setup.rb # -# Copyright (c) 2000-2004 Minero Aoki +# Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of @@ -22,175 +22,67 @@ unless File.respond_to?(:read) # Ruby 1.6 end end +unless Errno.const_defined?(:ENOTEMPTY) # Windows? + module Errno + class ENOTEMPTY + # We do not raise this exception, implementation is not needed. + end + end +end + def File.binread(fname) open(fname, 'rb') {|f| return f.read } end -# for corrupted windows stat(2) +# for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end -class SetupError < StandardError; end - -def setup_rb_error(msg) - raise SetupError, msg -end - -# -# Config -# - -if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } - ARGV.delete(arg) - require arg.split(/=/, 2)[1] - $".push 'rbconfig.rb' -else - require 'rbconfig' -end - -def multipackage_install? - FileTest.directory?(File.dirname($0) + '/packages') -end - - -class ConfigItem - def initialize(name, template, default, desc) - @name = name.freeze - @template = template - @value = default - @default = default.dup.freeze - @description = desc - end - - attr_reader :name - attr_reader :description - - attr_accessor :default - alias help_default default - - def help_opt - "--#{@name}=#{@template}" - end - - def value - @value - end - - def eval(table) - @value.gsub(%r<\$([^/]+)>) { table[$1] } - end - - def set(val) - @value = check(val) - end - - private - - def check(val) - setup_rb_error "config: --#{name} requires argument" unless val - val - end -end - -class BoolItem < ConfigItem - def config_type - 'bool' - end - - def help_opt - "--#{@name}" - end - - private +class ConfigTable - def check(val) - return 'yes' unless val - unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val - setup_rb_error "config: --#{@name} accepts only yes/no for argument" - end - (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' - end -end + include Enumerable -class PathItem < ConfigItem - def config_type - 'path' + def initialize(rbconfig) + @rbconfig = rbconfig + @items = [] + @table = {} + # options + @install_prefix = nil + @config_opt = nil + @verbose = true + @no_harm = false end - private - - def check(path) - setup_rb_error "config: --#{@name} requires argument" unless path - path[0,1] == '$' ? path : File.expand_path(path) - end -end + attr_accessor :install_prefix + attr_accessor :config_opt -class ProgramItem < ConfigItem - def config_type - 'program' - end -end + attr_writer :verbose -class SelectItem < ConfigItem - def initialize(name, template, default, desc) - super - @ok = template.split('/') - end - - def config_type - 'select' + def verbose? + @verbose end - private + attr_writer :no_harm - def check(val) - unless @ok.include?(val.strip) - setup_rb_error "config: use --#{@name}=#{@template} (#{val})" - end - val.strip + def no_harm? + @no_harm end -end -class PackageSelectionItem < ConfigItem - def initialize(name, template, default, help_default, desc) - super name, template, default, desc - @help_default = help_default + def [](key) + lookup(key).resolve(self) end - attr_reader :help_default - - def config_type - 'package' + def []=(key, val) + lookup(key).set val end - private - - def check(val) - unless File.dir?("packages/#{val}") - setup_rb_error "config: no such package: #{val}" - end - val + def names + @items.map {|i| i.name } end -end - -class ConfigTable_class - - def initialize(items) - @items = items - @table = {} - items.each do |i| - @table[i.name] = i - end - ALIASES.each do |ali, name| - @table[ali] = @table[name] - end - end - - include Enumerable def each(&block) @items.each(&block) @@ -201,7 +93,7 @@ class ConfigTable_class end def lookup(name) - @table[name] or raise ArgumentError, "no such config item: #{name}" + @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @@ -216,24 +108,24 @@ class ConfigTable_class item end - def new - dup() + def load_script(path, inst = nil) + if File.file?(path) + MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path + end end def savefile '.config' end - def load + def load_savefile begin - t = dup() File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) - t[k] = v.strip + self[k] = v.strip end - t rescue Errno::ENOENT - setup_rb_error $!.message + "#{File.basename($0)} config first" + setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end @@ -241,117 +133,151 @@ class ConfigTable_class @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| - f.printf "%s=%s\n", i.name, i.value if i.value + f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end - def [](key) - lookup(key).eval(self) + def load_standard_entries + standard_entries(@rbconfig).each do |ent| + add ent + end end - def []=(key, val) - lookup(key).set val - end + def standard_entries(rbconfig) + c = rbconfig + + rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) + + major = c['MAJOR'].to_i + minor = c['MINOR'].to_i + teeny = c['TEENY'].to_i + version = "#{major}.#{minor}" + + # ruby ver. >= 1.4.4? + newpath_p = ((major >= 2) or + ((major == 1) and + ((minor >= 5) or + ((minor == 4) and (teeny >= 4))))) + + if c['rubylibdir'] + # V > 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = c['rubylibdir'] + librubyverarch = c['archdir'] + siteruby = c['sitedir'] + siterubyver = c['sitelibdir'] + siterubyverarch = c['sitearchdir'] + elsif newpath_p + # 1.4.4 <= V <= 1.6.3 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = c['sitedir'] + siterubyver = "$siteruby/#{version}" + siterubyverarch = "$siterubyver/#{c['arch']}" + else + # V < 1.4.4 + libruby = "#{c['prefix']}/lib/ruby" + librubyver = "#{c['prefix']}/lib/ruby/#{version}" + librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" + siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" + siterubyver = siteruby + siterubyverarch = "$siterubyver/#{c['arch']}" + end + parameterize = lambda {|path| + path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') + } -end + if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } + makeprog = arg.sub(/'/, '').split(/=/, 2)[1] + else + makeprog = 'make' + end -c = ::Config::CONFIG - -rubypath = c['bindir'] + '/' + c['ruby_install_name'] - -major = c['MAJOR'].to_i -minor = c['MINOR'].to_i -teeny = c['TEENY'].to_i -version = "#{major}.#{minor}" - -# ruby ver. >= 1.4.4? -newpath_p = ((major >= 2) or - ((major == 1) and - ((minor >= 5) or - ((minor == 4) and (teeny >= 4))))) - -if c['rubylibdir'] - # V < 1.6.3 - _stdruby = c['rubylibdir'] - _siteruby = c['sitedir'] - _siterubyver = c['sitelibdir'] - _siterubyverarch = c['sitearchdir'] -elsif newpath_p - # 1.4.4 <= V <= 1.6.3 - _stdruby = "$prefix/lib/ruby/#{version}" - _siteruby = c['sitedir'] - _siterubyver = "$siteruby/#{version}" - _siterubyverarch = "$siterubyver/#{c['arch']}" -else - # V < 1.4.4 - _stdruby = "$prefix/lib/ruby/#{version}" - _siteruby = "$prefix/lib/ruby/#{version}/site_ruby" - _siterubyver = _siteruby - _siterubyverarch = "$siterubyver/#{c['arch']}" -end -libdir = '-* dummy libdir *-' -stdruby = '-* dummy rubylibdir *-' -siteruby = '-* dummy site_ruby *-' -siterubyver = '-* dummy site_ruby version *-' -parameterize = lambda {|path| - path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')\ - .sub(/\A#{Regexp.quote(libdir)}/, '$libdir')\ - .sub(/\A#{Regexp.quote(stdruby)}/, '$stdruby')\ - .sub(/\A#{Regexp.quote(siteruby)}/, '$siteruby')\ - .sub(/\A#{Regexp.quote(siterubyver)}/, '$siterubyver') -} -libdir = parameterize.call(c['libdir']) -stdruby = parameterize.call(_stdruby) -siteruby = parameterize.call(_siteruby) -siterubyver = parameterize.call(_siterubyver) -siterubyverarch = parameterize.call(_siterubyverarch) - -if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } - makeprog = arg.sub(/'/, '').split(/=/, 2)[1] -else - makeprog = 'make' -end + [ + ExecItem.new('installdirs', 'std/site/home', + 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ + {|val, table| + case val + when 'std' + table['rbdir'] = '$librubyver' + table['sodir'] = '$librubyverarch' + when 'site' + table['rbdir'] = '$siterubyver' + table['sodir'] = '$siterubyverarch' + when 'home' + setup_rb_error '$HOME was not set' unless ENV['HOME'] + table['prefix'] = ENV['HOME'] + table['rbdir'] = '$libdir/ruby' + table['sodir'] = '$libdir/ruby' + end + }, + PathItem.new('prefix', 'path', c['prefix'], + 'path prefix of target environment'), + PathItem.new('bindir', 'path', parameterize.call(c['bindir']), + 'the directory for commands'), + PathItem.new('libdir', 'path', parameterize.call(c['libdir']), + 'the directory for libraries'), + PathItem.new('datadir', 'path', parameterize.call(c['datadir']), + 'the directory for shared data'), + PathItem.new('mandir', 'path', parameterize.call(c['mandir']), + 'the directory for man pages'), + PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), + 'the directory for system configuration files'), + PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), + 'the directory for local state data'), + PathItem.new('libruby', 'path', libruby, + 'the directory for ruby libraries'), + PathItem.new('librubyver', 'path', librubyver, + 'the directory for standard ruby libraries'), + PathItem.new('librubyverarch', 'path', librubyverarch, + 'the directory for standard ruby extensions'), + PathItem.new('siteruby', 'path', siteruby, + 'the directory for version-independent aux ruby libraries'), + PathItem.new('siterubyver', 'path', siterubyver, + 'the directory for aux ruby libraries'), + PathItem.new('siterubyverarch', 'path', siterubyverarch, + 'the directory for aux ruby binaries'), + PathItem.new('rbdir', 'path', '$siterubyver', + 'the directory for ruby scripts'), + PathItem.new('sodir', 'path', '$siterubyverarch', + 'the directory for ruby extentions'), + PathItem.new('rubypath', 'path', rubypath, + 'the path to set to #! line'), + ProgramItem.new('rubyprog', 'name', rubypath, + 'the ruby program using for installation'), + ProgramItem.new('makeprog', 'name', makeprog, + 'the make program to compile ruby extentions'), + SelectItem.new('shebang', 'all/ruby/never', 'ruby', + 'shebang line (#!) editing mode'), + BoolItem.new('without-ext', 'yes/no', 'no', + 'does not compile/install ruby extentions') + ] + end + private :standard_entries + + def load_multipackage_entries + multipackage_entries().each do |ent| + add ent + end + end + + def multipackage_entries + [ + PackageSelectionItem.new('with', 'name,name...', '', 'ALL', + 'package names that you want to install'), + PackageSelectionItem.new('without', 'name,name...', '', 'NONE', + 'package names that you do not want to install') + ] + end + private :multipackage_entries -common_conf = [ - PathItem.new('prefix', 'path', c['prefix'], - 'path prefix of target environment'), - PathItem.new('bindir', 'path', parameterize.call(c['bindir']), - 'the directory for commands'), - PathItem.new('libdir', 'path', libdir, - 'the directory for libraries'), - PathItem.new('datadir', 'path', parameterize.call(c['datadir']), - 'the directory for shared data'), - PathItem.new('mandir', 'path', parameterize.call(c['mandir']), - 'the directory for man pages'), - PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), - 'the directory for man pages'), - PathItem.new('stdruby', 'path', stdruby, - 'the directory for standard ruby libraries'), - PathItem.new('siteruby', 'path', siteruby, - 'the directory for version-independent aux ruby libraries'), - PathItem.new('siterubyver', 'path', siterubyver, - 'the directory for aux ruby libraries'), - PathItem.new('siterubyverarch', 'path', siterubyverarch, - 'the directory for aux ruby binaries'), - PathItem.new('rbdir', 'path', '$siterubyver', - 'the directory for ruby scripts'), - PathItem.new('sodir', 'path', '$siterubyverarch', - 'the directory for ruby extentions'), - PathItem.new('rubypath', 'path', rubypath, - 'the path to set to #! line'), - ProgramItem.new('rubyprog', 'name', rubypath, - 'the ruby program using for installation'), - ProgramItem.new('makeprog', 'name', makeprog, - 'the make program to compile ruby extentions'), - SelectItem.new('shebang', 'all/ruby/never', 'ruby', - 'shebang line (#!) editing mode'), - BoolItem.new('without-ext', 'yes/no', 'no', - 'does not compile/install ruby extentions') -] -class ConfigTable_class # open again ALIASES = { - 'std-ruby' => 'stdruby', + 'std-ruby' => 'librubyver', + 'stdruby' => 'librubyver', + 'rubylibdir' => 'librubyver', + 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', @@ -365,78 +291,248 @@ class ConfigTable_class # open again 'make-prog' => 'makeprog', 'make' => 'makeprog' } -end -multipackage_conf = [ - PackageSelectionItem.new('with', 'name,name...', '', 'ALL', - 'package names that you want to install'), - PackageSelectionItem.new('without', 'name,name...', '', 'NONE', - 'package names that you do not want to install') -] -if multipackage_install? - ConfigTable = ConfigTable_class.new(common_conf + multipackage_conf) -else - ConfigTable = ConfigTable_class.new(common_conf) -end - -module MetaConfigAPI - - def eval_file_ifexist(fname) - instance_eval File.read(fname), fname, 1 if File.file?(fname) + def fixup + ALIASES.each do |ali, name| + @table[ali] = @table[name] + end + @items.freeze + @table.freeze + @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end - def config_names - ConfigTable.map {|i| i.name } + def parse_opt(opt) + m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" + m.to_a[1,2] end - def config?(name) - ConfigTable.key?(name) + def dllext + @rbconfig['DLEXT'] end - def bool_config?(name) - ConfigTable.lookup(name).config_type == 'bool' + def value_config?(name) + lookup(name).value? end - def path_config?(name) - ConfigTable.lookup(name).config_type == 'path' - end + class Item + def initialize(name, template, default, desc) + @name = name.freeze + @template = template + @value = default + @default = default + @description = desc + end - def value_config?(name) - case ConfigTable.lookup(name).config_type - when 'bool', 'path' + attr_reader :name + attr_reader :description + + attr_accessor :default + alias help_default default + + def help_opt + "--#{@name}=#{@template}" + end + + def value? true - else - false + end + + def value + @value + end + + def resolve(table) + @value.gsub(%r<\$([^/]+)>) { table[$1] } + end + + def set(val) + @value = check(val) + end + + private + + def check(val) + setup_rb_error "config: --#{name} requires argument" unless val + val end end - def add_config(item) - ConfigTable.add item + class BoolItem < Item + def config_type + 'bool' + end + + def help_opt + "--#{@name}" + end + + private + + def check(val) + return 'yes' unless val + case val + when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' + when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' + else + setup_rb_error "config: --#{@name} accepts only yes/no for argument" + end + end end - def add_bool_config(name, default, desc) - ConfigTable.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + class PathItem < Item + def config_type + 'path' + end + + private + + def check(path) + setup_rb_error "config: --#{@name} requires argument" unless path + path[0,1] == '$' ? path : File.expand_path(path) + end end - def add_path_config(name, default, desc) - ConfigTable.add PathItem.new(name, 'path', default, desc) + class ProgramItem < Item + def config_type + 'program' + end end - def set_config_default(name, default) - ConfigTable.lookup(name).default = default + class SelectItem < Item + def initialize(name, selection, default, desc) + super + @ok = selection.split('/') + end + + def config_type + 'select' + end + + private + + def check(val) + unless @ok.include?(val.strip) + setup_rb_error "config: use --#{@name}=#{@template} (#{val})" + end + val.strip + end end - def remove_config(name) - ConfigTable.remove(name) + class ExecItem < Item + def initialize(name, selection, desc, &block) + super name, selection, nil, desc + @ok = selection.split('/') + @action = block + end + + def config_type + 'exec' + end + + def value? + false + end + + def resolve(table) + setup_rb_error "$#{name()} wrongly used as option value" + end + + undef set + + def evaluate(val, table) + v = val.strip.downcase + unless @ok.include?(v) + setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" + end + @action.call v, table + end end -end + class PackageSelectionItem < Item + def initialize(name, template, default, help_default, desc) + super name, template, default, desc + @help_default = help_default + end + attr_reader :help_default -# -# File Operations -# + def config_type + 'package' + end + + private + + def check(val) + unless File.dir?("packages/#{val}") + setup_rb_error "config: no such package: #{val}" + end + val + end + end + + class MetaConfigEnvironment + def initialize(config, installer) + @config = config + @installer = installer + end + + def config_names + @config.names + end + + def config?(name) + @config.key?(name) + end + + def bool_config?(name) + @config.lookup(name).config_type == 'bool' + end + + def path_config?(name) + @config.lookup(name).config_type == 'path' + end + + def value_config?(name) + @config.lookup(name).config_type != 'exec' + end + + def add_config(item) + @config.add item + end + + def add_bool_config(name, default, desc) + @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) + end + def add_path_config(name, default, desc) + @config.add PathItem.new(name, 'path', default, desc) + end + + def set_config_default(name, default) + @config.lookup(name).default = default + end + + def remove_config(name) + @config.remove(name) + end + + # For only multipackage + def packages + raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer + @installer.packages + end + + # For only multipackage + def declare_packages(list) + raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer + @installer.packages = list + end + end + +end # class ConfigTable + + +# This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) @@ -444,7 +540,7 @@ module FileOperations $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? - # does not check '/'... it's too abnormal case + # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift @@ -456,49 +552,73 @@ module FileOperations end end - def rm_f(fname) - $stderr.puts "rm -f #{fname}" if verbose? + def rm_f(path) + $stderr.puts "rm -f #{path}" if verbose? return if no_harm? - - if File.exist?(fname) or File.symlink?(fname) - File.chmod 0777, fname - File.unlink fname - end + force_remove_file path end - def rm_rf(dn) - $stderr.puts "rm -rf #{dn}" if verbose? + def rm_rf(path) + $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? + remove_tree path + end + + def remove_tree(path) + if File.symlink?(path) + remove_file path + elsif File.dir?(path) + remove_tree0 path + else + force_remove_file path + end + end - Dir.chdir dn - Dir.foreach('.') do |fn| - next if fn == '.' - next if fn == '..' - if File.dir?(fn) - verbose_off { - rm_rf fn - } + def remove_tree0(path) + Dir.foreach(path) do |ent| + next if ent == '.' + next if ent == '..' + entpath = "#{path}/#{ent}" + if File.symlink?(entpath) + remove_file entpath + elsif File.dir?(entpath) + remove_tree0 entpath else - verbose_off { - rm_f fn - } + force_remove_file entpath end end - Dir.chdir '..' - Dir.rmdir dn + begin + Dir.rmdir path + rescue Errno::ENOTEMPTY + # directory may not be empty + end end def move_file(src, dest) - File.unlink dest if File.exist?(dest) + force_remove_file dest begin File.rename src, dest rescue - File.open(dest, 'wb') {|f| f.write File.binread(src) } + File.open(dest, 'wb') {|f| + f.write File.binread(src) + } File.chmod File.stat(src).mode, dest File.unlink src end end + def force_remove_file(path) + begin + remove_file path + rescue + end + end + + def remove_file(path) + File.chmod 0777, path + File.unlink path + end + def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? @@ -530,66 +650,42 @@ module FileOperations new_content != File.binread(path) end - def command(str) - $stderr.puts str if verbose? - system str or raise RuntimeError, "'system #{str}' failed" + def command(*args) + $stderr.puts args.join(' ') if verbose? + system(*args) or raise RuntimeError, + "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end - def ruby(str) - command config('rubyprog') + ' ' + str + def ruby(*args) + command config('rubyprog'), *args end - def make(task = '') - command config('makeprog') + ' ' + task + def make(task = nil) + command(*[config('makeprog'), task].compact) end def extdir?(dir) - File.exist?(dir + '/MANIFEST') + File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end - def all_files_in(dirname) - Dir.open(dirname) {|d| - return d.select {|ent| File.file?("#{dirname}/#{ent}") } + def files_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.file?("#{dir}/#{ent}") } } end - REJECT_DIRS = %w( - CVS SCCS RCS CVS.adm .svn - ) + DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) - def all_dirs_in(dirname) - Dir.open(dirname) {|d| - return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS + def directories_of(dir) + Dir.open(dir) {|d| + return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end -# -# Main Installer -# - -module HookUtils - - def run_hook(name) - try_run_hook "#{curr_srcdir()}/#{name}" or - try_run_hook "#{curr_srcdir()}/#{name}.rb" - end - - def try_run_hook(fname) - return false unless File.file?(fname) - begin - instance_eval File.read(fname), fname, 1 - rescue - setup_rb_error "hook #{fname} failed:\n" + $!.message - end - true - end - -end - - +# This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @@ -598,6 +694,7 @@ module HookScriptAPI alias config get_config + # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end @@ -606,10 +703,6 @@ module HookScriptAPI # srcdir/objdir (works only in the package directory) # - #abstract srcdir_root - #abstract objdir_root - #abstract relpath - def curr_srcdir "#{srcdir_root()}/#{relpath()}" end @@ -631,7 +724,7 @@ module HookScriptAPI end def srcfile?(path) - File.file? srcfile(path) + File.file?(srcfile(path)) end def srcentries(path = '.') @@ -657,8 +750,8 @@ end class ToplevelInstaller - Version = '3.3.1' - Copyright = 'Copyright (c) 2000-2004 Minero Aoki' + Version = '3.4.1' + Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], @@ -666,27 +759,44 @@ class ToplevelInstaller [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], + [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke - instance().invoke + config = ConfigTable.new(load_rbconfig()) + config.load_standard_entries + config.load_multipackage_entries if multipackage? + config.fixup + klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) + klass.new(File.dirname($0), config).invoke end - @singleton = nil - - def ToplevelInstaller.instance - @singleton ||= new(File.dirname($0)) - @singleton + def ToplevelInstaller.multipackage? + File.dir?(File.dirname($0) + '/packages') end - include MetaConfigAPI + def ToplevelInstaller.load_rbconfig + if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } + ARGV.delete(arg) + load File.expand_path(arg.split(/=/, 2)[1]) + $".push 'rbconfig.rb' + else + require 'rbconfig' + end + ::Config::CONFIG + end - def initialize(ardir_root) - @config = nil - @options = { 'verbose' => true } + def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) + @config = config + # cache + @valid_task_re = nil + end + + def config(key) + @config[key] end def inspect @@ -697,14 +807,20 @@ class ToplevelInstaller run_metaconfigs case task = parsearg_global() when nil, 'all' - @config = load_config('config') parsearg_config init_installers exec_config exec_setup exec_install else - @config = load_config(task) + case task + when 'config', 'test' + ; + when 'clean', 'distclean' + @config.load_savefile if File.exist?(@config.savefile) + else + @config.load_savefile + end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" @@ -712,25 +828,11 @@ class ToplevelInstaller end def run_metaconfigs - eval_file_ifexist "#{@ardir}/metaconfig" - end - - def load_config(task) - case task - when 'config' - ConfigTable.new - when 'clean', 'distclean' - if File.exist?(ConfigTable.savefile) - then ConfigTable.load - else ConfigTable.new - end - else - ConfigTable.load - end + @config.load_script "#{@ardir}/metaconfig" end def init_installers - @installer = Installer.new(@config, @options, @ardir, File.expand_path('.')) + @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # @@ -754,78 +856,89 @@ class ToplevelInstaller # def parsearg_global - valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/ - while arg = ARGV.shift case arg when /\A\w+\z/ - setup_rb_error "invalid task: #{arg}" unless valid_task =~ arg + setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg - when '-q', '--quiet' - @options['verbose'] = false - - when '--verbose' - @options['verbose'] = true - - when '-h', '--help' + @config.verbose = false + when '--verbose' + @config.verbose = true + when '--help' print_usage $stdout exit 0 - - when '-v', '--version' + when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 - when '--copyright' puts Copyright exit 0 - else setup_rb_error "unknown global option '#{arg}'" end end - nil end + def valid_task?(t) + valid_task_re() =~ t + end + + def valid_task_re + @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ + end def parsearg_no_options unless ARGV.empty? - setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}" + task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) + setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options + alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config - re = /\A--(#{ConfigTable.map {|i| i.name }.join('|')})(?:=(.*))?\z/ - @options['config-opt'] = [] - + evalopt = [] + set = [] + @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i - @options['config-opt'] = ARGV.dup + @config.config_opt = ARGV.dup break end - m = re.match(i) or setup_rb_error "config: unknown option #{i}" - name, value = *m.to_a[1,2] - @config[name] = value + name, value = *@config.parse_opt(i) + if @config.value_config?(name) + @config[name] = value + else + evalopt.push [name, value] + end + set.push name + end + evalopt.each do |name, value| + @config.lookup(name).evaluate value, @config + end + # Check if configuration is valid + set.each do |n| + @config[n] if @config.value_config?(n) end end def parsearg_install - @options['no-harm'] = false - @options['install-prefix'] = '' + @config.no_harm = false + @config.install_prefix = '' while a = ARGV.shift case a - when /\A--no-harm\z/ - @options['no-harm'] = true - when /\A--prefix=(.*)\z/ - path = $1 + when '--no-harm' + @config.no_harm = true + when /\A--prefix=/ + path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' - @options['install-prefix'] = path + @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end @@ -847,8 +960,8 @@ class ToplevelInstaller out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' - out.printf fmt, '-h,--help', 'print this message' - out.printf fmt, '-v,--version', 'print version and quit' + out.printf fmt, ' --help', 'print this message' + out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' @@ -859,14 +972,14 @@ class ToplevelInstaller fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' - ConfigTable.each do |item| + @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' - out.printf fmt, '--prefix=path', 'install path prefix', '$prefix' + out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end @@ -887,9 +1000,13 @@ class ToplevelInstaller @installer.exec_install end + def exec_test + @installer.exec_test + end + def exec_show - ConfigTable.each do |i| - printf "%-20s %s\n", i.name, i.value + @config.each do |i| + printf "%-20s %s\n", i.name, i.value if i.value? end end @@ -901,36 +1018,45 @@ class ToplevelInstaller @installer.exec_distclean end -end +end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller - include HookUtils - include HookScriptAPI include FileOperations - def initialize(ardir) + def initialize(ardir_root, config) super - @packages = all_dirs_in("#{@ardir}/packages") + @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? + @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs - eval_file_ifexist "#{@ardir}/metaconfig" + @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| - eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig" + @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end + attr_reader :packages + + def packages=(list) + raise 'package list is empty' if list.empty? + list.each do |name| + raise "directory packages/#{name} does not exist"\ + unless File.dir?("#{@ardir}/packages/#{name}") + end + @packages = list + end + def init_installers @installers = {} @packages.each do |pack| - @installers[pack] = Installer.new(@config, @options, + @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end - with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| @@ -954,21 +1080,6 @@ class ToplevelInstallerMulti < ToplevelInstaller f.puts end - # - # multi-package metaconfig API - # - - attr_reader :packages - - def declare_packages(list) - raise 'package list is empty' if list.empty? - list.each do |name| - raise "directory packages/#{name} does not exist"\ - unless File.dir?("#{@ardir}/packages/#{name}") - end - @packages = list - end - # # Task Handlers # @@ -992,15 +1103,21 @@ class ToplevelInstallerMulti < ToplevelInstaller run_hook 'post-install' end + def exec_test + run_hook 'pre-test' + each_selected_installers {|inst| inst.exec_test } + run_hook 'post-test' + end + def exec_clean - rm_f ConfigTable.savefile + rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean - rm_f ConfigTable.savefile + rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' @@ -1013,7 +1130,7 @@ class ToplevelInstallerMulti < ToplevelInstaller def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| - $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose'] + $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] @@ -1021,28 +1138,32 @@ class ToplevelInstallerMulti < ToplevelInstaller end end + def run_hook(id) + @root_installer.run_hook id + end + + # module FileOperations requires this def verbose? - @options['verbose'] + @config.verbose? end + # module FileOperations requires this def no_harm? - @options['no-harm'] + @config.no_harm? end -end +end # class ToplevelInstallerMulti class Installer - FILETYPES = %w( bin lib ext data ) + FILETYPES = %w( bin lib ext data conf man ) - include HookScriptAPI - include HookUtils include FileOperations + include HookScriptAPI - def initialize(config, opt, srcroot, objroot) + def initialize(config, srcroot, objroot) @config = config - @options = opt @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' @@ -1052,6 +1173,9 @@ class Installer "#<#{self.class} #{File.basename(@srcdir)}>" end + def noop(rel) + end + # # Hook Script API base methods # @@ -1069,23 +1193,25 @@ class Installer end # - # configs/options + # Config Access # - def no_harm? - @options['no-harm'] + # module FileOperations requires this + def verbose? + @config.verbose? end - def verbose? - @options['verbose'] + # module FileOperations requires this + def no_harm? + @config.no_harm? end def verbose_off begin - save, @options['verbose'] = @options['verbose'], false + save, @config.verbose = @config.verbose?, false yield ensure - @options['verbose'] = save + @config.verbose = save end end @@ -1097,22 +1223,19 @@ class Installer exec_task_traverse 'config' end - def config_dir_bin(rel) - end - - def config_dir_lib(rel) - end + alias config_dir_bin noop + alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end - def extconf - opt = @options['config-opt'].join(' ') - command "#{config('rubyprog')} #{curr_srcdir()}/extconf.rb #{opt}" - end + alias config_dir_data noop + alias config_dir_conf noop + alias config_dir_man noop - def config_dir_data(rel) + def extconf + ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # @@ -1124,39 +1247,90 @@ class Installer end def setup_dir_bin(rel) - all_files_in(curr_srcdir()).each do |fname| - adjust_shebang "#{curr_srcdir()}/#{fname}" + files_of(curr_srcdir()).each do |fname| + update_shebang_line "#{curr_srcdir()}/#{fname}" end end - def adjust_shebang(path) + alias setup_dir_lib noop + + def setup_dir_ext(rel) + make if extdir?(curr_srcdir()) + end + + alias setup_dir_data noop + alias setup_dir_conf noop + alias setup_dir_man noop + + def update_shebang_line(path) return if no_harm? + return if config('shebang') == 'never' + old = Shebang.load(path) + if old + $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 + new = new_shebang(old) + return if new.to_s == old.to_s + else + return unless config('shebang') == 'all' + new = Shebang.new(config('rubypath')) + end + $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? + open_atomic_writer(path) {|output| + File.open(path, 'rb') {|f| + f.gets if old # discard + output.puts new.to_s + output.print f.read + } + } + end + + def new_shebang(old) + if /\Aruby/ =~ File.basename(old.cmd) + Shebang.new(config('rubypath'), old.args) + elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' + Shebang.new(config('rubypath'), old.args[1..-1]) + else + return old unless config('shebang') == 'all' + Shebang.new(config('rubypath')) + end + end + + def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin - File.open(path, 'rb') {|r| - first = r.gets - return unless File.basename(config('rubypath')) == 'ruby' - return unless File.basename(first.sub(/\A\#!/, '').split[0]) == 'ruby' - $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? - File.open(tmpfile, 'wb') {|w| - w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath')) - w.write r.read - } - move_file tmpfile, File.basename(path) - } + File.open(tmpfile, 'wb', &block) + File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end - def setup_dir_lib(rel) - end + class Shebang + def Shebang.load(path) + line = nil + File.open(path) {|f| + line = f.gets + } + return nil unless /\A#!/ =~ line + parse(line) + end - def setup_dir_ext(rel) - make if extdir?(curr_srcdir()) - end + def Shebang.parse(line) + cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') + new(cmd, args) + end + + def initialize(cmd, args = []) + @cmd = cmd + @args = args + end - def setup_dir_data(rel) + attr_reader :cmd + attr_reader :args + + def to_s + "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") + end end # @@ -1169,63 +1343,77 @@ class Installer end def install_dir_bin(rel) - install_files collect_filenames_auto(), "#{config('bindir')}/#{rel}", 0755 + install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) - install_files ruby_scripts(), "#{config('rbdir')}/#{rel}", 0644 + install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) - install_files ruby_extentions('.'), + install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) - install_files collect_filenames_auto(), "#{config('datadir')}/#{rel}", 0644 + install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 + end + + def install_dir_conf(rel) + # FIXME: should not remove current config files + # (rename previous file to .old/.org) + install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 + end + + def install_dir_man(rel) + install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) - mkdir_p dest, @options['install-prefix'] + mkdir_p dest, @config.install_prefix list.each do |fname| - install fname, dest, mode, @options['install-prefix'] + install fname, dest, mode, @config.install_prefix end end - def ruby_scripts - collect_filenames_auto().select {|n| /\.rb\z/ =~ n } + def libfiles + glob_reject(%w(*.y *.output), targetfiles()) end - + + def rubyextentions(dir) + ents = glob_select("*.#{@config.dllext}", targetfiles()) + if ents.empty? + setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" + end + ents + end + + def targetfiles + mapdir(existfiles() - hookfiles()) + end + + def mapdir(ents) + ents.map {|ent| + if File.exist?(ent) + then ent # objdir + else "#{curr_srcdir()}/#{ent}" # srcdir + end + } + end + # picked up many entries from cvs-1.11.1/src/ignore.c - reject_patterns = %w( + JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) - mapping = { - '.' => '\.', - '$' => '\$', - '#' => '\#', - '*' => '.*' - } - REJECT_PATTERNS = Regexp.new('\A(?:' + - reject_patterns.map {|pat| - pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] } - }.join('|') + - ')\z') - - def collect_filenames_auto - mapdir((existfiles() - hookfiles()).reject {|fname| - REJECT_PATTERNS =~ fname - }) - end def existfiles - all_files_in(curr_srcdir()) | all_files_in('.') + glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles @@ -1234,24 +1422,49 @@ class Installer }.flatten end - def mapdir(filelist) - filelist.map {|fname| - if File.exist?(fname) # objdir - fname - else # srcdir - File.join(curr_srcdir(), fname) - end - } + def glob_select(pat, ents) + re = globs2re([pat]) + ents.select {|ent| re =~ ent } end - def ruby_extentions(dir) - Dir.open(dir) {|d| - ents = d.select {|fname| /\.#{::Config::CONFIG['DLEXT']}\z/ =~ fname } - if ents.empty? - setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" - end - return ents - } + def glob_reject(pats, ents) + re = globs2re(pats) + ents.reject {|ent| re =~ ent } + end + + GLOB2REGEX = { + '.' => '\.', + '$' => '\$', + '#' => '\#', + '*' => '.*' + } + + def globs2re(pats) + /\A(?:#{ + pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') + })\z/ + end + + # + # TASK test + # + + TESTDIR = 'test' + + def exec_test + unless File.directory?('test') + $stderr.puts 'no test in this package' if verbose? + return + end + $stderr.puts 'Running tests...' if verbose? + begin + require 'test/unit' + rescue LoadError + setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' + end + runner = Test::Unit::AutoRunner.new(true) + runner.to_run << TESTDIR + runner.run end # @@ -1260,53 +1473,51 @@ class Installer def exec_clean exec_task_traverse 'clean' - rm_f ConfigTable.savefile + rm_f @config.savefile rm_f 'InstalledFiles' end - def clean_dir_bin(rel) - end - - def clean_dir_lib(rel) - end + alias clean_dir_bin noop + alias clean_dir_lib noop + alias clean_dir_data noop + alias clean_dir_conf noop + alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end - def clean_dir_data(rel) - end - # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' - rm_f ConfigTable.savefile + rm_f @config.savefile rm_f 'InstalledFiles' end - def distclean_dir_bin(rel) - end - - def distclean_dir_lib(rel) - end + alias distclean_dir_bin noop + alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end + alias distclean_dir_data noop + alias distclean_dir_conf noop + alias distclean_dir_man noop + # - # lib + # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| - if config('without-ext') == 'yes' and type == 'ext' + if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end @@ -1319,7 +1530,7 @@ class Installer dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') - all_dirs_in(curr_srcdir()).each do |d| + directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" @@ -1341,16 +1552,30 @@ class Installer @currdir = File.dirname(rel) end -end + def run_hook(id) + path = [ "#{curr_srcdir()}/#{id}", + "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } + return unless path + begin + instance_eval File.read(path), path, 1 + rescue + raise if $DEBUG + setup_rb_error "hook #{path} failed:\n" + $!.message + end + end + +end # class Installer + + +class SetupError < StandardError; end +def setup_rb_error(msg) + raise SetupError, msg +end if $0 == __FILE__ begin - if multipackage_install? - ToplevelInstallerMulti.invoke - else - ToplevelInstaller.invoke - end + ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message -- cgit v1.2.3-24-ge0c7