1require 'forwardable' 2 3module MRuby 4 class Command 5 include Rake::DSL 6 extend Forwardable 7 def_delegators :@build, :filename, :objfile, :libfile, :exefile 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 def shellquote(s) 28 if ENV['OS'] == 'Windows_NT' 29 "\"#{s}\"" 30 else 31 "#{s}" 32 end 33 end 34 35 NotFoundCommands = {} 36 37 private 38 def _run(options, params={}) 39 return sh command + ' ' + ( options % params ) if NotFoundCommands.key? @command 40 begin 41 sh build.filename(command) + ' ' + ( options % params ) 42 rescue RuntimeError 43 NotFoundCommands[@command] = true 44 _run options, params 45 end 46 end 47 end 48 49 class Command::Compiler < Command 50 attr_accessor :flags, :include_paths, :defines, :source_exts 51 attr_accessor :compile_options, :option_define, :option_include_path, :out_ext 52 attr_accessor :cxx_compile_flag, :cxx_exception_flag, :cxx_invalid_flags 53 54 def initialize(build, source_exts=[]) 55 super(build) 56 @command = ENV['CC'] || 'cc' 57 @flags = [ENV['CFLAGS'] || []] 58 @source_exts = source_exts 59 @include_paths = ["#{MRUBY_ROOT}/include"] 60 @defines = %w() 61 @option_include_path = %q[-I"%s"] 62 @option_define = %q[-D"%s"] 63 @compile_options = %q[%{flags} -o "%{outfile}" -c "%{infile}"] 64 @cxx_invalid_flags = [] 65 end 66 67 alias header_search_paths include_paths 68 def search_header_path(name) 69 header_search_paths.find do |v| 70 File.exist? build.filename("#{v}/#{name}").sub(/^"(.*)"$/, '\1') 71 end 72 end 73 74 def search_header(name) 75 path = search_header_path name 76 path && build.filename("#{path}/#{name}").sub(/^"(.*)"$/, '\1') 77 end 78 79 def all_flags(_defines=[], _include_paths=[], _flags=[]) 80 define_flags = [defines, _defines].flatten.map{ |d| option_define % d } 81 include_path_flags = [include_paths, _include_paths].flatten.map do |f| 82 option_include_path % filename(f) 83 end 84 [flags, define_flags, include_path_flags, _flags].flatten.join(' ') 85 end 86 87 def run(outfile, infile, _defines=[], _include_paths=[], _flags=[]) 88 mkdir_p File.dirname(outfile) 89 _pp "CC", infile.relative_path, outfile.relative_path 90 _run compile_options, { :flags => all_flags(_defines, _include_paths, _flags), 91 :infile => filename(infile), :outfile => filename(outfile) } 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 131 # 132 # === Example of +.d+ file 133 # 134 # ==== Without <tt>-MP</tt> compiler flag 135 # 136 # /build/host/src/array.o: \ 137 # /src/array.c \ 138 # /include/mruby/common.h \ 139 # /include/mruby/value.h \ 140 # /src/value_array.h 141 # 142 # ==== With <tt>-MP</tt> compiler flag 143 # 144 # /build/host/src/array.o: \ 145 # /src/array.c \ 146 # /include/mruby/common.h \ 147 # /include/mruby/value.h \ 148 # /src/value_array.h 149 # 150 # /include/mruby/common.h: 151 # 152 # /include/mruby/value.h: 153 # 154 # /src/value_array.h: 155 # 156 def get_dependencies(file) 157 file = file.ext('d') unless File.extname(file) == '.d' 158 deps = [] 159 if File.exist?(file) 160 File.foreach(file){|line| deps << $1 if /^ +(.*?)(?: *\\)?$/ =~ line} 161 deps.uniq! 162 end 163 deps << MRUBY_CONFIG 164 end 165 end 166 167 class Command::Linker < Command 168 attr_accessor :flags, :library_paths, :flags_before_libraries, :libraries, :flags_after_libraries 169 attr_accessor :link_options, :option_library, :option_library_path 170 171 def initialize(build) 172 super 173 @command = ENV['LD'] || 'ld' 174 @flags = (ENV['LDFLAGS'] || []) 175 @flags_before_libraries, @flags_after_libraries = [], [] 176 @libraries = [] 177 @library_paths = [] 178 @option_library = %q[-l"%s"] 179 @option_library_path = %q[-L"%s"] 180 @link_options = %Q[%{flags} -o "%{outfile}" %{objs} %{flags_before_libraries} %{libs} %{flags_after_libraries}] 181 end 182 183 def all_flags(_library_paths=[], _flags=[]) 184 library_path_flags = [library_paths, _library_paths].flatten.map do |f| 185 option_library_path % filename(f) 186 end 187 [flags, library_path_flags, _flags].flatten.join(' ') 188 end 189 190 def library_flags(_libraries) 191 [libraries, _libraries].flatten.map{ |d| option_library % d }.join(' ') 192 end 193 194 def run(outfile, objfiles, _libraries=[], _library_paths=[], _flags=[], _flags_before_libraries=[], _flags_after_libraries=[]) 195 mkdir_p File.dirname(outfile) 196 library_flags = [libraries, _libraries].flatten.map { |d| option_library % d } 197 198 _pp "LD", outfile.relative_path 199 _run link_options, { :flags => all_flags(_library_paths, _flags), 200 :outfile => filename(outfile) , :objs => filename(objfiles).map{|f| %Q["#{f}"]}.join(' '), 201 :flags_before_libraries => [flags_before_libraries, _flags_before_libraries].flatten.join(' '), 202 :flags_after_libraries => [flags_after_libraries, _flags_after_libraries].flatten.join(' '), 203 :libs => library_flags.join(' ') } 204 end 205 end 206 207 class Command::Archiver < Command 208 attr_accessor :archive_options 209 210 def initialize(build) 211 super 212 @command = ENV['AR'] || 'ar' 213 @archive_options = 'rs "%{outfile}" %{objs}' 214 end 215 216 def run(outfile, objfiles) 217 mkdir_p File.dirname(outfile) 218 _pp "AR", outfile.relative_path 219 _run archive_options, { :outfile => filename(outfile), :objs => filename(objfiles).map{|f| %Q["#{f}"]}.join(' ') } 220 end 221 end 222 223 class Command::Yacc < Command 224 attr_accessor :compile_options 225 226 def initialize(build) 227 super 228 @command = 'bison' 229 @compile_options = %q[-o "%{outfile}" "%{infile}"] 230 end 231 232 def run(outfile, infile) 233 mkdir_p File.dirname(outfile) 234 _pp "YACC", infile.relative_path, outfile.relative_path 235 _run compile_options, { :outfile => filename(outfile) , :infile => filename(infile) } 236 end 237 end 238 239 class Command::Gperf < Command 240 attr_accessor :compile_options 241 242 def initialize(build) 243 super 244 @command = 'gperf' 245 @compile_options = %q[-L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"] 246 end 247 248 def run(outfile, infile) 249 mkdir_p File.dirname(outfile) 250 _pp "GPERF", infile.relative_path, outfile.relative_path 251 _run compile_options, { :outfile => filename(outfile) , :infile => filename(infile) } 252 end 253 end 254 255 class Command::Git < Command 256 attr_accessor :flags 257 attr_accessor :clone_options, :pull_options, :checkout_options, :checkout_detach_options, :reset_options 258 259 def initialize(build) 260 super 261 @command = 'git' 262 @flags = %w[] 263 @clone_options = "clone %{flags} %{url} %{dir}" 264 @pull_options = "--git-dir %{repo_dir}/.git --work-tree %{repo_dir} pull" 265 @checkout_options = "--git-dir %{repo_dir}/.git --work-tree %{repo_dir} checkout %{checksum_hash}" 266 @checkout_detach_options = "--git-dir %{repo_dir}/.git --work-tree %{repo_dir} checkout --detach %{checksum_hash}" 267 @reset_options = "--git-dir %{repo_dir}/.git --work-tree %{repo_dir} reset %{checksum_hash}" 268 end 269 270 def run_clone(dir, url, _flags = []) 271 _pp "GIT", url, dir.relative_path 272 _run clone_options, { :flags => [flags, _flags].flatten.join(' '), :url => shellquote(url), :dir => shellquote(filename(dir)) } 273 end 274 275 def run_pull(dir, url) 276 _pp "GIT PULL", url, dir.relative_path 277 _run pull_options, { :repo_dir => shellquote(dir) } 278 end 279 280 def run_checkout(dir, checksum_hash) 281 _pp "GIT CHECKOUT", dir, checksum_hash 282 _run checkout_options, { :checksum_hash => checksum_hash, :repo_dir => shellquote(dir) } 283 end 284 285 def run_checkout_detach(dir, checksum_hash) 286 _pp "GIT CHECKOUT DETACH", dir, checksum_hash 287 _run checkout_detach_options, { :checksum_hash => checksum_hash, :repo_dir => shellquote(dir) } 288 end 289 290 def run_reset_hard(dir, checksum_hash) 291 _pp "GIT RESET", dir, checksum_hash 292 _run reset_options, { :checksum_hash => checksum_hash, :repo_dir => shellquote(dir) } 293 end 294 295 def commit_hash(dir) 296 `#{@command} --git-dir #{shellquote(dir +'/.git')} --work-tree #{shellquote(dir)} rev-parse --verify HEAD`.strip 297 end 298 299 def current_branch(dir) 300 `#{@command} --git-dir #{shellquote(dir + '/.git')} --work-tree #{shellquote(dir)} rev-parse --abbrev-ref HEAD`.strip 301 end 302 end 303 304 class Command::Mrbc < Command 305 attr_accessor :compile_options 306 307 def initialize(build) 308 super 309 @command = nil 310 @compile_options = "-B%{funcname} -o-" 311 end 312 313 def run(out, infiles, funcname) 314 @command ||= @build.mrbcfile 315 infiles = [infiles].flatten 316 infiles.each do |f| 317 _pp "MRBC", f.relative_path, nil, :indent => 2 318 end 319 cmd = %Q["#{filename @command}" #{@compile_options % {:funcname => funcname}} #{filename(infiles).map{|f| %Q["#{f}"]}.join(' ')}] 320 puts cmd if Rake.verbose 321 IO.popen(cmd, 'r+') do |io| 322 out.puts io.read 323 end 324 # if mrbc execution fail, drop the file 325 if $?.exitstatus != 0 326 File.delete(out.path) 327 exit(-1) 328 end 329 end 330 end 331 332 class Command::CrossTestRunner < Command 333 attr_accessor :runner_options 334 attr_accessor :verbose_flag 335 attr_accessor :flags 336 337 def initialize(build) 338 super 339 @command = nil 340 @runner_options = '%{flags} %{infile}' 341 @verbose_flag = '' 342 @flags = [] 343 end 344 345 def run(testbinfile) 346 puts "TEST for " + @build.name 347 _run runner_options, { :flags => [flags, verbose_flag].flatten.join(' '), :infile => testbinfile } 348 end 349 end 350 351end 352