Initial commit.
[ruby-discid.git] / rake / extensiontask.rb
1 require 'rake'
2 require 'rake/clean'
3 require 'rake/tasklib'
4
5 module Rake
6
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).
11   #
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.
17   #
18   # Example:
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
23   #     t.dir = 'ext'
24   #     # link libraries (libbar.so)
25   #     t.link_libs << 'bar'
26   #   end
27   #
28   # Author::    Steve Sloan (mailto:steve@finagle.org)
29   # Copyright:: Copyright (c) 2006 Steve Sloan
30   # License::   GPL
31
32   class ExtensionTask < Rake::TaskLib
33     # The name of the extension
34     attr_accessor :name
35
36     # The filename of the extension library file (e.g. 'extension.so')
37     attr_accessor :lib_name
38
39     # Object files to build and link into the extension.
40     attr_accessor :objs
41
42     # The directory where the extension files (source, output, and
43     # intermediate) are stored.
44     attr_accessor :dir
45
46     # Environment configuration -- i.e. CONFIG from rbconfig, with a few other
47     # settings, and converted to lowercase-symbols.
48     attr_accessor :env
49
50     # Additional link libraries
51     attr_accessor :link_libs
52
53     # Same arguments as Rake::define_task
54     def initialize( args, &blk )
55       @env = @@DefaultEnv.dup
56       @name, @objs = resolve_args(args)
57       set_defaults
58       yield self  if block_given?
59       define_tasks
60     end
61
62     # Generate default values.  This is called from initialize _before_ the
63     # yield block.
64     #
65     # Defaults:
66     # - lib_name: name.so
67     # - objs: name.o (<- name.{c,cxx,cpp,cc})
68     # - dir: .
69     # - link_libs: <none>
70     def set_defaults
71       @lib_name ||= name.to_sym
72       @objs ||= [name.to_sym]
73       @dir ||= '.'
74       @link_libs ||= []
75     end
76
77     # Defines the library task.
78     def define_tasks
79       output_objs = @objs.collect { |obj| filepath obj, :objext }
80       output_lib = filepath lib_name, :dlext
81
82       task name => output_lib
83
84       file output_lib => output_objs do |t|
85         sh_cmd :ldshared, :dldflags, :ldflags,
86                {'-L' => :libdirs}, '-o', output_lib,
87                output_objs.join(' '),
88                link_libs.join(' '),
89                :libs, :dldlibs, :librubyarg_shared
90       end
91
92       CLEAN.include output_objs
93       CLOBBER.include output_lib
94       define_rules
95     end
96
97     # Defines C and C++ source-to-object rules, using the source extensions from env.
98     def define_rules
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
103         end
104       end
105
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
110         end
111       end
112     end
113
114     class << self
115       # The default environment for all extensions.
116       @@DefaultEnv = {}
117       def env
118         @@DefaultEnv
119       end
120       def env=(e)
121         @@DefaultEnv = e
122       end
123
124       Config::CONFIG.merge(ENV).each { |k, v| @@DefaultEnv[k.downcase.to_sym] = v }
125       @@DefaultEnv = {
126         :cxx => 'c++',
127         :cxxflags => '',
128         :c_exts => ['c'],
129         :cpp_exts => ['cc', 'cxx', 'cpp'],
130         :includedirs => [],
131         :libdirs => [],
132       }.update(@@DefaultEnv)
133     end
134
135   protected
136
137     # Handles convenience filenames:
138     # * f (String) => f
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
143     end
144
145     # Convenience function for cnstructing command lines for build tools.
146     def optify( *opts )
147       return optify(*opts.first)  if opts.size == 1 and opts.first.kind_of? Array
148       opts.collect do |opt|
149         case opt
150           when String then  opt
151           when Symbol then  optify env[opt]
152           when Hash
153             opt.collect do |k, v|
154               v = env[v]  if v.kind_of? Symbol
155               if v.kind_of? Array
156                 optify v.collect { |w| k.to_s + w.to_s }
157               elsif v
158                 k.to_s + v.to_s
159               end
160             end
161           else
162             opt.to_s
163         end
164       end.join(' ').squeeze(' ')
165     end
166
167     def sh_cmd( cmd, *opts )
168       sh optify( cmd, *opts )
169     end
170
171     # For some reason, Rake::TaskManager.resolve_args can't be found, so snarf it.
172     def resolve_args(args)
173       case args
174       when Hash
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)
180       else
181         task_name = args
182         deps = []
183       end
184       [task_name, deps]
185     end
186
187   end
188
189 end