4 # Copyright (c) 2000-2004 Minero Aoki
6 # This program is free software.
7 # You can distribute/modify this program under the terms of
8 # the GNU Lesser General Public License version 2.1.
12 # For backward compatibility
15 unless Enumerable.method_defined?(:map)
21 unless Enumerable.method_defined?(:detect)
27 unless Enumerable.method_defined?(:select)
33 unless Enumerable.method_defined?(:reject)
38 result.push i unless yield(i)
45 unless Enumerable.method_defined?(:inject)
49 result = yield(result, i)
56 unless Enumerable.method_defined?(:any?)
60 return true if yield(i)
67 unless File.respond_to?(:read)
76 # Application independent utilities
79 def File.binread(fname)
80 open(fname, 'rb') {|f|
85 # for corrupted windows stat(2)
87 File.directory?((path[-1,1] == '/') ? path : path + '/')
94 if arg = ARGV.detect{|arg| /\A--rbconfig=/ =~ arg }
96 require arg.split(/=/, 2)[1]
102 def multipackage_install?
103 FileTest.directory?(File.dirname($0) + '/packages')
111 rubypath = c['bindir'] + '/' + c['ruby_install_name']
113 major = c['MAJOR'].to_i
114 minor = c['MINOR'].to_i
115 teeny = c['TEENY'].to_i
116 version = "#{major}.#{minor}"
118 # ruby ver. >= 1.4.4?
119 newpath_p = ((major >= 2) or
122 ((minor == 4) and (teeny >= 4)))))
124 subprefix = lambda {|path|
125 path.sub(/\A#{Regexp.quote(c['prefix'])}/o, '$prefix')
130 stdruby = subprefix.call(c['rubylibdir'])
131 siteruby = subprefix.call(c['sitedir'])
132 versite = subprefix.call(c['sitelibdir'])
133 sodir = subprefix.call(c['sitearchdir'])
135 # 1.4.4 <= V <= 1.6.3
136 stdruby = "$prefix/lib/ruby/#{version}"
137 siteruby = subprefix.call(c['sitedir'])
138 versite = siteruby + '/' + version
139 sodir = "$site-ruby/#{c['arch']}"
142 stdruby = "$prefix/lib/ruby/#{version}"
143 siteruby = "$prefix/lib/ruby/#{version}/site_ruby"
145 sodir = "$site-ruby/#{c['arch']}"
148 if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
149 makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
154 common_descripters = [
155 [ 'prefix', [ c['prefix'],
157 'path prefix of target environment' ] ],
158 [ 'std-ruby', [ stdruby,
160 'the directory for standard ruby libraries' ] ],
161 [ 'site-ruby-common', [ siteruby,
163 'the directory for version-independent non-standard ruby libraries' ] ],
164 [ 'site-ruby', [ versite,
166 'the directory for non-standard ruby libraries' ] ],
167 [ 'bin-dir', [ '$prefix/bin',
169 'the directory for commands' ] ],
170 [ 'rb-dir', [ '$site-ruby',
172 'the directory for ruby scripts' ] ],
175 'the directory for ruby extentions' ] ],
176 [ 'data-dir', [ '$prefix/share',
178 'the directory for shared data' ] ],
179 [ 'ruby-path', [ rubypath,
181 'path to set to #! line' ] ],
182 [ 'ruby-prog', [ rubypath,
184 'the ruby program using for installation' ] ],
185 [ 'make-prog', [ makeprog,
187 'the make program to compile ruby extentions' ] ],
188 [ 'without-ext', [ 'no',
190 'does not compile/install ruby extentions' ] ]
192 multipackage_descripters = [
195 'package names that you want to install',
199 'package names that you do not want to install',
202 if multipackage_install?
203 DESCRIPTER = common_descripters + multipackage_descripters
205 DESCRIPTER = common_descripters
208 SAVE_FILE = 'config.save'
210 def ConfigTable.each_name(&block)
215 DESCRIPTER.map {|name, *dummy| name }
218 def ConfigTable.each_definition(&block)
219 DESCRIPTER.each(&block)
222 def ConfigTable.get_entry(name)
223 name, ent = DESCRIPTER.assoc(name)
227 def ConfigTable.get_entry!(name)
228 get_entry(name) or raise ArgumentError, "no such config: #{name}"
231 def ConfigTable.add_entry(name, vals)
232 ConfigTable::DESCRIPTER.push [name,vals]
235 def ConfigTable.remove_entry(name)
236 get_entry(name) or raise ArgumentError, "no such config: #{name}"
237 DESCRIPTER.delete_if {|n, arr| n == name }
240 def ConfigTable.config_key?(name)
241 get_entry(name) ? true : false
244 def ConfigTable.bool_config?(name)
245 ent = get_entry(name) or return false
249 def ConfigTable.value_config?(name)
250 ent = get_entry(name) or return false
254 def ConfigTable.path_config?(name)
255 ent = get_entry(name) or return false
266 c.initialize_from_table
272 c.initialize_from_file
276 def initialize_from_table
278 DESCRIPTER.each do |k, (default, vname, desc, default2)|
283 def initialize_from_file
284 raise InstallError, "#{File.basename $0} config first"\
285 unless File.file?(SAVE_FILE)
287 File.foreach(SAVE_FILE) do |line|
288 k, v = line.split(/=/, 2)
294 File.open(SAVE_FILE, 'w') {|f|
295 @table.each do |k, v|
296 f.printf "%s=%s\n", k, v if v
302 raise InstallError, "unknown config option #{k}"\
303 unless ConfigTable.config_key?(k)
308 return nil unless @table[key]
309 @table[key].gsub(%r<\$([^/]+)>) { self[$1] }
312 def set_raw(key, val)
325 def eval_file_ifexist(fname)
326 instance_eval File.read(fname), fname, 1 if File.file?(fname)
334 ConfigTable.config_key?(name)
337 def bool_config?(name)
338 ConfigTable.bool_config?(name)
341 def value_config?(name)
342 ConfigTable.value_config?(name)
345 def path_config?(name)
346 ConfigTable.path_config?(name)
349 def add_config(name, argname, default, desc)
350 ConfigTable.add_entry name,[default,argname,desc]
353 def add_path_config(name, default, desc)
354 add_config name, 'path', default, desc
357 def add_bool_config(name, default, desc)
358 add_config name, 'yes/no', default ? 'yes' : 'no', desc
361 def set_config_default(name, default)
362 if bool_config?(name)
363 ConfigTable.get_entry!(name)[0] = (default ? 'yes' : 'no')
365 ConfigTable.get_entry!(name)[0] = default
369 def remove_config(name)
370 ent = ConfigTable.get_entry(name)
371 ConfigTable.remove_entry name
381 module FileOperations
383 def mkdir_p(dirname, prefix = nil)
384 dirname = prefix + dirname if prefix
385 $stderr.puts "mkdir -p #{dirname}" if verbose?
388 # does not check '/'... it's too abnormal case
389 dirs = dirname.split(%r<(?=/)>)
390 if /\A[a-z]:\z/i =~ dirs[0]
392 dirs[0] = disk + dirs[0]
394 dirs.each_index do |idx|
395 path = dirs[0..idx].join('')
396 Dir.mkdir path unless File.dir?(path)
401 $stderr.puts "rm -f #{fname}" if verbose?
404 if File.exist?(fname) or File.symlink?(fname)
405 File.chmod 0777, fname
411 $stderr.puts "rm -rf #{dn}" if verbose?
415 Dir.foreach('.') do |fn|
432 def move_file(src, dest)
433 File.unlink dest if File.exist?(dest)
435 File.rename src, dest
437 File.open(dest, 'wb') {|f| f.write File.binread(src) }
438 File.chmod File.stat(src).mode, dest
443 def install(from, dest, mode, prefix = nil)
444 $stderr.puts "install #{from} #{dest}" if verbose?
447 realdest = prefix + dest if prefix
448 realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
449 str = File.binread(from)
450 if diff?(str, realdest)
452 rm_f realdest if File.exist?(realdest)
454 File.open(realdest, 'wb') {|f|
457 File.chmod mode, realdest
459 File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
461 f.puts realdest.sub(prefix, '')
469 def diff?(new_content, path)
470 return true unless File.exist?(path)
471 new_content != File.binread(path)
475 $stderr.puts str if verbose?
476 system str or raise RuntimeError, "'system #{str}' failed"
480 command config('ruby-prog') + ' ' + str
484 command config('make-prog') + ' ' + task
488 File.exist?(dir + '/MANIFEST')
491 def all_files_in(dirname)
492 Dir.open(dirname) {|d|
493 return d.select {|ent| File.file?("#{dirname}/#{ent}") }
501 def all_dirs_in(dirname)
502 Dir.open(dirname) {|d|
503 return d.select {|n| File.dir?("#{dirname}/#{n}") } - %w(. ..) - REJECT_DIRS
513 class InstallError < StandardError; end
519 try_run_hook "#{curr_srcdir()}/#{name}" or
520 try_run_hook "#{curr_srcdir()}/#{name}.rb"
523 def try_run_hook(fname)
524 return false unless File.file?(fname)
526 instance_eval File.read(fname), fname, 1
528 raise InstallError, "hook #{fname} failed:\n" + $!.message
542 alias config get_config
544 def set_config(key, val)
549 # srcdir/objdir (works only in the package directory)
552 #abstract srcdir_root
553 #abstract objdir_root
557 "#{srcdir_root()}/#{relpath()}"
561 "#{objdir_root()}/#{relpath()}"
565 "#{curr_srcdir()}/#{path}"
569 File.exist?(srcfile(path))
572 def srcdirectory?(path)
573 File.dir?(srcfile(path))
577 File.file? srcfile(path)
580 def srcentries(path = '.')
581 Dir.open("#{curr_srcdir()}/#{path}") {|d|
582 return d.to_a - %w(. ..)
586 def srcfiles(path = '.')
587 srcentries(path).select {|fname|
588 File.file?(File.join(curr_srcdir(), path, fname))
592 def srcdirectories(path = '.')
593 srcentries(path).select {|fname|
594 File.dir?(File.join(curr_srcdir(), path, fname))
601 class ToplevelInstaller
604 Copyright = 'Copyright (c) 2000-2004 Minero Aoki'
607 [ 'config', 'saves your configurations' ],
608 [ 'show', 'shows current configuration' ],
609 [ 'setup', 'compiles ruby extentions and others' ],
610 [ 'install', 'installs files' ],
611 [ 'clean', "does `make clean' for each extention" ],
612 [ 'distclean',"does `make distclean' for each extention" ]
615 def ToplevelInstaller.invoke
621 def ToplevelInstaller.instance
622 @singleton ||= new(File.dirname($0))
626 include MetaConfigAPI
628 def initialize(ardir_root)
630 @options = { 'verbose' => true }
631 @ardir = File.expand_path(ardir_root)
635 "#<#{self.class} #{__id__()}>"
640 task = parsearg_global()
641 @config = load_config(task)
642 __send__ "parsearg_#{task}"
644 __send__ "exec_#{task}"
648 eval_file_ifexist "#{@ardir}/metaconfig"
651 def load_config(task)
655 when 'clean', 'distclean'
656 if File.exist?('config.save')
657 then ConfigTable.load
666 @installer = Installer.new(@config, @options, @ardir, File.expand_path('.'))
670 # Hook Script API bases
690 valid_task = /\A(?:#{TASKS.map {|task,desc| task }.join '|'})\z/
692 while arg = ARGV.shift
695 raise InstallError, "invalid task: #{arg}" unless valid_task =~ arg
699 @options['verbose'] = false
702 @options['verbose'] = true
708 when '-v', '--version'
709 puts "#{File.basename($0)} version #{Version}"
717 raise InstallError, "unknown global option '#{arg}'"
721 raise InstallError, <<EOS
722 No task or global option given.
723 Typical installation procedure is:
724 $ ruby #{File.basename($0)} config
725 $ ruby #{File.basename($0)} setup
726 # ruby #{File.basename($0)} install (may require root privilege)
731 def parsearg_no_options
732 raise InstallError, "#{task}: unknown options: #{ARGV.join ' '}"\
736 alias parsearg_show parsearg_no_options
737 alias parsearg_setup parsearg_no_options
738 alias parsearg_clean parsearg_no_options
739 alias parsearg_distclean parsearg_no_options
742 re = /\A--(#{ConfigTable.keys.join '|'})(?:=(.*))?\z/
743 @options['config-opt'] = []
747 @options['config-opt'] = ARGV.dup
750 m = re.match(i) or raise InstallError, "config: unknown option #{i}"
751 name, value = m.to_a[1,2]
753 if ConfigTable.bool_config?(name)
754 raise InstallError, "config: --#{name} allows only yes/no for argument"\
755 unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ value
756 value = (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no'
759 raise InstallError, "config: --#{name} requires argument"\
760 unless ConfigTable.bool_config?(name)
763 @config[name] = value
768 @options['no-harm'] = false
769 @options['install-prefix'] = ''
773 @options['no-harm'] = true
774 when /\A--prefix=(.*)\z/
776 path = File.expand_path(path) unless path[0,1] == '/'
777 @options['install-prefix'] = path
779 raise InstallError, "install: unknown option #{a}"
785 out.puts 'Typical Installation Procedure:'
786 out.puts " $ ruby #{File.basename $0} config"
787 out.puts " $ ruby #{File.basename $0} setup"
788 out.puts " # ruby #{File.basename $0} install (may require root privilege)"
790 out.puts 'Detailed Usage:'
791 out.puts " ruby #{File.basename $0} <global option>"
792 out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
796 out.puts 'Global options:'
797 out.printf fmt, '-q,--quiet', 'suppress message outputs'
798 out.printf fmt, ' --verbose', 'output messages verbosely'
799 out.printf fmt, '-h,--help', 'print this message'
800 out.printf fmt, '-v,--version', 'print version and quit'
801 out.printf fmt, ' --copyright', 'print copyright and quit'
805 TASKS.each do |name, desc|
806 out.printf " %-10s %s\n", name, desc
810 out.puts 'Options for config:'
811 ConfigTable.each_definition do |name, (default, arg, desc, default2)|
812 out.printf " %-20s %s [%s]\n",
813 '--'+ name + (ConfigTable.bool_config?(name) ? '' : '='+arg),
817 out.printf " %-20s %s [%s]\n",
818 '--rbconfig=path', 'your rbconfig.rb to load', "running ruby's"
821 out.puts 'Options for install:'
822 out.printf " %-20s %s [%s]\n",
823 '--no-harm', 'only display what to do if given', 'off'
824 out.printf " %-20s %s [%s]\n",
825 '--prefix', 'install path prefix', '$prefix'
835 @installer.exec_config
836 @config.save # must be final
840 @installer.exec_setup
844 @installer.exec_install
848 ConfigTable.each_name do |k|
849 v = @config.get_raw(k)
851 v = '(not specified)'
853 printf "%-10s %s\n", k, v
858 @installer.exec_clean
862 @installer.exec_distclean
868 class ToplevelInstallerMulti < ToplevelInstaller
871 include HookScriptAPI
872 include FileOperations
874 def initialize(ardir)
876 @packages = all_dirs_in("#{@ardir}/packages")
877 raise 'no package exists' if @packages.empty?
881 eval_file_ifexist "#{@ardir}/metaconfig"
882 @packages.each do |name|
883 eval_file_ifexist "#{@ardir}/packages/#{name}/metaconfig"
889 @packages.each do |pack|
890 @installers[pack] = Installer.new(@config, @options,
891 "#{@ardir}/packages/#{pack}",
895 with = extract_selection(config('with'))
896 without = extract_selection(config('without'))
897 @selected = @installers.keys.select {|name|
898 (with.empty? or with.include?(name)) \
899 and not without.include?(name)
903 def extract_selection(list)
906 raise InstallError, "no such package: #{name}" \
907 unless @installers.key?(name)
914 f.puts 'Inluded packages:'
915 f.puts ' ' + @packages.sort.join(' ')
920 # multi-package metaconfig API
923 attr_reader :packages
925 def declare_packages(list)
926 raise 'package list is empty' if list.empty?
928 raise "directory packages/#{name} does not exist"\
929 unless File.dir?("#{@ardir}/packages/#{name}")
939 run_hook 'pre-config'
940 each_selected_installers {|inst| inst.exec_config }
941 run_hook 'post-config'
942 @config.save # must be final
947 each_selected_installers {|inst| inst.exec_setup }
948 run_hook 'post-setup'
952 run_hook 'pre-install'
953 each_selected_installers {|inst| inst.exec_install }
954 run_hook 'post-install'
960 each_selected_installers {|inst| inst.exec_clean }
961 run_hook 'post-clean'
966 run_hook 'pre-distclean'
967 each_selected_installers {|inst| inst.exec_distclean }
968 run_hook 'post-distclean'
975 def each_selected_installers
976 Dir.mkdir 'packages' unless File.dir?('packages')
977 @selected.each do |pack|
978 $stderr.puts "Processing the package `#{pack}' ..." if @options['verbose']
979 Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
980 Dir.chdir "packages/#{pack}"
981 yield @installers[pack]
999 FILETYPES = %w( bin lib ext data )
1001 include HookScriptAPI
1003 include FileOperations
1005 def initialize(config, opt, srcroot, objroot)
1008 @srcdir = File.expand_path(srcroot)
1009 @objdir = File.expand_path(objroot)
1014 "#<#{self.class} #{File.basename(@srcdir)}>"
1018 # Hook Script API bases
1047 save, @options['verbose'] = @options['verbose'], false
1050 @options['verbose'] = save
1059 exec_task_traverse 'config'
1062 def config_dir_bin(rel)
1065 def config_dir_lib(rel)
1068 def config_dir_ext(rel)
1069 extconf if extdir?(curr_srcdir())
1073 opt = @options['config-opt'].join(' ')
1074 command "#{config('ruby-prog')} #{curr_srcdir()}/extconf.rb #{opt}"
1077 def config_dir_data(rel)
1085 exec_task_traverse 'setup'
1088 def setup_dir_bin(rel)
1089 all_files_in(curr_srcdir()).each do |fname|
1090 adjust_shebang "#{curr_srcdir()}/#{fname}"
1094 # modify: #!/usr/bin/ruby
1095 # modify: #! /usr/bin/ruby
1097 # not modify: #!/usr/bin/env ruby
1098 SHEBANG_RE = /\A\#!\s*\S*ruby\S*/
1100 def adjust_shebang(path)
1103 tmpfile = File.basename(path) + '.tmp'
1105 File.open(path, 'rb') {|r|
1106 File.open(tmpfile, 'wb') {|w|
1108 return unless SHEBANG_RE =~ first
1110 $stderr.puts "adjusting shebang: #{File.basename path}" if verbose?
1111 w.print first.sub(SHEBANG_RE, '#!' + config('ruby-path'))
1115 move_file tmpfile, File.basename(path)
1117 File.unlink tmpfile if File.exist?(tmpfile)
1121 def setup_dir_lib(rel)
1124 def setup_dir_ext(rel)
1125 make if extdir?(curr_srcdir())
1128 def setup_dir_data(rel)
1136 exec_task_traverse 'install'
1139 def install_dir_bin(rel)
1140 install_files collect_filenames_auto(), "#{config('bin-dir')}/#{rel}", 0755
1143 def install_dir_lib(rel)
1144 install_files ruby_scripts(), "#{config('rb-dir')}/#{rel}", 0644
1147 def install_dir_ext(rel)
1148 return unless extdir?(curr_srcdir())
1149 install_files ruby_extentions('.'),
1150 "#{config('so-dir')}/#{File.dirname(rel)}",
1154 def install_dir_data(rel)
1155 install_files collect_filenames_auto(), "#{config('data-dir')}/#{rel}", 0644
1158 def install_files(list, dest, mode)
1159 mkdir_p dest, @options['install-prefix']
1160 list.each do |fname|
1161 install fname, dest, mode, @options['install-prefix']
1166 collect_filenames_auto().select {|n| /\.rb\z/ =~ n }
1169 # picked up many entries from cvs-1.11.1/src/ignore.c
1170 reject_patterns = %w(
1171 core RCSLOG tags TAGS .make.state
1172 .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
1173 *~ *.old *.bak *.BAK *.orig *.rej _$* *$
1183 REJECT_PATTERNS = Regexp.new('\A(?:' +
1184 reject_patterns.map {|pat|
1185 pat.gsub(/[\.\$\#\*]/) {|ch| mapping[ch] }
1189 def collect_filenames_auto
1190 mapdir((existfiles() - hookfiles()).reject {|fname|
1191 REJECT_PATTERNS =~ fname
1196 all_files_in(curr_srcdir()) | all_files_in('.')
1200 %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
1201 %w( config setup install clean ).map {|t| sprintf(fmt, t) }
1205 def mapdir(filelist)
1206 filelist.map {|fname|
1207 if File.exist?(fname) # objdir
1210 File.join(curr_srcdir(), fname)
1215 def ruby_extentions(dir)
1216 _ruby_extentions(dir) or
1217 raise InstallError, "no ruby extention exists: 'ruby #{$0} setup' first"
1220 DLEXT = /\.#{ ::Config::CONFIG['DLEXT'] }\z/
1222 def _ruby_extentions(dir)
1224 return d.select {|fname| DLEXT =~ fname }
1233 exec_task_traverse 'clean'
1235 rm_f 'InstalledFiles'
1238 def clean_dir_bin(rel)
1241 def clean_dir_lib(rel)
1244 def clean_dir_ext(rel)
1245 return unless extdir?(curr_srcdir())
1246 make 'clean' if File.file?('Makefile')
1249 def clean_dir_data(rel)
1257 exec_task_traverse 'distclean'
1259 rm_f 'InstalledFiles'
1262 def distclean_dir_bin(rel)
1265 def distclean_dir_lib(rel)
1268 def distclean_dir_ext(rel)
1269 return unless extdir?(curr_srcdir())
1270 make 'distclean' if File.file?('Makefile')
1277 def exec_task_traverse(task)
1278 run_hook "pre-#{task}"
1279 FILETYPES.each do |type|
1280 if config('without-ext') == 'yes' and type == 'ext'
1281 $stderr.puts 'skipping ext/* by user option' if verbose?
1284 traverse task, type, "#{task}_dir_#{type}"
1286 run_hook "post-#{task}"
1289 def traverse(task, rel, mid)
1291 run_hook "pre-#{task}"
1292 __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
1293 all_dirs_in(curr_srcdir()).each do |d|
1294 traverse task, "#{rel}/#{d}", mid
1296 run_hook "post-#{task}"
1301 return unless File.dir?("#{@srcdir}/#{rel}")
1303 dir = File.basename(rel)
1304 Dir.mkdir dir unless File.dir?(dir)
1307 $stderr.puts '---> ' + rel if verbose?
1311 $stderr.puts '<--- ' + rel if verbose?
1312 @currdir = File.dirname(rel)
1320 if multipackage_install?
1321 ToplevelInstallerMulti.invoke
1323 ToplevelInstaller.invoke
1327 $stderr.puts $!.message
1328 $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."