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