ruby-changes:21855
From: drbrain <ko1@a...>
Date: Wed, 30 Nov 2011 09:57:37 +0900 (JST)
Subject: [ruby-changes:21855] drbrain:r33904 (trunk): * lib/mkmf.rb: Use MakeMakefile's rm_f to avoid conflict with Rake or
drbrain 2011-11-30 09:57:24 +0900 (Wed, 30 Nov 2011) New Revision: 33904 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=33904 Log: * lib/mkmf.rb: Use MakeMakefile's rm_f to avoid conflict with Rake or FileUtils. * test/ruby/test_module.rb: Hide MakeMakefile's inclusion in Object Modified files: trunk/ChangeLog trunk/ext/extmk.rb trunk/lib/mkmf.rb trunk/test/mkmf/base.rb trunk/test/ruby/test_module.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 33903) +++ ChangeLog (revision 33904) @@ -1,3 +1,9 @@ +Wed Nov 30 08:57:07 2011 Eric Hodel <drbrain@s...> + + * lib/mkmf.rb: Use MakeMakefile's rm_f to avoid conflict with Rake or + FileUtils. + * test/ruby/test_module.rb: Hide MakeMakefile's inclusion in Object + Wed Nov 30 09:12:43 2011 NAKAMURA Usaku <usa@r...> * lib/rdoc/encoding.rb (RDoc::Encoding.read_file): fixup newline chars Index: lib/mkmf.rb =================================================================== --- lib/mkmf.rb (revision 33903) +++ lib/mkmf.rb (revision 33904) @@ -6,176 +6,7 @@ require 'fileutils' require 'shellwords' -CONFIG = RbConfig::MAKEFILE_CONFIG -ORIG_LIBPATH = ENV['LIB'] - -C_EXT = %w[c m] -CXX_EXT = %w[cc mm cxx cpp] -if File::FNM_SYSCASE.zero? - CXX_EXT.concat(%w[C]) -end -SRC_EXT = C_EXT + CXX_EXT -$static = nil -$config_h = '$(arch_hdrdir)/ruby/config.h' -$default_static = $static - -unless defined? $configure_args - $configure_args = {} - args = CONFIG["configure_args"] - if ENV["CONFIGURE_ARGS"] - args << " " << ENV["CONFIGURE_ARGS"] - end - for arg in Shellwords::shellwords(args) - arg, val = arg.split('=', 2) - next unless arg - arg.tr!('_', '-') - if arg.sub!(/^(?!--)/, '--') - val or next - arg.downcase! - end - next if /^--(?:top|topsrc|src|cur)dir$/ =~ arg - $configure_args[arg] = val || true - end - for arg in ARGV - arg, val = arg.split('=', 2) - next unless arg - arg.tr!('_', '-') - if arg.sub!(/^(?!--)/, '--') - val or next - arg.downcase! - end - $configure_args[arg] = val || true - end -end - -$libdir = CONFIG["libdir"] -$rubylibdir = CONFIG["rubylibdir"] -$archdir = CONFIG["archdir"] -$sitedir = CONFIG["sitedir"] -$sitelibdir = CONFIG["sitelibdir"] -$sitearchdir = CONFIG["sitearchdir"] -$vendordir = CONFIG["vendordir"] -$vendorlibdir = CONFIG["vendorlibdir"] -$vendorarchdir = CONFIG["vendorarchdir"] - -$mswin = /mswin/ =~ RUBY_PLATFORM -$bccwin = /bccwin/ =~ RUBY_PLATFORM -$mingw = /mingw/ =~ RUBY_PLATFORM -$cygwin = /cygwin/ =~ RUBY_PLATFORM -$netbsd = /netbsd/ =~ RUBY_PLATFORM -$os2 = /os2/ =~ RUBY_PLATFORM -$beos = /beos/ =~ RUBY_PLATFORM -$haiku = /haiku/ =~ RUBY_PLATFORM -$solaris = /solaris/ =~ RUBY_PLATFORM -$universal = /universal/ =~ RUBY_PLATFORM -$dest_prefix_pattern = (File::PATH_SEPARATOR == ';' ? /\A([[:alpha:]]:)?/ : /\A/) - # :stopdoc: - -def config_string(key, config = CONFIG) - s = config[key] and !s.empty? and block_given? ? yield(s) : s -end - -def dir_re(dir) - Regexp.new('\$(?:\('+dir+'\)|\{'+dir+'\})(?:\$(?:\(target_prefix\)|\{target_prefix\}))?') -end - -def relative_from(path, base) - dir = File.join(path, "") - if File.expand_path(dir) == File.expand_path(dir, base) - path - else - File.join(base, path) - end -end - -INSTALL_DIRS = [ - [dir_re('commondir'), "$(RUBYCOMMONDIR)"], - [dir_re('sitedir'), "$(RUBYCOMMONDIR)"], - [dir_re('vendordir'), "$(RUBYCOMMONDIR)"], - [dir_re('rubylibdir'), "$(RUBYLIBDIR)"], - [dir_re('archdir'), "$(RUBYARCHDIR)"], - [dir_re('sitelibdir'), "$(RUBYLIBDIR)"], - [dir_re('vendorlibdir'), "$(RUBYLIBDIR)"], - [dir_re('sitearchdir'), "$(RUBYARCHDIR)"], - [dir_re('vendorarchdir'), "$(RUBYARCHDIR)"], - [dir_re('rubyhdrdir'), "$(RUBYHDRDIR)"], - [dir_re('sitehdrdir'), "$(SITEHDRDIR)"], - [dir_re('vendorhdrdir'), "$(VENDORHDRDIR)"], - [dir_re('bindir'), "$(BINDIR)"], -] - -def install_dirs(target_prefix = nil) - if $extout - dirs = [ - ['BINDIR', '$(extout)/bin'], - ['RUBYCOMMONDIR', '$(extout)/common'], - ['RUBYLIBDIR', '$(RUBYCOMMONDIR)$(target_prefix)'], - ['RUBYARCHDIR', '$(extout)/$(arch)$(target_prefix)'], - ['HDRDIR', '$(extout)/include/ruby$(target_prefix)'], - ['ARCHHDRDIR', '$(extout)/include/$(arch)/ruby$(target_prefix)'], - ['extout', "#$extout"], - ['extout_prefix', "#$extout_prefix"], - ] - elsif $extmk - dirs = [ - ['BINDIR', '$(bindir)'], - ['RUBYCOMMONDIR', '$(rubylibdir)'], - ['RUBYLIBDIR', '$(rubylibdir)$(target_prefix)'], - ['RUBYARCHDIR', '$(archdir)$(target_prefix)'], - ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], - ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], - ] - elsif $configure_args.has_key?('--vendor') - dirs = [ - ['BINDIR', '$(bindir)'], - ['RUBYCOMMONDIR', '$(vendordir)$(target_prefix)'], - ['RUBYLIBDIR', '$(vendorlibdir)$(target_prefix)'], - ['RUBYARCHDIR', '$(vendorarchdir)$(target_prefix)'], - ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], - ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], - ] - else - dirs = [ - ['BINDIR', '$(bindir)'], - ['RUBYCOMMONDIR', '$(sitedir)$(target_prefix)'], - ['RUBYLIBDIR', '$(sitelibdir)$(target_prefix)'], - ['RUBYARCHDIR', '$(sitearchdir)$(target_prefix)'], - ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], - ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], - ] - end - dirs << ['target_prefix', (target_prefix ? "/#{target_prefix}" : "")] - dirs -end - -def map_dir(dir, map = nil) - map ||= INSTALL_DIRS - map.inject(dir) {|d, (orig, new)| d.gsub(orig, new)} -end - -topdir = File.dirname(File.dirname(__FILE__)) -path = File.expand_path($0) -$extmk = path[0, topdir.size+1] == topdir+"/" -$extmk &&= %r"\A(?:ext|enc|tool|test(?:/.+)?)\z" =~ File.dirname(path[topdir.size+1..-1]) -$extmk &&= true -if not $extmk and File.exist?(($hdrdir = RbConfig::CONFIG["rubyhdrdir"]) + "/ruby/ruby.h") - $topdir = $hdrdir - $top_srcdir = $hdrdir - $arch_hdrdir = $hdrdir + "/$(arch)" -elsif File.exist?(($hdrdir = ($top_srcdir ||= topdir) + "/include") + "/ruby.h") - $topdir ||= RbConfig::CONFIG["topdir"] - $arch_hdrdir = "$(extout)/include/$(arch)" -else - abort "mkmf.rb can't find header files for ruby at #{$hdrdir}/ruby.h" -end - -OUTFLAG = CONFIG['OUTFLAG'] -COUTFLAG = CONFIG['COUTFLAG'] -CPPOUTFILE = CONFIG['CPPOUTFILE'] - -CONFTEST_C = "conftest.c".freeze - class String # Wraps a string in escaped quotes if it contains whitespace. def quote @@ -195,1086 +26,1275 @@ self[/\A[^()]+/] end end + class Array # Wraps all strings in escaped quotes if they contain whitespace. def quote map {|s| s.quote} end end +# :startdoc: -def rm_f(*files) - opt = (Hash === files.last ? [files.pop] : []) - FileUtils.rm_f(Dir[*files.flatten], *opt) -end +## +# mkmf.rb is used by ruby C extensions to generate a Makefile which will +# correctly compile and link the C extension to ruby and a third-party +# library. +module MakeMakefile -def rm_rf(*files) - opt = (Hash === files.last ? [files.pop] : []) - FileUtils.rm_rf(Dir[*files.flatten], *opt) -end + CONFIG = RbConfig::MAKEFILE_CONFIG + ORIG_LIBPATH = ENV['LIB'] -# Returns time stamp of the +target+ file if it exists and is newer -# than or equal to all of +times+. -def modified?(target, times) - (t = File.mtime(target)) rescue return nil - Array === times or times = [times] - t if times.all? {|n| n <= t} -end + C_EXT = %w[c m] + CXX_EXT = %w[cc mm cxx cpp] + if File::FNM_SYSCASE.zero? + CXX_EXT.concat(%w[C]) + end + SRC_EXT = C_EXT + CXX_EXT + $static = nil + $config_h = '$(arch_hdrdir)/ruby/config.h' + $default_static = $static -def merge_libs(*libs) - libs.inject([]) do |x, y| - xy = x & y - xn = yn = 0 - y = y.inject([]) {|ary, e| ary.last == e ? ary : ary << e} - y.each_with_index do |v, yi| - if xy.include?(v) - xi = [x.index(v), xn].max() - x[xi, 1] = y[yn..yi] - xn, yn = xi + (yi - yn + 1), yi + 1 + unless defined? $configure_args + $configure_args = {} + args = CONFIG["configure_args"] + if ENV["CONFIGURE_ARGS"] + args << " " << ENV["CONFIGURE_ARGS"] + end + for arg in Shellwords::shellwords(args) + arg, val = arg.split('=', 2) + next unless arg + arg.tr!('_', '-') + if arg.sub!(/^(?!--)/, '--') + val or next + arg.downcase! end + next if /^--(?:top|topsrc|src|cur)dir$/ =~ arg + $configure_args[arg] = val || true end - x.concat(y[yn..-1] || []) + for arg in ARGV + arg, val = arg.split('=', 2) + next unless arg + arg.tr!('_', '-') + if arg.sub!(/^(?!--)/, '--') + val or next + arg.downcase! + end + $configure_args[arg] = val || true + end end -end -# This is a custom logging module. It generates an mkmf.log file when you -# run your extconf.rb script. This can be useful for debugging unexpected -# failures. -# -# This module and its associated methods are meant for internal use only. -# -module Logging - @log = nil - @logfile = 'mkmf.log' - @orgerr = $stderr.dup - @orgout = $stdout.dup - @postpone = 0 - @quiet = $extmk + $libdir = CONFIG["libdir"] + $rubylibdir = CONFIG["rubylibdir"] + $archdir = CONFIG["archdir"] + $sitedir = CONFIG["sitedir"] + $sitelibdir = CONFIG["sitelibdir"] + $sitearchdir = CONFIG["sitearchdir"] + $vendordir = CONFIG["vendordir"] + $vendorlibdir = CONFIG["vendorlibdir"] + $vendorarchdir = CONFIG["vendorarchdir"] - def self::log_open - @log ||= File::open(@logfile, 'wb') - @log.sync = true - end + $mswin = /mswin/ =~ RUBY_PLATFORM + $bccwin = /bccwin/ =~ RUBY_PLATFORM + $mingw = /mingw/ =~ RUBY_PLATFORM + $cygwin = /cygwin/ =~ RUBY_PLATFORM + $netbsd = /netbsd/ =~ RUBY_PLATFORM + $os2 = /os2/ =~ RUBY_PLATFORM + $beos = /beos/ =~ RUBY_PLATFORM + $haiku = /haiku/ =~ RUBY_PLATFORM + $solaris = /solaris/ =~ RUBY_PLATFORM + $universal = /universal/ =~ RUBY_PLATFORM + $dest_prefix_pattern = (File::PATH_SEPARATOR == ';' ? /\A([[:alpha:]]:)?/ : /\A/) - def self::open - log_open - $stderr.reopen(@log) - $stdout.reopen(@log) - yield - ensure - $stderr.reopen(@orgerr) - $stdout.reopen(@orgout) + # :stopdoc: + + def config_string(key, config = CONFIG) + s = config[key] and !s.empty? and block_given? ? yield(s) : s end + module_function :config_string - def self::message(*s) - log_open - @log.printf(*s) + def dir_re(dir) + Regexp.new('\$(?:\('+dir+'\)|\{'+dir+'\})(?:\$(?:\(target_prefix\)|\{target_prefix\}))?') end + module_function :dir_re - def self::logfile file - @logfile = file - log_close + def relative_from(path, base) + dir = File.join(path, "") + if File.expand_path(dir) == File.expand_path(dir, base) + path + else + File.join(base, path) + end end - def self::log_close - if @log and not @log.closed? - @log.flush - @log.close - @log = nil + INSTALL_DIRS = [ + [dir_re('commondir'), "$(RUBYCOMMONDIR)"], + [dir_re('sitedir'), "$(RUBYCOMMONDIR)"], + [dir_re('vendordir'), "$(RUBYCOMMONDIR)"], + [dir_re('rubylibdir'), "$(RUBYLIBDIR)"], + [dir_re('archdir'), "$(RUBYARCHDIR)"], + [dir_re('sitelibdir'), "$(RUBYLIBDIR)"], + [dir_re('vendorlibdir'), "$(RUBYLIBDIR)"], + [dir_re('sitearchdir'), "$(RUBYARCHDIR)"], + [dir_re('vendorarchdir'), "$(RUBYARCHDIR)"], + [dir_re('rubyhdrdir'), "$(RUBYHDRDIR)"], + [dir_re('sitehdrdir'), "$(SITEHDRDIR)"], + [dir_re('vendorhdrdir'), "$(VENDORHDRDIR)"], + [dir_re('bindir'), "$(BINDIR)"], + ] + + def install_dirs(target_prefix = nil) + if $extout + dirs = [ + ['BINDIR', '$(extout)/bin'], + ['RUBYCOMMONDIR', '$(extout)/common'], + ['RUBYLIBDIR', '$(RUBYCOMMONDIR)$(target_prefix)'], + ['RUBYARCHDIR', '$(extout)/$(arch)$(target_prefix)'], + ['HDRDIR', '$(extout)/include/ruby$(target_prefix)'], + ['ARCHHDRDIR', '$(extout)/include/$(arch)/ruby$(target_prefix)'], + ['extout', "#$extout"], + ['extout_prefix', "#$extout_prefix"], + ] + elsif $extmk + dirs = [ + ['BINDIR', '$(bindir)'], + ['RUBYCOMMONDIR', '$(rubylibdir)'], + ['RUBYLIBDIR', '$(rubylibdir)$(target_prefix)'], + ['RUBYARCHDIR', '$(archdir)$(target_prefix)'], + ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], + ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], + ] + elsif $configure_args.has_key?('--vendor') + dirs = [ + ['BINDIR', '$(bindir)'], + ['RUBYCOMMONDIR', '$(vendordir)$(target_prefix)'], + ['RUBYLIBDIR', '$(vendorlibdir)$(target_prefix)'], + ['RUBYARCHDIR', '$(vendorarchdir)$(target_prefix)'], + ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], + ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], + ] + else + dirs = [ + ['BINDIR', '$(bindir)'], + ['RUBYCOMMONDIR', '$(sitedir)$(target_prefix)'], + ['RUBYLIBDIR', '$(sitelibdir)$(target_prefix)'], + ['RUBYARCHDIR', '$(sitearchdir)$(target_prefix)'], + ['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'], + ['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'], + ] end + dirs << ['target_prefix', (target_prefix ? "/#{target_prefix}" : "")] + dirs end - def self::postpone - tmplog = "mkmftmp#{@postpone += 1}.log" - open do - log, *save = @log, @logfile, @orgout, @orgerr - @log, @logfile, @orgout, @orgerr = nil, tmplog, log, log - begin - log.print(open {yield @log}) - ensure - @log.close if @log and not @log.closed? - File::open(tmplog) {|t| FileUtils.copy_stream(t, log)} if File.exist?(tmplog) - @log, @logfile, @orgout, @orgerr = log, *save - @postpone -= 1 - rm_f tmplog + def map_dir(dir, map = nil) + map ||= INSTALL_DIRS + map.inject(dir) {|d, (orig, new)| d.gsub(orig, new)} + end + + topdir = File.dirname(File.dirname(__FILE__)) + path = File.expand_path($0) + $extmk = path[0, topdir.size+1] == topdir+"/" + $extmk &&= %r"\A(?:ext|enc|tool|test(?:/.+)?)\z" =~ File.dirname(path[topdir.size+1..-1]) + $extmk &&= true + if not $extmk and File.exist?(($hdrdir = RbConfig::CONFIG["rubyhdrdir"]) + "/ruby/ruby.h") + $topdir = $hdrdir + $top_srcdir = $hdrdir + $arch_hdrdir = $hdrdir + "/$(arch)" + elsif File.exist?(($hdrdir = ($top_srcdir ||= topdir) + "/include") + "/ruby.h") + $topdir ||= RbConfig::CONFIG["topdir"] + $arch_hdrdir = "$(extout)/include/$(arch)" + else + abort "mkmf.rb can't find header files for ruby at #{$hdrdir}/ruby.h" + end + + OUTFLAG = CONFIG['OUTFLAG'] + COUTFLAG = CONFIG['COUTFLAG'] + CPPOUTFILE = CONFIG['CPPOUTFILE'] + + CONFTEST_C = "conftest.c".freeze + + def rm_f(*files) + opt = (Hash === files.last ? [files.pop] : []) + FileUtils.rm_f(Dir[*files.flatten], *opt) + end + module_function :rm_f + + def rm_rf(*files) + opt = (Hash === files.last ? [files.pop] : []) + FileUtils.rm_rf(Dir[*files.flatten], *opt) + end + module_function :rm_rf + + # Returns time stamp of the +target+ file if it exists and is newer than or + # equal to all of +times+. + def modified?(target, times) + (t = File.mtime(target)) rescue return nil + Array === times or times = [times] + t if times.all? {|n| n <= t} + end + + def merge_libs(*libs) + libs.inject([]) do |x, y| + xy = x & y + xn = yn = 0 + y = y.inject([]) {|ary, e| ary.last == e ? ary : ary << e} + y.each_with_index do |v, yi| + if xy.include?(v) + xi = [x.index(v), xn].max() + x[xi, 1] = y[yn..yi] + xn, yn = xi + (yi - yn + 1), yi + 1 + end end + x.concat(y[yn..-1] || []) end end - class << self - attr_accessor :quiet + # This is a custom logging module. It generates an mkmf.log file when you + # run your extconf.rb script. This can be useful for debugging unexpected + # failures. + # + # This module and its associated methods are meant for internal use only. + # + module Logging + @log = nil + @logfile = 'mkmf.log' + @orgerr = $stderr.dup + @orgout = $stdout.dup + @postpone = 0 + @quiet = $extmk + + def self::log_open + @log ||= File::open(@logfile, 'wb') + @log.sync = true + end + + def self::open + log_open + $stderr.reopen(@log) + $stdout.reopen(@log) + yield + ensure + $stderr.reopen(@orgerr) + $stdout.reopen(@orgout) + end + + def self::message(*s) + log_open + @log.printf(*s) + end + + def self::logfile file + @logfile = file + log_close + end + + def self::log_close + if @log and not @log.closed? + @log.flush + @log.close + @log = nil + end + end + + def self::postpone + tmplog = "mkmftmp#{@postpone += 1}.log" + open do + log, *save = @log, @logfile, @orgout, @orgerr + @log, @logfile, @orgout, @orgerr = nil, tmplog, log, log + begin + log.print(open {yield @log}) + ensure + @log.close if @log and not @log.closed? + File::open(tmplog) {|t| FileUtils.copy_stream(t, log)} if File.exist?(tmplog) + @log, @logfile, @orgout, @orgerr = log, *save + @postpone -= 1 + MakeMakefile.rm_f tmplog + end + end + end + + class << self + attr_accessor :quiet + end end -end -def xsystem command, opts = nil - varpat = /\$\((\w+)\)|\$\{(\w+)\}/ - if varpat =~ command - vars = Hash.new {|h, k| h[k] = ''; ENV[k]} - command = command.dup - nil while command.gsub!(varpat) {vars[$1||$2]} - end - Logging::open do - puts command.quote - if opts and opts[:werror] - result = nil - Logging.postpone do |log| - result = (system(command) and File.zero?(log.path)) - "" + def xsystem command, opts = nil + varpat = /\$\((\w+)\)|\$\{(\w+)\}/ + if varpat =~ command + vars = Hash.new {|h, k| h[k] = ''; ENV[k]} + command = command.dup + nil while command.gsub!(varpat) {vars[$1||$2]} + end + Logging::open do + puts command.quote + if opts and opts[:werror] + result = nil + Logging.postpone do |log| + result = (system(command) and File.zero?(log.path)) + "" + end + result + else + system(command) end - result - else - system(command) end end -end -def xpopen command, *mode, &block - Logging::open do - case mode[0] - when nil, /^r/ - puts "#{command} |" - else - puts "| #{command}" + def xpopen command, *mode, &block + Logging::open do + case mode[0] + when nil, /^r/ + puts "#{command} |" + else + puts "| #{command}" + end + IO.popen(command, *mode, &block) end - IO.popen(command, *mode, &block) end -end -def log_src(src) - src = src.split(/^/) - fmt = "%#{src.size.to_s.size}d: %s" - Logging::message <<"EOM" + def log_src(src) + src = src.split(/^/) + fmt = "%#{src.size.to_s.size}d: %s" + Logging::message <<"EOM" checked program was: /* begin */ EOM - src.each_with_index {|line, no| Logging::message fmt, no+1, line} - Logging::message <<"EOM" + src.each_with_index {|line, no| Logging::message fmt, no+1, line} + Logging::message <<"EOM" /* end */ EOM -end + end -def create_tmpsrc(src) - src = "#{COMMON_HEADERS}\n#{src}" - src = yield(src) if block_given? - src.gsub!(/[ \t]+$/, '') - src.gsub!(/\A\n+|^\n+$/, '') - src.sub!(/[^\n]\z/, "\\&\n") - count = 0 - begin - open(CONFTEST_C, "wb") do |cfile| - cfile.print src + def create_tmpsrc(src) + src = "#{COMMON_HEADERS}\n#{src}" + src = yield(src) if block_given? + src.gsub!(/[ \t]+$/, '') + src.gsub!(/\A\n+|^\n+$/, '') + src.sub!(/[^\n]\z/, "\\&\n") + count = 0 + begin + open(CONFTEST_C, "wb") do |cfile| + cfile.print src + end + (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/