#
# setup.rb
#
# Copyright (c) 2000-2004 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.6
module Enumerable
alias map collect
end
end
unless File.respond_to?(:read) # Ruby 1.6
def File.read(fname)
open(fname) {|f|
return f.read
}
end
end
def 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 + '/')
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
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
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
class PathItem < ConfigItem
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
class ProgramItem < ConfigItem
def config_type
'program'
end
end
class SelectItem < ConfigItem
def initialize(name, template, default, desc)
super
@ok = template.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
class PackageSelectionItem < ConfigItem
def initialize(name, template, default, help_default, desc)
super name, template, default, desc
@help_default = help_default
end
attr_reader :help_default
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 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)
end
def key?(name)
@table.key?(name)
end
def lookup(name)
@table[name] or raise ArgumentError, "no such config item: #{name}"
end
def add(item)
@items.push item
@table[item.name] = item
end
def remove(name)
item = lookup(name)
@items.delete_if {|i| i.name == name }
@table.delete_if {|name, i| i.name == name }
item
end
def new
dup()
end
def savefile
'.config'
end
def load
begin
t = dup()
File.foreach(savefile()) do |line|
k, v = *line.split(/=/, 2)
t[k] = v.strip
end
t
rescue Errno::ENOENT
setup_rb_error $!.message + "#{File.basename($0)} config first"
end
end
def 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
end
}
end
if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
else
makeprog = 'make'
end
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',
'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'
}
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)
end
def config_names
ConfigTable.map {|i| i.name }
end
def config?(name)
ConfigTable.key?(name)
end
def bool_config?(name)
ConfigTable.lookup(name).config_type == 'bool'
end
def path_config?(name)
ConfigTable.lookup(name).config_type == 'path'
end
def value_config?(name)
case ConfigTable.lookup(name).config_type
when 'bool', 'path'
true
else
false
end
end
def add_path_config(name, default, desc)
ConfigTable.add PathItem.new(name, 'path', default, desc)
end
def set_config_default(name, default)
ConfigTable.lookup(name).default = default
end
def remove_config(name)
ConfigTable.remove(name)
end
end
#
# File Operations
#
module FileOperations
def 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 case
dirs = File.expand_path(dirname).split(%r<(?=/)>)
if /\A[a-z]:\z/i =~ dirs[0]
disk = dirs.shift
dirs[0] = disk + dirs[0]
end
dirs.each_index do |idx|
path = dirs[0..idx].join('')
Dir.mkdir path unless File.dir?(path)
end
end
def rm_f(fname)
$stderr.puts "rm -f #{fname}" if verbose?
return if no_harm?
if File.exist?(fname) or File.symlink?(fname)
File.chmod 0777, fname
File.unlink fname
end
end
def rm_rf(dn)
$stderr.puts "rm -rf #{dn}" if verbose?
return if no_harm?
Dir.chdir dn
Dir.foreach('.') do |fn|
next if fn == '.'
next if fn == '..'
if File.dir?(fn)
verbose_off {
rm_rf fn
}
else
verbose_off {
rm_f fn
}
end
end
Dir.chdir '..'
Dir.rmdir dn
end
def move_file(src, dest)
File.unlink dest if File.exist?(dest)
begin
File.rename src, dest
rescue
File.open(dest, 'wb') {|f| f.write File.binread(src) }
File.chmod File.stat(src).mode, dest
File.unlink src
end
end
def install(from, dest, mode, prefix = nil)
$stderr.puts "install #{from} #{dest}" if verbose?
return if no_harm?
realdest = prefix ? prefix + File.expand_path(dest) : dest
realdest = 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, realdest
File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
if prefix
f.puts realdest.sub(prefix, '')
else
f.puts realdest
end
}
end
end
def invoke
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)
__send__ "parsearg_#{task}"
init_installers
__send__ "exec_#{task}"
end
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
end
def init_installers
@installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
end
while arg = ARGV.shift
case arg
when /\A\w+\z/
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'
print_usage $stdout
exit 0
when '-v', '--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 parsearg_no_options
unless ARGV.empty?
setup_rb_error "#{task}: unknown options: #{ARGV.join ' '}"
end
end
alias parsearg_show parsearg_no_options
alias parsearg_setup parsearg_no_options
alias parsearg_clean parsearg_no_options
alias parsearg_distclean parsearg_no_options
while i = ARGV.shift
if /\A--?\z/ =~ i
@options['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
end
end
def parsearg_install
@options['no-harm'] = false
@options['install-prefix'] = ''
while a = ARGV.shift
case a
when /\A--no-harm\z/
@options['no-harm'] = true
when /\A--prefix=(.*)\z/
path = $1
path = File.expand_path(path) unless path[0,1] == '/'
@options['install-prefix'] = path
else
setup_rb_error "install: unknown option #{a}"
end
end
end
fmt = " %-24s %s\n"
out.puts
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, ' --copyright', 'print copyright and quit'
out.puts
out.puts 'Tasks:'
TASKS.each do |name, desc|
out.printf fmt, name, desc
end
fmt = " %-24s %s [%s]\n"
out.puts
out.puts 'Options for CONFIG or ALL:'
ConfigTable.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.puts
end
#
# Task Handlers
#
def exec_config
@installer.exec_config
@config.save # must be final
end
def exec_setup
@installer.exec_setup
end
def exec_install
@installer.exec_install
end
def exec_show
ConfigTable.each do |i|
printf "%-20s %s\n", i.name, i.value
end
end
def exec_clean
@installer.exec_clean
end
def exec_distclean
@installer.exec_distclean
end
end
class ToplevelInstallerMulti < ToplevelInstaller
include HookUtils
include HookScriptAPI
include FileOperations
def initialize(ardir)
super
@packages = all_dirs_in("#{@ardir}/packages")
raise 'no package exists' if @packages.empty?
end
def run_metaconfigs
eval_file_ifexist "#{@ardir}/metaconfig"
@packages.each do |name|
eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
end
end
def init_installers
@installers = {}
@packages.each do |pack|
@installers[pack] = Installer.new(@config, @options,
"#{@ardir}/packages/#{pack}",
"packages/#{pack}")
end
with = 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)
}
end
def extract_selection(list)
a = list.split(/,/)
a.each do |name|
setup_rb_error "no such package: #{name}" unless @installers.key?(name)
end
a
end
def print_usage(f)
super
f.puts 'Inluded packages:'
f.puts ' ' + @packages.sort.join(' ')
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
#
def exec_config
run_hook 'pre-config'
each_selected_installers {|inst| inst.exec_config }
run_hook 'post-config'
@config.save # must be final
end
def mapdir(filelist)
filelist.map {|fname|
if File.exist?(fname) # objdir
fname
else # srcdir
File.join(curr_srcdir(), fname)
end
}
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
}
end
#
# TASK clean
#
def exec_clean
exec_task_traverse 'clean'
rm_f ConfigTable.savefile
rm_f 'InstalledFiles'
end
def clean_dir_bin(rel)
end
def clean_dir_lib(rel)
end
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 'InstalledFiles'
end
def distclean_dir_bin(rel)
end
def distclean_dir_lib(rel)
end
def distclean_dir_ext(rel)
return unless extdir?(curr_srcdir())
make 'distclean' if File.file?('Makefile')
end
#
# lib
#
def exec_task_traverse(task)
run_hook "pre-#{task}"
FILETYPES.each do |type|
if config('without-ext') == 'yes' and type == 'ext'
$stderr.puts 'skipping ext/* by user option' if verbose?
next
end
traverse task, type, "#{task}_dir_#{type}"
end
run_hook "post-#{task}"
end
def traverse(task, rel, mid)
dive_into(rel) {
run_hook "pre-#{task}"
__send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
all_dirs_in(curr_srcdir()).each do |d|
traverse task, "#{rel}/#{d}", mid
end
run_hook "post-#{task}"
}
end
dir = File.basename(rel)
Dir.mkdir dir unless File.dir?(dir)
prevdir = Dir.pwd
Dir.chdir dir
$stderr.puts '---> ' + rel if verbose?
@currdir = rel
yield
Dir.chdir prevdir
$stderr.puts '<--- ' + rel if verbose?
@currdir = File.dirname(rel)
end
end
if $0 == __FILE__
begin
if multipackage_install?
ToplevelInstallerMulti.invoke
else
ToplevelInstaller.invoke
end
rescue SetupError
raise if $DEBUG
$stderr.puts $!.message
$stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
exit 1
end
end