1require 'forwardable'
2
3module MRuby
4  class Command
5    include Rake::DSL
6    extend Forwardable
7    def_delegators :@build, :filename, :objfile, :libfile, :exefile, :cygwin_filename
8    attr_accessor :build, :command
9
10    def initialize(build)
11      @build = build
12    end
13
14    # clone is deep clone without @build
15    def clone
16      target = super
17      excepts = %w(@build)
18      instance_variables.each do |attr|
19        unless excepts.include?(attr.to_s)
20          val = Marshal::load(Marshal.dump(instance_variable_get(attr))) # deep clone
21          target.instance_variable_set(attr, val)
22        end
23      end
24      target
25    end
26
27    NotFoundCommands = {}
28
29    private
30    def _run(options, params={})
31      return sh command + ' ' + ( options % params ) if NotFoundCommands.key? @command
32      begin
33        sh build.filename(command) + ' ' + ( options % params )
34      rescue RuntimeError
35        NotFoundCommands[@command] = true
36        _run options, params
37      end
38    end
39  end
40
41  class Command::Compiler < Command
42    attr_accessor :flags, :include_paths, :defines, :source_exts
43    attr_accessor :compile_options, :option_define, :option_include_path, :out_ext
44    attr_accessor :cxx_compile_flag, :cxx_exception_flag
45
46    def initialize(build, source_exts=[])
47      super(build)
48      @command = ENV['CC'] || 'cc'
49      @flags = [ENV['CFLAGS'] || []]
50      @source_exts = source_exts
51      @include_paths = ["#{MRUBY_ROOT}/include"]
52      @defines = %w()
53      @option_include_path = '-I%s'
54      @option_define = '-D%s'
55      @compile_options = '%{flags} -o %{outfile} -c %{infile}'
56    end
57
58    alias header_search_paths include_paths
59    def search_header_path(name)
60      header_search_paths.find do |v|
61        File.exist? build.filename("#{v}/#{name}").sub(/^"(.*)"$/, '\1')
62      end
63    end
64
65    def search_header(name)
66      path = search_header_path name
67      path && build.filename("#{path}/#{name}").sub(/^"(.*)"$/, '\1')
68    end
69
70    def all_flags(_defines=[], _include_paths=[], _flags=[])
71      define_flags = [defines, _defines].flatten.map{ |d| option_define % d }
72      include_path_flags = [include_paths, _include_paths].flatten.map do |f|
73        if MRUBY_BUILD_HOST_IS_CYGWIN
74          option_include_path % cygwin_filename(f)
75        else
76          option_include_path % filename(f)
77        end
78      end
79      [flags, define_flags, include_path_flags, _flags].flatten.join(' ')
80    end
81
82    def run(outfile, infile, _defines=[], _include_paths=[], _flags=[])
83      FileUtils.mkdir_p File.dirname(outfile)
84      _pp "CC", infile.relative_path, outfile.relative_path
85      if MRUBY_BUILD_HOST_IS_CYGWIN
86        _run compile_options, { :flags => all_flags(_defines, _include_paths, _flags),
87                                :infile => cygwin_filename(infile), :outfile => cygwin_filename(outfile) }
88      else
89        _run compile_options, { :flags => all_flags(_defines, _include_paths, _flags),
90                                :infile => filename(infile), :outfile => filename(outfile) }
91      end
92    end
93
94    def define_rules(build_dir, source_dir='')
95      @out_ext = build.exts.object
96      gemrake = File.join(source_dir, "mrbgem.rake")
97      rakedep = File.exist?(gemrake) ? [ gemrake ] : []
98
99      if build_dir.include? "mrbgems/"
100        generated_file_matcher = Regexp.new("^#{Regexp.escape build_dir}/(.*)#{Regexp.escape out_ext}$")
101      else
102        generated_file_matcher = Regexp.new("^#{Regexp.escape build_dir}/(?!mrbgems/.+/)(.*)#{Regexp.escape out_ext}$")
103      end
104      source_exts.each do |ext, compile|
105        rule generated_file_matcher => [
106          proc { |file|
107            file.sub(generated_file_matcher, "#{source_dir}/\\1#{ext}")
108          },
109          proc { |file|
110            get_dependencies(file) + rakedep
111          }
112        ] do |t|
113          run t.name, t.prerequisites.first
114        end
115
116        rule generated_file_matcher => [
117          proc { |file|
118            file.sub(generated_file_matcher, "#{build_dir}/\\1#{ext}")
119          },
120          proc { |file|
121            get_dependencies(file) + rakedep
122          }
123        ] do |t|
124          run t.name, t.prerequisites.first
125        end
126      end
127    end
128
129    private
130    def get_dependencies(file)
131      file = file.ext('d') unless File.extname(file) == '.d'
132      if File.exist?(file)
133        File.read(file).gsub("\\\n ", "").scan(/^\S+:\s+(.+)$/).flatten.map {|s| s.split(' ') }.flatten
134      else
135        []
136      end + [ MRUBY_CONFIG ]
137    end
138  end
139
140  class Command::Linker < Command
141    attr_accessor :flags, :library_paths, :flags_before_libraries, :libraries, :flags_after_libraries
142    attr_accessor :link_options, :option_library, :option_library_path
143
144    def initialize(build)
145      super
146      @command = ENV['LD'] || 'ld'
147      @flags = (ENV['LDFLAGS'] || [])
148      @flags_before_libraries, @flags_after_libraries = [], []
149      @libraries = []
150      @library_paths = []
151      @option_library = '-l%s'
152      @option_library_path = '-L%s'
153      @link_options = "%{flags} -o %{outfile} %{objs} %{flags_before_libraries} %{libs} %{flags_after_libraries}"
154    end
155
156    def all_flags(_library_paths=[], _flags=[])
157      library_path_flags = [library_paths, _library_paths].flatten.map do |f|
158        if MRUBY_BUILD_HOST_IS_CYGWIN
159          option_library_path % cygwin_filename(f)
160        else
161          option_library_path % filename(f)
162        end
163      end
164      [flags, library_path_flags, _flags].flatten.join(' ')
165    end
166
167    def library_flags(_libraries)
168      [libraries, _libraries].flatten.map{ |d| option_library % d }.join(' ')
169    end
170
171    def run(outfile, objfiles, _libraries=[], _library_paths=[], _flags=[], _flags_before_libraries=[], _flags_after_libraries=[])
172      FileUtils.mkdir_p File.dirname(outfile)
173      library_flags = [libraries, _libraries].flatten.map { |d| option_library % d }
174
175      _pp "LD", outfile.relative_path
176      if MRUBY_BUILD_HOST_IS_CYGWIN
177        _run link_options, { :flags => all_flags(_library_paths, _flags),
178                             :outfile => cygwin_filename(outfile) , :objs => cygwin_filename(objfiles).join(' '),
179                             :flags_before_libraries => [flags_before_libraries, _flags_before_libraries].flatten.join(' '),
180                             :flags_after_libraries => [flags_after_libraries, _flags_after_libraries].flatten.join(' '),
181                             :libs => library_flags.join(' ') }
182      else
183        _run link_options, { :flags => all_flags(_library_paths, _flags),
184                             :outfile => filename(outfile) , :objs => filename(objfiles).join(' '),
185                             :flags_before_libraries => [flags_before_libraries, _flags_before_libraries].flatten.join(' '),
186                             :flags_after_libraries => [flags_after_libraries, _flags_after_libraries].flatten.join(' '),
187                             :libs => library_flags.join(' ') }
188      end
189    end
190  end
191
192  class Command::Archiver < Command
193    attr_accessor :archive_options
194
195    def initialize(build)
196      super
197      @command = ENV['AR'] || 'ar'
198      @archive_options = 'rs %{outfile} %{objs}'
199    end
200
201    def run(outfile, objfiles)
202      FileUtils.mkdir_p File.dirname(outfile)
203      _pp "AR", outfile.relative_path
204      if MRUBY_BUILD_HOST_IS_CYGWIN
205        _run archive_options, { :outfile => cygwin_filename(outfile), :objs => cygwin_filename(objfiles).join(' ') }
206      else
207        _run archive_options, { :outfile => filename(outfile), :objs => filename(objfiles).join(' ') }
208      end
209    end
210  end
211
212  class Command::Yacc < Command
213    attr_accessor :compile_options
214
215    def initialize(build)
216      super
217      @command = 'bison'
218      @compile_options = '-o %{outfile} %{infile}'
219    end
220
221    def run(outfile, infile)
222      FileUtils.mkdir_p File.dirname(outfile)
223      _pp "YACC", infile.relative_path, outfile.relative_path
224      _run compile_options, { :outfile => filename(outfile) , :infile => filename(infile) }
225    end
226  end
227
228  class Command::Gperf < Command
229    attr_accessor :compile_options
230
231    def initialize(build)
232      super
233      @command = 'gperf'
234      @compile_options = '-L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k"1,3,$" %{infile} > %{outfile}'
235    end
236
237    def run(outfile, infile)
238      FileUtils.mkdir_p File.dirname(outfile)
239      _pp "GPERF", infile.relative_path, outfile.relative_path
240      _run compile_options, { :outfile => filename(outfile) , :infile => filename(infile) }
241    end
242  end
243
244  class Command::Git < Command
245    attr_accessor :flags
246    attr_accessor :clone_options, :pull_options, :checkout_options
247
248    def initialize(build)
249      super
250      @command = 'git'
251      @flags = %w[]
252      @clone_options = "clone %{flags} %{url} %{dir}"
253      @pull_options = "pull"
254      @checkout_options = "checkout %{checksum_hash}"
255    end
256
257    def run_clone(dir, url, _flags = [])
258      _pp "GIT", url, dir.relative_path
259      _run clone_options, { :flags => [flags, _flags].flatten.join(' '), :url => url, :dir => filename(dir) }
260    end
261
262    def run_pull(dir, url)
263      root = Dir.pwd
264      Dir.chdir dir
265      _pp "GIT PULL", url, dir.relative_path
266      _run pull_options
267      Dir.chdir root
268    end
269
270    def run_checkout(dir, checksum_hash)
271      root = Dir.pwd
272      Dir.chdir dir
273      _pp "GIT CHECKOUT", checksum_hash
274      _run checkout_options, { :checksum_hash => checksum_hash }
275      Dir.chdir root
276    end
277  end
278
279  class Command::Mrbc < Command
280    attr_accessor :compile_options
281
282    def initialize(build)
283      super
284      @command = nil
285      @compile_options = "-B%{funcname} -o-"
286    end
287
288    def run(out, infiles, funcname)
289      @command ||= @build.mrbcfile
290      infiles = [infiles].flatten
291      infiles.each do |f|
292        _pp "MRBC", f.relative_path, nil, :indent => 2
293      end
294      IO.popen("#{filename @command} #{@compile_options % {:funcname => funcname}} #{filename(infiles).join(' ')}", 'r+') do |io|
295        out.puts io.read
296      end
297      # if mrbc execution fail, drop the file
298      if $?.exitstatus != 0
299        File.delete(out.path)
300        exit(-1)
301      end
302    end
303  end
304
305  class Command::CrossTestRunner < Command
306    attr_accessor :runner_options
307    attr_accessor :verbose_flag
308    attr_accessor :flags
309
310    def initialize(build)
311      super
312      @command = nil
313      @runner_options = '%{flags} %{infile}'
314      @verbose_flag = ''
315      @flags = []
316    end
317
318    def run(testbinfile)
319      puts "TEST for " + @build.name
320      _run runner_options, { :flags => [flags, verbose_flag].flatten.join(' '), :infile => testbinfile }
321    end
322  end
323
324end
325