7 # Create a build task that will generate a Ruby extension (e.g. .so) from one or more
8 # C (.c) or C++ (.cc, .cpp, .cxx) files, and is intended as a replcaement for mkmf.
9 # It determines platform-specific settings (e.g. file extensions, compiler flags, etc.)
10 # from rbconfig (note: examples assume *nix file extensions).
12 # *Note*: Strings vs Symbols
13 # In places where filenames are expected (e.g. lib_name and objs), Strings are used
14 # as verbatim filenames, while, Symbols have the platform-dependant extension
15 # appended (e.g. '.so' for libraries and '.o' for objects). Also, only Symbols
16 # have #dir prepended to them.
19 # desc "build sample extension"
20 # # build sample.so (from foo.{c,cc,cxx,cpp}, through foo.o)
21 # Rake::ExtensionTask.new :sample => :foo do |t|
22 # # all extension files under this directory
24 # # link libraries (libbar.so)
25 # t.link_libs << 'bar'
28 # Author:: Steve Sloan (mailto:steve@finagle.org)
29 # Copyright:: Copyright (c) 2006 Steve Sloan
32 class ExtensionTask < Rake::TaskLib
33 # The name of the extension
36 # The filename of the extension library file (e.g. 'extension.so')
37 attr_accessor :lib_name
39 # Object files to build and link into the extension.
42 # The directory where the extension files (source, output, and
43 # intermediate) are stored.
46 # Environment configuration -- i.e. CONFIG from rbconfig, with a few other
47 # settings, and converted to lowercase-symbols.
50 # Additional link libraries
51 attr_accessor :link_libs
53 # Same arguments as Rake::define_task
54 def initialize( args, &blk )
55 @env = @@DefaultEnv.dup
56 @name, @objs = resolve_args(args)
58 yield self if block_given?
62 # Generate default values. This is called from initialize _before_ the
67 # - objs: name.o (<- name.{c,cxx,cpp,cc})
71 @lib_name ||= name.to_sym
72 @objs ||= [name.to_sym]
77 # Defines the library task.
79 output_objs = @objs.collect { |obj| filepath obj, :objext }
80 output_lib = filepath lib_name, :dlext
82 task name => output_lib
84 file output_lib => output_objs do |t|
85 sh_cmd :ldshared, :dldflags, :ldflags,
86 {'-L' => :libdirs}, '-o', output_lib,
87 output_objs.join(' '),
89 :libs, :dldlibs, :librubyarg_shared
92 CLEAN.include output_objs
93 CLOBBER.include output_lib
97 # Defines C and C++ source-to-object rules, using the source extensions from env.
99 for ext in env[:c_exts]
100 Rake::Task.create_rule '.'+env[:objext] => '.'+ext do |r|
101 sh_cmd :cc, :cflags, :cppflags, {'-D' => :defines}, {'-I' => :includedirs}, {'-I' => :topdir},
102 '-c', '-o', r.name, r.sources
106 for ext in env[:cpp_exts]
107 Rake::Task.create_rule '.'+env[:objext] => '.'+ext do |r|
108 sh_cmd :cxx, :cxxflags, :cppflags, {'-D' => :defines}, {'-I' => :includedirs}, {'-I' => :topdir},
109 '-o', r.name, '-c', r.sources
115 # The default environment for all extensions.
124 Config::CONFIG.merge(ENV).each { |k, v| @@DefaultEnv[k.downcase.to_sym] = v }
129 :cpp_exts => ['cc', 'cxx', 'cpp'],
132 }.update(@@DefaultEnv)
137 # Handles convenience filenames:
139 # * f (Symbol) => dir/f.ext
140 def filepath( f, ext )
141 ext = env[ext] if Symbol === ext
142 Symbol === f ? File.join( dir, "#{f}.#{ext}" ) : f
145 # Convenience function for cnstructing command lines for build tools.
147 return optify(*opts.first) if opts.size == 1 and opts.first.kind_of? Array
148 opts.collect do |opt|
151 when Symbol then optify env[opt]
153 opt.collect do |k, v|
154 v = env[v] if v.kind_of? Symbol
156 optify v.collect { |w| k.to_s + w.to_s }
164 end.join(' ').squeeze(' ')
167 def sh_cmd( cmd, *opts )
168 sh optify( cmd, *opts )
171 # For some reason, Rake::TaskManager.resolve_args can't be found, so snarf it.
172 def resolve_args(args)
175 fail "Too Many Task Names: #{args.keys.join(' ')}" if args.size > 1
176 fail "No Task Name Given" if args.size < 1
177 task_name = args.keys[0]
178 deps = args[task_name]
179 deps = [deps] if (String===deps) || (Regexp===deps) || (Proc===deps)