1require "mruby/build/load_gems"
2require "mruby/build/command"
3
4module MRuby
5  class << self
6    def targets
7      @targets ||= {}
8    end
9
10    def each_target(&block)
11      return to_enum(:each_target) if block.nil?
12      @targets.each do |key, target|
13        target.instance_eval(&block)
14      end
15    end
16  end
17
18  class Toolchain
19    class << self
20      attr_accessor :toolchains
21    end
22
23    def initialize(name, &block)
24      @name, @initializer = name.to_s, block
25      MRuby::Toolchain.toolchains ||= {}
26      MRuby::Toolchain.toolchains[@name] = self
27    end
28
29    def setup(conf,params={})
30      conf.instance_exec(conf, params, &@initializer)
31    end
32
33    def self.load
34      Dir.glob("#{MRUBY_ROOT}/tasks/toolchains/*.rake").each do |file|
35        Kernel.load file
36      end
37    end
38  end
39  Toolchain.load
40
41  class Build
42    class << self
43      attr_accessor :current
44    end
45    include Rake::DSL
46    include LoadGems
47    attr_accessor :name, :bins, :exts, :file_separator, :build_dir, :gem_clone_dir
48    attr_reader :libmruby_objs, :gems, :toolchains
49    attr_writer :enable_bintest, :enable_test
50
51    alias libmruby libmruby_objs
52
53    COMPILERS = %w(cc cxx objc asm)
54    COMMANDS = COMPILERS + %w(linker archiver yacc gperf git exts mrbc)
55    attr_block MRuby::Build::COMMANDS
56
57    Exts = Struct.new(:object, :executable, :library)
58
59    def initialize(name='host', build_dir=nil, &block)
60      @name = name.to_s
61
62      unless MRuby.targets[@name]
63        if ENV['OS'] == 'Windows_NT'
64          @exts = Exts.new('.o', '.exe', '.a')
65        else
66          @exts = Exts.new('.o', '', '.a')
67        end
68
69        build_dir = build_dir || ENV['MRUBY_BUILD_DIR'] || "#{MRUBY_ROOT}/build"
70
71        @file_separator = '/'
72        @build_dir = "#{build_dir}/#{@name}"
73        @gem_clone_dir = "#{build_dir}/mrbgems"
74        @cc = Command::Compiler.new(self, %w(.c))
75        @cxx = Command::Compiler.new(self, %w(.cc .cxx .cpp))
76        @objc = Command::Compiler.new(self, %w(.m))
77        @asm = Command::Compiler.new(self, %w(.S .asm))
78        @linker = Command::Linker.new(self)
79        @archiver = Command::Archiver.new(self)
80        @yacc = Command::Yacc.new(self)
81        @gperf = Command::Gperf.new(self)
82        @git = Command::Git.new(self)
83        @mrbc = Command::Mrbc.new(self)
84
85        @bins = []
86        @gems, @libmruby_objs = MRuby::Gem::List.new, []
87        @build_mrbtest_lib_only = false
88        @cxx_exception_enabled = false
89        @cxx_exception_disabled = false
90        @cxx_abi_enabled = false
91        @enable_bintest = false
92        @enable_test = false
93        @toolchains = []
94
95        MRuby.targets[@name] = self
96      end
97
98      MRuby::Build.current = MRuby.targets[@name]
99      MRuby.targets[@name].instance_eval(&block)
100
101      build_mrbc_exec if name == 'host'
102      build_mrbtest if test_enabled?
103    end
104
105    def debug_enabled?
106      @enable_debug
107    end
108
109    def enable_debug
110      compilers.each do |c|
111        c.defines += %w(MRB_DEBUG)
112        if toolchains.any? { |toolchain| toolchain == "gcc" }
113          c.flags += %w(-g3 -O0)
114        end
115      end
116      @mrbc.compile_options += ' -g'
117
118      @enable_debug = true
119    end
120
121    def disable_cxx_exception
122      if @cxx_exception_enabled or @cxx_abi_enabled
123        raise "cxx_exception already enabled"
124      end
125      @cxx_exception_disabled = true
126    end
127
128    def enable_cxx_exception
129      return if @cxx_exception_enabled
130      return if @cxx_abi_enabled
131      if @cxx_exception_disabled
132        raise "cxx_exception disabled"
133      end
134      @cxx_exception_enabled = true
135      compilers.each { |c|
136        c.defines += %w(MRB_ENABLE_CXX_EXCEPTION)
137        c.flags << c.cxx_exception_flag
138      }
139      linker.command = cxx.command if toolchains.find { |v| v == 'gcc' }
140    end
141
142    def cxx_exception_enabled?
143      @cxx_exception_enabled
144    end
145
146    def cxx_abi_enabled?
147      @cxx_abi_enabled
148    end
149
150    def enable_cxx_abi
151      return if @cxx_abi_enabled
152      if @cxx_exception_enabled
153        raise "cxx_exception already enabled"
154      end
155      compilers.each { |c|
156        c.defines += %w(MRB_ENABLE_CXX_EXCEPTION MRB_ENABLE_CXX_ABI)
157        c.flags << c.cxx_compile_flag
158      }
159      compilers.each { |c| c.flags << c.cxx_compile_flag }
160      linker.command = cxx.command if toolchains.find { |v| v == 'gcc' }
161      @cxx_abi_enabled = true
162    end
163
164    def compile_as_cxx src, cxx_src, obj = nil, includes = []
165      obj = objfile(cxx_src) if obj.nil?
166
167      file cxx_src => [src, __FILE__] do |t|
168        FileUtils.mkdir_p File.dirname t.name
169        IO.write t.name, <<EOS
170#define __STDC_CONSTANT_MACROS
171#define __STDC_LIMIT_MACROS
172
173#ifndef MRB_ENABLE_CXX_ABI
174extern "C" {
175#endif
176#include "#{File.absolute_path src}"
177#ifndef MRB_ENABLE_CXX_ABI
178}
179#endif
180EOS
181      end
182
183      file obj => cxx_src do |t|
184        cxx.run t.name, t.prerequisites.first, [], ["#{MRUBY_ROOT}/src"] + includes
185      end
186
187      obj
188    end
189
190    def enable_bintest
191      @enable_bintest = true
192    end
193
194    def bintest_enabled?
195      @enable_bintest
196    end
197
198    def toolchain(name, params={})
199      tc = Toolchain.toolchains[name.to_s]
200      fail "Unknown #{name} toolchain" unless tc
201      tc.setup(self, params)
202      @toolchains.unshift name.to_s
203    end
204
205    def primary_toolchain
206      @toolchains.first
207    end
208
209    def root
210      MRUBY_ROOT
211    end
212
213    def enable_test
214      @enable_test = true
215    end
216
217    def test_enabled?
218      @enable_test
219    end
220
221    def build_mrbtest
222      gem :core => 'mruby-test'
223    end
224
225    def build_mrbc_exec
226      gem :core => 'mruby-bin-mrbc'
227    end
228
229    def mrbcfile
230      return @mrbcfile if @mrbcfile
231
232      mrbc_build = MRuby.targets['host']
233      gems.each { |v| mrbc_build = self if v.name == 'mruby-bin-mrbc' }
234      @mrbcfile = mrbc_build.exefile("#{mrbc_build.build_dir}/bin/mrbc")
235    end
236
237    def compilers
238      COMPILERS.map do |c|
239        instance_variable_get("@#{c}")
240      end
241    end
242
243    def define_rules
244      compilers.each do |compiler|
245        if respond_to?(:enable_gems?) && enable_gems?
246          compiler.defines -= %w(DISABLE_GEMS)
247        else
248          compiler.defines += %w(DISABLE_GEMS)
249        end
250        compiler.define_rules build_dir, File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
251      end
252    end
253
254    def filename(name)
255      if name.is_a?(Array)
256        name.flatten.map { |n| filename(n) }
257      else
258        '"%s"' % name.gsub('/', file_separator)
259      end
260    end
261
262    def cygwin_filename(name)
263      if name.is_a?(Array)
264        name.flatten.map { |n| cygwin_filename(n) }
265      else
266        '"%s"' % `cygpath -w "#{filename(name)}"`.strip
267      end
268    end
269
270    def exefile(name)
271      if name.is_a?(Array)
272        name.flatten.map { |n| exefile(n) }
273      elsif File.extname(name).empty?
274        "#{name}#{exts.executable}"
275      else
276        # `name` sometimes have (non-standard) extension (e.g. `.bat`).
277        name
278      end
279    end
280
281    def objfile(name)
282      if name.is_a?(Array)
283        name.flatten.map { |n| objfile(n) }
284      else
285        "#{name}#{exts.object}"
286      end
287    end
288
289    def libfile(name)
290      if name.is_a?(Array)
291        name.flatten.map { |n| libfile(n) }
292      else
293        "#{name}#{exts.library}"
294      end
295    end
296
297    def build_mrbtest_lib_only
298      @build_mrbtest_lib_only = true
299    end
300
301    def build_mrbtest_lib_only?
302      @build_mrbtest_lib_only
303    end
304
305    def verbose_flag
306      $verbose ? ' -v' : ''
307    end
308
309    def run_test
310      puts ">>> Test #{name} <<<"
311      mrbtest = exefile("#{build_dir}/bin/mrbtest")
312      sh "#{filename mrbtest.relative_path}#{verbose_flag}"
313      puts
314    end
315
316    def run_bintest
317      targets = @gems.select { |v| File.directory? "#{v.dir}/bintest" }.map { |v| filename v.dir }
318      targets << filename(".") if File.directory? "./bintest"
319      sh "ruby test/bintest.rb#{verbose_flag} #{targets.join ' '}"
320    end
321
322    def print_build_summary
323      puts "================================================"
324      puts "      Config Name: #{@name}"
325      puts " Output Directory: #{self.build_dir.relative_path}"
326      puts "         Binaries: #{@bins.join(', ')}" unless @bins.empty?
327      unless @gems.empty?
328        puts "    Included Gems:"
329        @gems.map do |gem|
330          gem_version = " - #{gem.version}" if gem.version != '0.0.0'
331          gem_summary = " - #{gem.summary}" if gem.summary
332          puts "             #{gem.name}#{gem_version}#{gem_summary}"
333          puts "               - Binaries: #{gem.bins.join(', ')}" unless gem.bins.empty?
334        end
335      end
336      puts "================================================"
337      puts
338    end
339
340    def libmruby_static
341      libfile("#{build_dir}/lib/libmruby")
342    end
343
344    def libmruby_core_static
345      libfile("#{build_dir}/lib/libmruby_core")
346    end
347
348    def libraries
349      [libmruby_static]
350    end
351  end # Build
352
353  class CrossBuild < Build
354    attr_block %w(test_runner)
355    # cross compiling targets for building native extensions.
356    # host  - arch of where the built binary will run
357    # build - arch of the machine building the binary
358    attr_accessor :host_target, :build_target
359
360    def initialize(name, build_dir=nil, &block)
361      @endian = nil
362      @test_runner = Command::CrossTestRunner.new(self)
363      super
364    end
365
366    def mrbcfile
367      MRuby.targets['host'].exefile("#{MRuby.targets['host'].build_dir}/bin/mrbc")
368    end
369
370    def run_test
371      @test_runner.runner_options << ' -v' if $verbose
372      mrbtest = exefile("#{build_dir}/bin/mrbtest")
373      if (@test_runner.command == nil)
374        puts "You should run #{mrbtest} on target device."
375        puts
376      else
377        @test_runner.run(mrbtest)
378      end
379    end
380
381    def big_endian
382      if @endian
383        puts "Endian has already specified as #{@endian}."
384        return
385      end
386      @endian = :big
387      @mrbc.compile_options += ' -E'
388      compilers.each do |c|
389        c.defines += %w(MRB_ENDIAN_BIG)
390      end
391    end
392
393    def little_endian
394      if @endian
395        puts "Endian has already specified as #{@endian}."
396        return
397      end
398      @endian = :little
399      @mrbc.compile_options += ' -e'
400    end
401  end # CrossBuild
402end # MRuby
403