Rev 30 | Blame | Compare with Previous | Last modification | View Log | RSS feed
## setup.rb## Copyright (c) 2000-2005 Minero Aoki## This program is free software.# You can distribute/modify this program under the terms of# the GNU LGPL, Lesser General Public License version 2.1.#unless Enumerable.method_defined?(:map) # Ruby 1.4.6module Enumerablealias map collectendendunless File.respond_to?(:read) # Ruby 1.6def File.read(fname)open(fname) {|f|return f.read}endendunless Errno.const_defined?(:ENOTEMPTY) # Windows?module Errnoclass ENOTEMPTY# We do not raise this exception, implementation is not needed.endendenddef File.binread(fname)open(fname, 'rb') {|f|return f.read}end# for corrupted Windows' stat(2)def File.dir?(path)File.directory?((path[-1,1] == '/') ? path : path + '/')endclass ConfigTableinclude Enumerabledef initialize(rbconfig)@rbconfig = rbconfig@items = []@table = {}# options@install_prefix = nil@config_opt = nil@verbose = true@no_harm = falseendattr_accessor :install_prefixattr_accessor :config_optattr_writer :verbosedef verbose?@verboseendattr_writer :no_harmdef no_harm?@no_harmenddef [](key)lookup(key).resolve(self)enddef []=(key, val)lookup(key).set valenddef names@items.map {|i| i.name }enddef each(&block)@items.each(&block)enddef key?(name)@table.key?(name)enddef lookup(name)@table[name] or setup_rb_error "no such config item: #{name}"enddef add(item)@items.push item@table[item.name] = itemenddef remove(name)item = lookup(name)@items.delete_if {|i| i.name == name }@table.delete_if {|name, i| i.name == name }itemenddef load_script(path, inst = nil)if File.file?(path)MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), pathendenddef savefile'.config'enddef load_savefilebeginFile.foreach(savefile()) do |line|k, v = *line.split(/=/, 2)self[k] = v.stripendrescue Errno::ENOENTsetup_rb_error $!.message + "\n#{File.basename($0)} config first"endenddef save@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? and i.valueend}enddef load_standard_entriesstandard_entries(@rbconfig).each do |ent|add entendenddef standard_entries(rbconfig)c = rbconfigrubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])major = c['MAJOR'].to_iminor = c['MINOR'].to_iteeny = c['TEENY'].to_iversion = "#{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.3libruby = "#{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.3libruby = "#{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.4libruby = "#{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 = siterubysiterubyverarch = "$siterubyver/#{c['arch']}"endparameterize = lambda {|path|path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')}if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }makeprog = arg.sub(/'/, '').split(/=/, 2)[1]elsemakeprog = 'make'end[ExecItem.new('installdirs', 'std/site/home','std: install under libruby; site: install under site_ruby; home: install under $HOME')\{|val, table|case valwhen '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')]endprivate :standard_entriesdef load_multipackage_entriesmultipackage_entries().each do |ent|add entendenddef 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')]endprivate :multipackage_entriesALIASES = {'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','bin-dir' => 'bindir','rb-dir' => 'rbdir','so-dir' => 'sodir','data-dir' => 'datadir','ruby-path' => 'rubypath','ruby-prog' => 'rubyprog','ruby' => 'rubyprog','make-prog' => 'makeprog','make' => 'makeprog'}def fixupALIASES.each do |ali, name|@table[ali] = @table[name]end@items.freeze@table.freeze@options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/enddef parse_opt(opt)m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"m.to_a[1,2]enddef dllext@rbconfig['DLEXT']enddef value_config?(name)lookup(name).value?endclass Itemdef initialize(name, template, default, desc)@name = name.freeze@template = template@value = default@default = default@description = descendattr_reader :nameattr_reader :descriptionattr_accessor :defaultalias help_default defaultdef help_opt"--#{@name}=#{@template}"enddef value?trueenddef value@valueenddef resolve(table)@value.gsub(%r<\$([^/]+)>) { table[$1] }enddef set(val)@value = check(val)endprivatedef check(val)setup_rb_error "config: --#{name} requires argument" unless valvalendendclass BoolItem < Itemdef config_type'bool'enddef help_opt"--#{@name}"endprivatedef check(val)return 'yes' unless valcase valwhen /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'elsesetup_rb_error "config: --#{@name} accepts only yes/no for argument"endendendclass PathItem < Itemdef config_type'path'endprivatedef check(path)setup_rb_error "config: --#{@name} requires argument" unless pathpath[0,1] == '$' ? path : File.expand_path(path)endendclass ProgramItem < Itemdef config_type'program'endendclass SelectItem < Itemdef initialize(name, selection, default, desc)super@ok = selection.split('/')enddef config_type'select'endprivatedef check(val)unless @ok.include?(val.strip)setup_rb_error "config: use --#{@name}=#{@template} (#{val})"endval.stripendendclass ExecItem < Itemdef initialize(name, selection, desc, &block)super name, selection, nil, desc@ok = selection.split('/')@action = blockenddef config_type'exec'enddef value?falseenddef resolve(table)setup_rb_error "$#{name()} wrongly used as option value"endundef setdef evaluate(val, table)v = val.strip.downcaseunless @ok.include?(v)setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"end@action.call v, tableendendclass PackageSelectionItem < Itemdef initialize(name, template, default, help_default, desc)super name, template, default, desc@help_default = help_defaultendattr_reader :help_defaultdef config_type'package'endprivatedef check(val)unless File.dir?("packages/#{val}")setup_rb_error "config: no such package: #{val}"endvalendendclass MetaConfigEnvironmentdef initialize(config, installer)@config = config@installer = installerenddef config_names@config.namesenddef config?(name)@config.key?(name)enddef bool_config?(name)@config.lookup(name).config_type == 'bool'enddef path_config?(name)@config.lookup(name).config_type == 'path'enddef value_config?(name)@config.lookup(name).config_type != 'exec'enddef add_config(item)@config.add itemenddef add_bool_config(name, default, desc)@config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)enddef add_path_config(name, default, desc)@config.add PathItem.new(name, 'path', default, desc)enddef set_config_default(name, default)@config.lookup(name).default = defaultenddef remove_config(name)@config.remove(name)end# For only multipackagedef packagesraise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer@installer.packagesend# For only multipackagedef 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 = listendendend # class ConfigTable# This module requires: #verbose?, #no_harm?module FileOperationsdef mkdir_p(dirname, prefix = nil)dirname = prefix + File.expand_path(dirname) if prefix$stderr.puts "mkdir -p #{dirname}" if verbose?return if no_harm?# Does not check '/', it's too abnormal.dirs = File.expand_path(dirname).split(%r<(?=/)>)if /\A[a-z]:\z/i =~ dirs[0]disk = dirs.shiftdirs[0] = disk + dirs[0]enddirs.each_index do |idx|path = dirs[0..idx].join('')Dir.mkdir path unless File.dir?(path)endenddef rm_f(path)$stderr.puts "rm -f #{path}" if verbose?return if no_harm?force_remove_file pathenddef rm_rf(path)$stderr.puts "rm -rf #{path}" if verbose?return if no_harm?remove_tree pathenddef remove_tree(path)if File.symlink?(path)remove_file pathelsif File.dir?(path)remove_tree0 pathelseforce_remove_file pathendenddef remove_tree0(path)Dir.foreach(path) do |ent|next if ent == '.'next if ent == '..'entpath = "#{path}/#{ent}"if File.symlink?(entpath)remove_file entpathelsif File.dir?(entpath)remove_tree0 entpathelseforce_remove_file entpathendendbeginDir.rmdir pathrescue Errno::ENOTEMPTY# directory may not be emptyendenddef move_file(src, dest)force_remove_file destbeginFile.rename src, destrescueFile.open(dest, 'wb') {|f|f.write File.binread(src)}File.chmod File.stat(src).mode, destFile.unlink srcendenddef force_remove_file(path)beginremove_file pathrescueendenddef remove_file(path)File.chmod 0777, pathFile.unlink pathenddef install(from, dest, mode, prefix = nil)$stderr.puts "install #{from} #{dest}" if verbose?return if no_harm?realdest = prefix ? prefix + File.expand_path(dest) : destrealdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)str = File.binread(from)if diff?(str, realdest)verbose_off {rm_f realdest if File.exist?(realdest)}File.open(realdest, 'wb') {|f|f.write str}File.chmod mode, realdestFile.open("#{objdir_root()}/InstalledFiles", 'a') {|f|if prefixf.puts realdest.sub(prefix, '')elsef.puts realdestend}endenddef diff?(new_content, path)return true unless File.exist?(path)new_content != File.binread(path)enddef command(*args)$stderr.puts args.join(' ') if verbose?system(*args) or raise RuntimeError,"system(#{args.map{|a| a.inspect }.join(' ')}) failed"enddef ruby(*args)command config('rubyprog'), *argsenddef make(task = nil)command(*[config('makeprog'), task].compact)enddef extdir?(dir)File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")enddef files_of(dir)Dir.open(dir) {|d|return d.select {|ent| File.file?("#{dir}/#{ent}") }}endDIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )def directories_of(dir)Dir.open(dir) {|d|return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT}endend# This module requires: #srcdir_root, #objdir_root, #relpathmodule HookScriptAPIdef get_config(key)@config[key]endalias config get_config# obsolete: use metaconfig to change configurationdef set_config(key, val)@config[key] = valend## srcdir/objdir (works only in the package directory)#def curr_srcdir"#{srcdir_root()}/#{relpath()}"enddef curr_objdir"#{objdir_root()}/#{relpath()}"enddef srcfile(path)"#{curr_srcdir()}/#{path}"enddef srcexist?(path)File.exist?(srcfile(path))enddef srcdirectory?(path)File.dir?(srcfile(path))enddef srcfile?(path)File.file?(srcfile(path))enddef srcentries(path = '.')Dir.open("#{curr_srcdir()}/#{path}") {|d|return d.to_a - %w(. ..)}enddef srcfiles(path = '.')srcentries(path).select {|fname|File.file?(File.join(curr_srcdir(), path, fname))}enddef srcdirectories(path = '.')srcentries(path).select {|fname|File.dir?(File.join(curr_srcdir(), path, fname))}endendclass ToplevelInstallerVersion = '3.4.1'Copyright = 'Copyright (c) 2000-2005 Minero Aoki'TASKS = [[ 'all', 'do config, setup, then install' ],[ 'config', 'saves your configurations' ],[ '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.invokeconfig = ConfigTable.new(load_rbconfig())config.load_standard_entriesconfig.load_multipackage_entries if multipackage?config.fixupklass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)klass.new(File.dirname($0), config).invokeenddef ToplevelInstaller.multipackage?File.dir?(File.dirname($0) + '/packages')enddef ToplevelInstaller.load_rbconfigif arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }ARGV.delete(arg)load File.expand_path(arg.split(/=/, 2)[1])$".push 'rbconfig.rb'elserequire 'rbconfig'end::Config::CONFIGenddef initialize(ardir_root, config)@ardir = File.expand_path(ardir_root)@config = config# cache@valid_task_re = nilenddef config(key)@config[key]enddef inspect"#<#{self.class} #{__id__()}>"enddef invokerun_metaconfigscase task = parsearg_global()when nil, 'all'parsearg_configinit_installersexec_configexec_setupexec_installelsecase taskwhen 'config', 'test';when 'clean', 'distclean'@config.load_savefile if File.exist?(@config.savefile)else@config.load_savefileend__send__ "parsearg_#{task}"init_installers__send__ "exec_#{task}"endenddef run_metaconfigs@config.load_script "#{@ardir}/metaconfig"enddef init_installers@installer = Installer.new(@config, @ardir, File.expand_path('.'))end## Hook Script API bases#def srcdir_root@ardirenddef objdir_root'.'enddef relpath'.'end## Option Parsing#def parsearg_globalwhile arg = ARGV.shiftcase argwhen /\A\w+\z/setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)return argwhen '-q', '--quiet'@config.verbose = falsewhen '--verbose'@config.verbose = truewhen '--help'print_usage $stdoutexit 0when '--version'puts "#{File.basename($0)} version #{Version}"exit 0when '--copyright'puts Copyrightexit 0elsesetup_rb_error "unknown global option '#{arg}'"endendnilenddef valid_task?(t)valid_task_re() =~ tenddef valid_task_re@valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/enddef parsearg_no_optionsunless ARGV.empty?task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"endendalias parsearg_show parsearg_no_optionsalias parsearg_setup parsearg_no_optionsalias parsearg_test parsearg_no_optionsalias parsearg_clean parsearg_no_optionsalias parsearg_distclean parsearg_no_optionsdef parsearg_configevalopt = []set = []@config.config_opt = []while i = ARGV.shiftif /\A--?\z/ =~ i@config.config_opt = ARGV.dupbreakendname, value = *@config.parse_opt(i)if @config.value_config?(name)@config[name] = valueelseevalopt.push [name, value]endset.push nameendevalopt.each do |name, value|@config.lookup(name).evaluate value, @configend# Check if configuration is validset.each do |n|@config[n] if @config.value_config?(n)endenddef parsearg_install@config.no_harm = false@config.install_prefix = ''while a = ARGV.shiftcase awhen '--no-harm'@config.no_harm = truewhen /\A--prefix=/path = a.split(/=/, 2)[1]path = File.expand_path(path) unless path[0,1] == '/'@config.install_prefix = pathelsesetup_rb_error "install: unknown option #{a}"endendenddef print_usage(out)out.puts 'Typical Installation Procedure:'out.puts " $ ruby #{File.basename $0} config"out.puts " $ ruby #{File.basename $0} setup"out.puts " # ruby #{File.basename $0} install (may require root privilege)"out.putsout.puts 'Detailed Usage:'out.puts " ruby #{File.basename $0} <global option>"out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"fmt = " %-24s %s\n"out.putsout.puts 'Global options:'out.printf fmt, '-q,--quiet', 'suppress message outputs'out.printf fmt, ' --verbose', 'output messages verbosely'out.printf fmt, ' --help', 'print this message'out.printf fmt, ' --version', 'print version and quit'out.printf fmt, ' --copyright', 'print copyright and quit'out.putsout.puts 'Tasks:'TASKS.each do |name, desc|out.printf fmt, name, descendfmt = " %-24s %s [%s]\n"out.putsout.puts 'Options for CONFIG or ALL:'@config.each do |item|out.printf fmt, item.help_opt, item.description, item.help_defaultendout.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"out.putsout.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', ''out.putsend## Task Handlers#def exec_config@installer.exec_config@config.save # must be finalenddef exec_setup@installer.exec_setupenddef exec_install@installer.exec_installenddef exec_test@installer.exec_testenddef exec_show@config.each do |i|printf "%-20s %s\n", i.name, i.value if i.value?endenddef exec_clean@installer.exec_cleanenddef exec_distclean@installer.exec_distcleanendend # class ToplevelInstallerclass ToplevelInstallerMulti < ToplevelInstallerinclude FileOperationsdef initialize(ardir_root, config)super@packages = directories_of("#{@ardir}/packages")raise 'no package exists' if @packages.empty?@root_installer = Installer.new(@config, @ardir, File.expand_path('.'))enddef run_metaconfigs@config.load_script "#{@ardir}/metaconfig", self@packages.each do |name|@config.load_script "#{@ardir}/packages/#{name}/metaconfig"endendattr_reader :packagesdef 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 = listenddef init_installers@installers = {}@packages.each do |pack|@installers[pack] = Installer.new(@config,"#{@ardir}/packages/#{pack}","packages/#{pack}")endwith = extract_selection(config('with'))without = extract_selection(config('without'))@selected = @installers.keys.select {|name|(with.empty? or with.include?(name)) \and not without.include?(name)}enddef extract_selection(list)a = list.split(/,/)a.each do |name|setup_rb_error "no such package: #{name}" unless @installers.key?(name)endaenddef print_usage(f)superf.puts 'Inluded packages:'f.puts ' ' + @packages.sort.join(' ')f.putsend## Task Handlers#def exec_configrun_hook 'pre-config'each_selected_installers {|inst| inst.exec_config }run_hook 'post-config'@config.save # must be finalenddef exec_setuprun_hook 'pre-setup'each_selected_installers {|inst| inst.exec_setup }run_hook 'post-setup'enddef exec_installrun_hook 'pre-install'each_selected_installers {|inst| inst.exec_install }run_hook 'post-install'enddef exec_testrun_hook 'pre-test'each_selected_installers {|inst| inst.exec_test }run_hook 'post-test'enddef exec_cleanrm_f @config.savefilerun_hook 'pre-clean'each_selected_installers {|inst| inst.exec_clean }run_hook 'post-clean'enddef exec_distcleanrm_f @config.savefilerun_hook 'pre-distclean'each_selected_installers {|inst| inst.exec_distclean }run_hook 'post-distclean'end## lib#def each_selected_installersDir.mkdir 'packages' unless File.dir?('packages')@selected.each do |pack|$stderr.puts "Processing the package `#{pack}' ..." if verbose?Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")Dir.chdir "packages/#{pack}"yield @installers[pack]Dir.chdir '../..'endenddef run_hook(id)@root_installer.run_hook idend# module FileOperations requires thisdef verbose?@config.verbose?end# module FileOperations requires thisdef no_harm?@config.no_harm?endend # class ToplevelInstallerMulticlass InstallerFILETYPES = %w( bin lib ext data conf man )include FileOperationsinclude HookScriptAPIdef initialize(config, srcroot, objroot)@config = config@srcdir = File.expand_path(srcroot)@objdir = File.expand_path(objroot)@currdir = '.'enddef inspect"#<#{self.class} #{File.basename(@srcdir)}>"enddef noop(rel)end## Hook Script API base methods#def srcdir_root@srcdirenddef objdir_root@objdirenddef relpath@currdirend## Config Access## module FileOperations requires thisdef verbose?@config.verbose?end# module FileOperations requires thisdef no_harm?@config.no_harm?enddef verbose_offbeginsave, @config.verbose = @config.verbose?, falseyieldensure@config.verbose = saveendend## TASK config#def exec_configexec_task_traverse 'config'endalias config_dir_bin noopalias config_dir_lib noopdef config_dir_ext(rel)extconf if extdir?(curr_srcdir())endalias config_dir_data noopalias config_dir_conf noopalias config_dir_man noopdef extconfruby "#{curr_srcdir()}/extconf.rb", *@config.config_optend## TASK setup#def exec_setupexec_task_traverse 'setup'enddef setup_dir_bin(rel)files_of(curr_srcdir()).each do |fname|update_shebang_line "#{curr_srcdir()}/#{fname}"endendalias setup_dir_lib noopdef setup_dir_ext(rel)make if extdir?(curr_srcdir())endalias setup_dir_data noopalias setup_dir_conf noopalias setup_dir_man noopdef 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 > 1new = new_shebang(old)return if new.to_s == old.to_selsereturn 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 # discardoutput.puts new.to_soutput.print f.read}}enddef 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])elsereturn old unless config('shebang') == 'all'Shebang.new(config('rubypath'))endenddef open_atomic_writer(path, &block)tmpfile = File.basename(path) + '.tmp'beginFile.open(tmpfile, 'wb', &block)File.rename tmpfile, File.basename(path)ensureFile.unlink tmpfile if File.exist?(tmpfile)endendclass Shebangdef Shebang.load(path)line = nilFile.open(path) {|f|line = f.gets}return nil unless /\A#!/ =~ lineparse(line)enddef Shebang.parse(line)cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')new(cmd, args)enddef initialize(cmd, args = [])@cmd = cmd@args = argsendattr_reader :cmdattr_reader :argsdef to_s"#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")endend## TASK install#def exec_installrm_f 'InstalledFiles'exec_task_traverse 'install'enddef install_dir_bin(rel)install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755enddef install_dir_lib(rel)install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644enddef install_dir_ext(rel)return unless extdir?(curr_srcdir())install_files rubyextentions('.'),"#{config('sodir')}/#{File.dirname(rel)}",0555enddef install_dir_data(rel)install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644enddef install_dir_conf(rel)# FIXME: should not remove current config files# (rename previous file to .old/.org)install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644enddef install_dir_man(rel)install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644enddef install_files(list, dest, mode)mkdir_p dest, @config.install_prefixlist.each do |fname|install fname, dest, mode, @config.install_prefixendenddef libfilesglob_reject(%w(*.y *.output), targetfiles())enddef rubyextentions(dir)ents = glob_select("*.#{@config.dllext}", targetfiles())if ents.empty?setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"endentsenddef targetfilesmapdir(existfiles() - hookfiles())enddef mapdir(ents)ents.map {|ent|if File.exist?(ent)then ent # objdirelse "#{curr_srcdir()}/#{ent}" # srcdirend}end# picked up many entries from cvs-1.11.1/src/ignore.cJUNK_FILES = %w(core RCSLOG tags TAGS .make.state.nse_depinfo #* .#* cvslog.* ,* .del-* *.olb*~ *.old *.bak *.BAK *.orig *.rej _$* *$*.org *.in .*)def existfilesglob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))enddef hookfiles%w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|%w( config setup install clean ).map {|t| sprintf(fmt, t) }}.flattenenddef glob_select(pat, ents)re = globs2re([pat])ents.select {|ent| re =~ ent }enddef glob_reject(pats, ents)re = globs2re(pats)ents.reject {|ent| re =~ ent }endGLOB2REGEX = {'.' => '\.','$' => '\$','#' => '\#','*' => '.*'}def globs2re(pats)/\A(?:#{pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')})\z/end## TASK test#TESTDIR = 'test'def exec_testunless File.directory?('test')$stderr.puts 'no test in this package' if verbose?returnend$stderr.puts 'Running tests...' if verbose?beginrequire 'test/unit'rescue LoadErrorsetup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'endrunner = Test::Unit::AutoRunner.new(true)runner.to_run << TESTDIRrunner.runend## TASK clean#def exec_cleanexec_task_traverse 'clean'rm_f @config.savefilerm_f 'InstalledFiles'endalias clean_dir_bin noopalias clean_dir_lib noopalias clean_dir_data noopalias clean_dir_conf noopalias clean_dir_man noopdef clean_dir_ext(rel)return unless extdir?(curr_srcdir())make 'clean' if File.file?('Makefile')end## TASK distclean#def exec_distcleanexec_task_traverse 'distclean'rm_f @config.savefilerm_f 'InstalledFiles'endalias distclean_dir_bin noopalias distclean_dir_lib noopdef distclean_dir_ext(rel)return unless extdir?(curr_srcdir())make 'distclean' if File.file?('Makefile')endalias distclean_dir_data noopalias distclean_dir_conf noopalias distclean_dir_man noop## Traversing#def exec_task_traverse(task)run_hook "pre-#{task}"FILETYPES.each do |type|if type == 'ext' and config('without-ext') == 'yes'$stderr.puts 'skipping ext/* by user option' if verbose?nextendtraverse task, type, "#{task}_dir_#{type}"endrun_hook "post-#{task}"enddef traverse(task, rel, mid)dive_into(rel) {run_hook "pre-#{task}"__send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')directories_of(curr_srcdir()).each do |d|traverse task, "#{rel}/#{d}", midendrun_hook "post-#{task}"}enddef dive_into(rel)return unless File.dir?("#{@srcdir}/#{rel}")dir = File.basename(rel)Dir.mkdir dir unless File.dir?(dir)prevdir = Dir.pwdDir.chdir dir$stderr.puts '---> ' + rel if verbose?@currdir = relyieldDir.chdir prevdir$stderr.puts '<--- ' + rel if verbose?@currdir = File.dirname(rel)enddef run_hook(id)path = [ "#{curr_srcdir()}/#{id}","#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }return unless pathbegininstance_eval File.read(path), path, 1rescueraise if $DEBUGsetup_rb_error "hook #{path} failed:\n" + $!.messageendendend # class Installerclass SetupError < StandardError; enddef setup_rb_error(msg)raise SetupError, msgendif $0 == __FILE__beginToplevelInstaller.invokerescue SetupErrorraise if $DEBUG$stderr.puts $!.message$stderr.puts "Try 'ruby #{$0} --help' for detailed usage."exit 1endend