* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / ext / extmk.rb
blob52f10defea314141424c19a4a3ceaeaec8f94257
1 #! /usr/local/bin/ruby
2 # -*- ruby -*-
4 $extension = nil
5 $extstatic = nil
6 $force_static = nil
7 $install = nil
8 $destdir = nil
9 $dryrun = false
10 $clean = nil
11 $nodynamic = nil
12 $extinit = nil
13 $extobjs = nil
14 $extflags = ""
15 $extlibs = nil
16 $extpath = nil
17 $ignore = nil
18 $message = nil
20 $progname = $0
21 alias $PROGRAM_NAME $0
22 alias $0 $progname
24 $extlist = []
25 $compiled = {}
27 srcdir = File.dirname(File.dirname(__FILE__))
28 unless defined?(CROSS_COMPILING) and CROSS_COMPILING
29   $:.replace([File.expand_path("lib", srcdir), Dir.pwd])
30 end
31 $:.unshift(srcdir)
32 require 'rbconfig'
34 $topdir = "."
35 $top_srcdir = srcdir
37 $" << "mkmf.rb"
38 load File.expand_path("lib/mkmf.rb", srcdir)
39 require 'optparse/shellwords'
41 def sysquote(x)
42   @quote ||= /human|os2|macos/ =~ (CROSS_COMPILING || RUBY_PLATFORM)
43   @quote ? x.quote : x
44 end
46 def extract_makefile(makefile, keep = true)
47   m = File.read(makefile)
48   if !(target = m[/^TARGET[ \t]*=[ \t]*(\S*)/, 1])
49     return keep
50   end
51   installrb = {}
52   m.scan(/^install-rb-default:[ \t]*(\S+)\n\1:[ \t]*(\S+)/) {installrb[$2] = $1}
53   oldrb = installrb.keys.sort
54   newrb = install_rb(nil, "").collect {|d, *f| f}.flatten.sort
55   if target_prefix = m[/^target_prefix[ \t]*=[ \t]*\/(.*)/, 1]
56     target = "#{target_prefix}/#{target}"
57   end
58   unless oldrb == newrb
59     if $extout
60       newrb.each {|f| installrb.delete(f)}
61       unless installrb.empty?
62         config = CONFIG.dup
63         install_dirs(target_prefix).each {|var, val| config[var] = val}
64         FileUtils.rm_f(installrb.values.collect {|f| RbConfig.expand(f, config)}, :verbose => true)
65       end
66     end
67     return false
68   end
69   $target = target
70   $extconf_h = m[/^RUBY_EXTCONF_H[ \t]*=[ \t]*(\S+)/, 1]
71   $static ||= m[/^EXTSTATIC[ \t]*=[ \t]*(\S+)/, 1] || false
72   /^STATIC_LIB[ \t]*=[ \t]*\S+/ =~ m or $static = nil
73   $preload = Shellwords.shellwords(m[/^preload[ \t]*=[ \t]*(.*)/, 1] || "")
74   $DLDFLAGS += " " + (m[/^dldflags[ \t]*=[ \t]*(.*)/, 1] || "")
75   if s = m[/^LIBS[ \t]*=[ \t]*(.*)/, 1]
76     s.sub!(/^#{Regexp.quote($LIBRUBYARG)} */, "")
77     s.sub!(/ *#{Regexp.quote($LIBS)}$/, "")
78     $libs = s
79   end
80   $objs = (m[/^OBJS[ \t]*=[ \t](.*)/, 1] || "").split
81   $srcs = (m[/^SRCS[ \t]*=[ \t](.*)/, 1] || "").split
82   $LOCAL_LIBS = m[/^LOCAL_LIBS[ \t]*=[ \t]*(.*)/, 1] || ""
83   $LIBPATH = Shellwords.shellwords(m[/^libpath[ \t]*=[ \t]*(.*)/, 1] || "") - %w[$(libdir) $(topdir)]
84   true
85 end
87 def extmake(target)
88   print "#{$message} #{target}\n"
89   $stdout.flush
91   FileUtils.mkpath target unless File.directory?(target)
92   begin
93     dir = Dir.pwd
94     FileUtils.mkpath target unless File.directory?(target)
95     Dir.chdir target
96     top_srcdir = $top_srcdir
97     topdir = $topdir
98     hdrdir = $hdrdir
99     prefix = "../" * (target.count("/")+1)
100     $top_srcdir = relative_from(top_srcdir, prefix)
101     $hdrdir = relative_from(hdrdir, prefix)
102     $topdir = prefix + $topdir
103     $target = target
104     $mdir = target
105     $srcdir = File.join($top_srcdir, "ext", $mdir)
106     $preload = nil
107     $objs = ""
108     $srcs = ""
109     $compiled[target] = false
110     makefile = "./Makefile"
111     ok = File.exist?(makefile)
112     unless $ignore
113       rbconfig0 = RbConfig::CONFIG
114       mkconfig0 = CONFIG
115       rbconfig = {
116         "hdrdir" => $hdrdir,
117         "srcdir" => $srcdir,
118         "topdir" => $topdir,
119       }
120       mkconfig = {
121         "hdrdir" => ($hdrdir == top_srcdir) ? top_srcdir : "$(top_srcdir)/include",
122         "srcdir" => "$(top_srcdir)/ext/#{$mdir}",
123         "topdir" => $topdir,
124       }
125       rbconfig0.each_pair {|key, val| rbconfig[key] ||= val.dup}
126       mkconfig0.each_pair {|key, val| mkconfig[key] ||= val.dup}
127       RbConfig.module_eval {
128         remove_const(:CONFIG)
129         const_set(:CONFIG, rbconfig)
130         remove_const(:MAKEFILE_CONFIG)
131         const_set(:MAKEFILE_CONFIG, mkconfig)
132       }
133       Object.class_eval {
134         remove_const(:CONFIG)
135         const_set(:CONFIG, mkconfig)
136       }
137       begin
138         $extconf_h = nil
139         ok &&= extract_makefile(makefile)
140         if (($extconf_h && !File.exist?($extconf_h)) ||
141             !(t = modified?(makefile, MTIMES)) ||
142             ["#{$srcdir}/makefile.rb", "#{$srcdir}/extconf.rb", "#{$srcdir}/depend"].any? {|f| modified?(f, [t])})
143         then
144           ok = false
145           init_mkmf
146           Logging::logfile 'mkmf.log'
147           rm_f makefile
148           if File.exist?($0 = "#{$srcdir}/makefile.rb")
149             load $0
150           elsif File.exist?($0 = "#{$srcdir}/extconf.rb")
151             load $0
152           else
153             create_makefile(target)
154           end
155           $defs << "-DRUBY_EXPORT" if $static
156           ok = File.exist?(makefile)
157         end
158       rescue SystemExit
159         # ignore
160       ensure
161         rm_f "conftest*"
162         config = $0
163         $0 = $PROGRAM_NAME
164       end
165     end
166     ok = yield(ok) if block_given?
167     unless ok
168       open(makefile, "w") do |f|
169         f.print(*dummy_makefile(CONFIG["srcdir"]))
170       end
171       return true
172     end
173     args = sysquote($mflags)
174     unless $destdir.to_s.empty? or $mflags.include?("DESTDIR")
175       args += [sysquote("DESTDIR=" + relative_from($destdir, "../"+prefix))]
176     end
177     if $static
178       args += ["static"] unless $clean
179       $extlist.push [$static, $target, File.basename($target), $preload]
180     end
181     unless system($make, *args)
182       $ignore or $continue or return false
183     end
184     $compiled[target] = true
185     if $clean
186       FileUtils.rm_f("mkmf.log")
187       if $clean != true
188         FileUtils.rm_f([makefile, $extconf_h || "extconf.h"])
189       end
190     end
191     if $static
192       $extflags ||= ""
193       $extlibs ||= []
194       $extpath ||= []
195       unless $mswin
196         $extflags = ($extflags.split | $DLDFLAGS.split | $LDFLAGS.split).join(" ")
197       end
198       $extlibs = merge_libs($extlibs, $libs.split, $LOCAL_LIBS.split)
199       $extpath |= $LIBPATH
200     end
201   ensure
202     unless $ignore
203       RbConfig.module_eval {
204         remove_const(:CONFIG)
205         const_set(:CONFIG, rbconfig0)
206         remove_const(:MAKEFILE_CONFIG)
207         const_set(:MAKEFILE_CONFIG, mkconfig0)
208       }
209       Object.class_eval {
210         remove_const(:CONFIG)
211         const_set(:CONFIG, mkconfig0)
212       }
213     end
214     $top_srcdir = top_srcdir
215     $topdir = topdir
216     $hdrdir = hdrdir
217     Dir.chdir dir
218   end
219   begin
220     Dir.rmdir target
221     target = File.dirname(target)
222   rescue SystemCallError
223     break
224   end while true
225   true
228 def compiled?(target)
229   $compiled[target]
232 def parse_args()
233   $mflags = []
235   $optparser ||= OptionParser.new do |opts|
236     opts.on('-n') {$dryrun = true}
237     opts.on('--[no-]extension [EXTS]', Array) do |v|
238       $extension = (v == false ? [] : v)
239     end
240     opts.on('--[no-]extstatic [STATIC]', Array) do |v|
241       if ($extstatic = v) == false
242         $extstatic = []
243       elsif v
244         $force_static = true if $extstatic.delete("static")
245         $extstatic = nil if $extstatic.empty?
246       end
247     end
248     opts.on('--dest-dir=DIR') do |v|
249       $destdir = v
250     end
251     opts.on('--extout=DIR') do |v|
252       $extout = (v unless v.empty?)
253     end
254     opts.on('--make=MAKE') do |v|
255       $make = v || 'make'
256     end
257     opts.on('--make-flags=FLAGS', '--mflags', Shellwords) do |v|
258       v.grep(/\A([-\w]+)=(.*)/) {$configure_args["--#{$1}"] = $2}
259       if arg = v.first
260         arg.insert(0, '-') if /\A[^-][^=]*\Z/ =~ arg
261       end
262       $mflags.concat(v)
263     end
264     opts.on('--message [MESSAGE]', String) do |v|
265       $message = v
266     end
267   end
268   begin
269     $optparser.parse!(ARGV)
270   rescue OptionParser::InvalidOption => e
271     retry if /^--/ =~ e.args[0]
272     $optparser.warn(e)
273     abort $optparser.to_s
274   end
276   $destdir ||= ''
278   $make, *rest = Shellwords.shellwords($make)
279   $mflags.unshift(*rest) unless rest.empty?
281   def $mflags.set?(flag)
282     grep(/\A-(?!-).*#{flag.chr}/i) { return true }
283     false
284   end
285   def $mflags.defined?(var)
286     grep(/\A#{var}=(.*)/) {return $1}
287     false
288   end
290   if $mflags.set?(?n)
291     $dryrun = true
292   else
293     $mflags.unshift '-n' if $dryrun
294   end
296   $continue = $mflags.set?(?k)
297   if $extout
298     $extout = '$(topdir)/'+$extout
299     RbConfig::CONFIG["extout"] = CONFIG["extout"] = $extout
300     $extout_prefix = $extout ? "$(extout)$(target_prefix)/" : ""
301     $mflags << "extout=#$extout" << "extout_prefix=#$extout_prefix"
302   end
305 parse_args()
307 if target = ARGV.shift and /^[a-z-]+$/ =~ target
308   $mflags.push(target)
309   case target
310   when /^(dist|real)?(clean)$/
311     target = $2
312     $ignore ||= true
313     $clean = $1 ? $1[0] : true
314   when /^install\b/
315     $install = true
316     $ignore ||= true
317     $mflags.unshift("INSTALL_PROG=install -c -p -m 0755",
318                     "INSTALL_DATA=install -c -p -m 0644",
319                     "MAKEDIRS=mkdir -p") if $dryrun
320   end
322 unless $message
323   if target
324     $message = target.sub(/^(\w+)e?\b/, '\1ing').tr('-', ' ')
325   else
326     $message = "compiling"
327   end
330 EXEEXT = CONFIG['EXEEXT']
331 if CROSS_COMPILING
332   $ruby = $mflags.defined?("MINIRUBY") || CONFIG['MINIRUBY']
333 elsif sep = config_string('BUILD_FILE_SEPARATOR')
334   $ruby = "$(topdir:/=#{sep})#{sep}miniruby" + EXEEXT
335 else
336   $ruby = '$(topdir)/miniruby' + EXEEXT
338 $ruby << " -I'$(topdir)'"
339 unless CROSS_COMPILING
340   $ruby << " -I'$(top_srcdir)/lib'"
341   $ruby << " -I'$(extout)/$(arch)' -I'$(extout)/common'" if $extout
342   $ruby << " -I./- -I'$(top_srcdir)/ext' -rpurelib.rb"
343   ENV["RUBYLIB"] = "-"
344   ENV["RUBYOPT"] = "-r#{File.expand_path('ext/purelib.rb', $top_srcdir)}"
346 $config_h = '$(arch_hdrdir)/ruby/config.h'
347 $mflags << "ruby=#$ruby"
349 MTIMES = [__FILE__, 'rbconfig.rb', srcdir+'/lib/mkmf.rb'].collect {|f| File.mtime(f)}
351 # get static-link modules
352 $static_ext = {}
353 if $extstatic
354   $extstatic.each do |t|
355     target = t
356     target = target.downcase if /mswin32|bccwin32/ =~ RUBY_PLATFORM
357     $static_ext[target] = $static_ext.size
358   end
360 for dir in ["ext", File::join($top_srcdir, "ext")]
361   setup = File::join(dir, CONFIG['setup'])
362   if File.file? setup
363     f = open(setup)
364     while line = f.gets()
365       line.chomp!
366       line.sub!(/#.*$/, '')
367       next if /^\s*$/ =~ line
368       target, opt = line.split(nil, 3)
369       if target == 'option'
370         case opt
371         when 'nodynamic'
372           $nodynamic = true
373         end
374         next
375       end
376       target = target.downcase if /mswin32|bccwin32/ =~ RUBY_PLATFORM
377       $static_ext[target] = $static_ext.size
378     end
379     MTIMES << f.mtime
380     $setup = setup
381     f.close
382     break
383   end
384 end unless $extstatic
386 ext_prefix = "#{$top_srcdir}/ext"
387 exts = $static_ext.sort_by {|t, i| i}.collect {|t, i| t}
388 if $extension
389   exts |= $extension.select {|d| File.directory?("#{ext_prefix}/#{d}")}
390 else
391   withes, withouts = %w[--with --without].collect {|w|
392     if not (w = %w[-extensions -ext].collect {|o|arg_config(w+o)}).any?
393       nil
394     elsif (w = w.grep(String)).empty?
395       proc {true}
396     else
397       proc {|c1| w.collect {|o| o.split(/,/)}.flatten.any?(&c1)}
398     end
399   }
400   if withes
401     withouts ||= proc {true}
402   else
403     withes = proc {false}
404     withouts ||= withes
405   end
406   cond = proc {|ext, *|
407     cond1 = proc {|n| File.fnmatch(n, ext)}
408     withes.call(cond1) or !withouts.call(cond1)
409   }
410   exts |= Dir.glob("#{ext_prefix}/*/**/extconf.rb").collect {|d|
411     d = File.dirname(d)
412     d.slice!(0, ext_prefix.length + 1)
413     d
414   }.find_all {|ext|
415     with_config(ext, &cond)
416   }.sort
419 if $extout
420   extout = RbConfig.expand("#{$extout}", RbConfig::CONFIG.merge("topdir"=>$topdir))
421   unless $ignore
422     FileUtils.mkpath(extout)
423   end
426 dir = Dir.pwd
427 FileUtils::makedirs('ext')
428 Dir::chdir('ext')
430 hdrdir = $hdrdir
431 $hdrdir = ($top_srcdir = relative_from(srcdir, $topdir = "..")) + "/include"
432 exts.each do |d|
433   $static = $force_static ? $static_ext[target] : false
435   if $ignore or !$nodynamic or $static
436     extmake(d) or abort
437   end
439 $top_srcdir = srcdir
440 $topdir = "."
441 $hdrdir = hdrdir
443 extinit = Struct.new(:c, :o) {
444   def initialize(src)
445     super("#{src}.c", "#{src}.#{$OBJEXT}")
446   end
447 }.new("extinit")
448 if $ignore
449   FileUtils.rm_f(extinit.to_a) if $clean
450   Dir.chdir ".."
451   if $clean
452     Dir.rmdir('ext') rescue nil
453     if $extout
454       FileUtils.rm_rf([extout+"/common", extout+"/include/ruby", extout+"/rdoc"])
455       FileUtils.rm_rf(extout+"/"+CONFIG["arch"])
456       if $clean != true
457         FileUtils.rm_rf(extout+"/include/"+CONFIG["arch"])
458         FileUtils.rm_f($mflags.defined?("INSTALLED_LIST")||ENV["INSTALLED_LIST"]||".installed.list")
459         Dir.rmdir(extout+"/include") rescue nil
460         Dir.rmdir(extout) rescue nil
461       end
462     end
463   end
464   exit
467 $extinit ||= ""
468 $extobjs ||= ""
469 $extpath ||= []
470 $extflags ||= ""
471 $extlibs ||= []
472 unless $extlist.empty?
473   $extinit << "\n" unless $extinit.empty?
474   list = $extlist.dup
475   built = []
476   while e = list.shift
477     s,t,i,r = e
478     if r and !(r -= built).empty?
479       l = list.size
480       if (while l > 0; break true if r.include?(list[l-=1][1]) end)
481         list.insert(l + 1, e)
482       end
483       next
484     end
485     f = format("%s/%s.%s", s, i, $LIBEXT)
486     if File.exist?(f)
487       $extinit << "    init(Init_#{i}, \"#{t}.so\");\n"
488       $extobjs << "ext/#{f} "
489       built << t
490     end
491   end
493   src = %{\
494 #include "ruby.h"
496 #define init(func, name) {      \\
497     extern void func _((void)); \\
498     ruby_init_ext(name, func);  \\
501 void ruby_init_ext _((const char *name, void (*init)(void)));
503 void Init_ext _((void))\n{\n#$extinit}
505   if !modified?(extinit.c, MTIMES) || IO.read(extinit.c) != src
506     open(extinit.c, "w") {|fe| fe.print src}
507   end
509   $extobjs = "ext/#{extinit.o} #{$extobjs}"
510   if RUBY_PLATFORM =~ /m68k-human|beos/
511     $extflags.delete("-L/usr/local/lib")
512   end
513   $extpath.delete("$(topdir)")
514   $extflags = libpathflag($extpath) << " " << $extflags.strip
515   conf = [
516     ['LIBRUBY_SO_UPDATE', '$(LIBRUBY_EXTS)'],
517     ['SETUP', $setup],
518     [enable_config("shared", $enable_shared) ? 'DLDOBJS' : 'EXTOBJS', $extobjs],
519     ['EXTLIBS', $extlibs.join(' ')], ['EXTLDFLAGS', $extflags]
520   ].map {|n, v|
521     "#{n}=#{v}" if v and !(v = v.strip).empty?
522   }.compact
523   puts(*conf)
524   $stdout.flush
525   $mflags.concat(conf)
526 else
527   FileUtils.rm_f(extinit.to_a)
529 rubies = []
530 %w[RUBY RUBYW STATIC_RUBY].each {|n|
531   r = n
532   if r = arg_config("--"+r.downcase) || config_string(r+"_INSTALL_NAME")
533     rubies << Config.expand(r+=EXEEXT)
534     $mflags << "#{n}=#{r}"
535   end
538 Dir.chdir ".."
539 unless $destdir.to_s.empty?
540   $mflags.defined?("DESTDIR") or $mflags << "DESTDIR=#{$destdir}"
542 puts "making #{rubies.join(', ')}"
543 $stdout.flush
544 $mflags.concat(rubies)
546 if $nmake == ?b
547   unless (vars = $mflags.grep(/\A\w+=/n)).empty?
548     open(mkf = "libruby.mk", "wb") do |tmf|
549       tmf.puts("!include Makefile")
550       tmf.puts
551       tmf.puts(*vars.map {|v| v.sub(/=/, " = ")})
552       tmf.puts("PRE_LIBRUBY_UPDATE = del #{mkf}")
553     end
554     $mflags.unshift("-f#{mkf}")
555     vars.each {|flag| flag.sub!(/\A/, "-D")}
556   end
558 $mflags.unshift("topdir=#$topdir")
559 ENV.delete("RUBYOPT")
560 system($make, *sysquote($mflags)) or exit($?.exitstatus)
562 #Local variables:
563 # mode: ruby
564 #end: