# coding=utf-8 require 'rbconfig' require 'fileutils' module CastOff module Compiler class CodeManager include RbConfig include CastOff::Util extend CastOff::Util attr_reader :signiture, :compilation_target def self.generate_signiture(unique_string) unique_string.gsub(/\.|\/|-|:/, "_") end def self.base_directory_name() p = generate_signiture(File.expand_path(__FILE__)) p.gsub(/_lib_ruby_gems_1_9_1_gems_/, '.').gsub(/_lib_cast_off_compile_code_manager_rb$/, '') end CastOffDir = "#{ENV["HOME"]}/.CastOff" FileUtils.mkdir(CastOffDir) unless File.exist?(CastOffDir) BaseDir = "#{CastOffDir}/#{base_directory_name()}" FileUtils.mkdir(BaseDir) unless File.exist?(BaseDir) @@program_name = File.basename($PROGRAM_NAME) @@program_sign = generate_signiture(@@program_name) CastOff::Compiler.class_eval do def program_name=(name) sign = CastOff::Compiler::CodeManager.generate_signiture(name) CastOff::Compiler::CodeManager.class_variable_set(:@@program_sign, sign) CastOff::Compiler::CodeManager.class_variable_set(:@@program_name, name) name end end def self.program_dir() dir = "#{BaseDir}/#{@@program_sign}" FileUtils.mkdir(dir) unless File.exist?(dir) dir end def self.clear() dir = program_dir() vlog("delete #{dir}") FileUtils.remove_entry_secure(dir) dir end def self.compiled_method_exist?(filepath, line_no) return false unless filepath && line_no >= 0 base_sign = generate_signiture("#{filepath}_#{line_no}") File.exist?("#{BaseDir}/#{@@program_sign}/#{base_sign}/loadable") end def create_dstdir() FileUtils.mkdir(@dstdir) unless File.exist?(@dstdir) FileUtils.touch(@lockpath) unless File.exist?(@lockpath) end def version_up() v = File.exist?(@versionpath) ? File.read(@versionpath).to_i : @version bug() if v < 0 v = [@version, v].max + 1 dlog("version: #{@version} => #{v}") File.open(@versionpath, 'w'){|f| f.write(v)} set_path() end def fetch_version() bug() unless File.exist?(@dstdir) if File.exist?(@versionpath) version = File.read(@versionpath) else version = 0 end version.to_i end FILEPATH_LIMITED = CONFIG["host_os"].match(/mswin/) FILEPATH_LIMIT = 255 def check_length() return unless FILEPATH_LIMITED raise(UnsupportedError.new(<<-EOS)) if @longpath.length > FILEPATH_LIMIT Failed to generate signiture for #{@filepath}:#{@line_no}. Signiture is generated from filepath and line_no. Max length of signiture is #{FILEPATH_LIMIT} in this environment. EOS end def initialize(filepath, line_no) @filepath = filepath @line_no = line_no @compilation_target = nil set_path() end def set_path() base_sign = CodeManager.generate_signiture("#{@filepath}_#{@line_no}") dir = CodeManager.program_dir() @dstdir = "#{dir}/#{base_sign}" @lockpath = "#{@dstdir}/lock" @versionfile = "version" @versionpath = "#{@dstdir}/#{@versionfile}" create_dstdir() @version = fetch_version() @signiture = "#{base_sign}_ver#{@version}" @dstfile = "#{@signiture}.c" @depfile = "#{@signiture}.mdep" @deppath = "#{@dstdir}/#{@depfile}" @conffile = "#{@signiture}.conf" @base_configuration_path = "#{@dstdir}/#{base_sign}.base.conf" @annotation_path = "#{@dstdir}/#{@signiture}.ann" @development_mark_file = "development" @development_mark_path = "#{@dstdir}/#{@development_mark_file}" @loadable_mark_path = "#{@dstdir}/loadable" @dstbin = "#{@dstdir}/#{@signiture}.#{CONFIG['DLEXT']}" @longpath = @base_configuration_path # FIXME check_length() end class CompilationTarget include CastOff::Util attr_reader :target_object, :method_id def initialize(target, mid, singleton) @target_object = target @method_id = mid @singleton_p = singleton bug() unless @method_id.is_a?(Symbol) bug() unless @singleton_p == true || @singleton_p == false end def singleton_method? @singleton_p end end def compilation_target_is_a(target, mid, singleton) @compilation_target = CompilationTarget.new(target, mid, singleton) end def do_atomically() bug() unless block_given? File.open(@lockpath, "w") do |f| f.flock(File::LOCK_EX) yield end end def target_file_updated? not (File.exist?(@dstbin) && File.mtime(@filepath).tv_sec < File.mtime(@dstbin).tv_sec) end def compiled_binary() @dstbin end def remove_binary_if_raised(&b) begin b.call() rescue => e FileUtils.remove_entry_secure(@dstbin) if File.exist?(@dstbin) raise(e) end end def compile_c_source(c_source, conf, dep) base = load_base_configuration() FileUtils.remove_entry_secure(@dstdir, true) create_dstdir() File.open("#{@dstdir}/#{@dstfile}", 'w'){|f| f.write(c_source)} gen_makefile(@dstdir, @dstfile) gen_header_files(@dstdir) Dir.chdir(@dstdir) do File.open(@dstfile, 'wb:us-ascii') do |f| f.write(c_source.dup.force_encoding('US-ASCII')) end if CONFIG["host_os"].match(/mswin/) # windows makecmd = 'nmake' else # linux, freebsd, solaris makecmd = 'make' end log = `#{makecmd} 2>&1` if $? != 0 dlog(c_source) raise(CompileError.new(<<-EOS)) Failed to compile c source: status = (#{$?}) ---------- Error ---------- #{log} EOS else dlog(c_source, 2) dlog(log, 2) end remove_binary_if_raised do File.open(@conffile, 'wb:us-ascii') do |f| bug() unless conf conf.dump(f) end File.open(@depfile, 'wb:us-ascii') do |f| bug() unless dep.instance_of?(Dependency) dep.dump(f) end end bug() unless @version.is_a?(Integer) File.open(@versionfile, 'w'){|f| f.write(@version)} save_base_configuration(base) if base end end def last_configuration_enabled_development? File.exist?(@development_mark_path) end def dump_development_mark(conf) bug() unless conf FileUtils.touch(@development_mark_path) if conf.development? end def loadable() FileUtils.touch(@loadable_mark_path) end def save_base_configuration(conf) begin File.open(@base_configuration_path, 'wb:us-ascii') do |f| bug() unless conf.instance_of?(Configuration) conf.dump(f) end rescue => e vlog("failed to update base configuration: #{e}") end end def clear_base_configuration() return unless File.exist?(@base_configuration_path) FileUtils.remove_entry_secure(@base_configuration_path) end def load_base_configuration() return nil unless File.exist?(@base_configuration_path) conf_str = File.open(@base_configuration_path, 'rb:us-ascii').read() conf_str.untaint # FIXME Configuration.load(conf_str) end def load_last_configuration() return nil unless File.exist?("#{@dstdir}/#{@conffile}") exist_conf_str = File.open("#{@dstdir}/#{@conffile}", 'rb:us-ascii').read() # exist_conf_str が tainted であるため、Marshal.load で読み込んだ class も tainted になってしまう # RDoc だと、それのせいで Insecure: can't modify array となる exist_conf_str.untaint # FIXME Configuration.load(exist_conf_str) end def save_annotation(ann) bug() unless ann.is_a?(Hash) remove_binary_if_raised do File.open("#{@annotation_path}", 'wb:us-ascii'){|f| Marshal.dump(ann, f)} end end def load_annotation() return nil unless File.exist?(@annotation_path) ann_str = File.open(@annotation_path, 'rb:us-ascii').read() ann_str.untaint # FIXME Marshal.load(ann_str) end def load_dependency() raise(LoadError.new("method dependency file is not exist")) unless File.exist?(@deppath) str = File.open(@deppath, 'rb:us-ascii').read() str.untaint # FIXME Dependency.load(str) end def gen_makefile(dir, file) Dir.chdir(dir) do File.open('extconf.rb', 'wb:us-ascii') do |f| f.write <<-EOS require 'mkmf' create_makefile("#{File.basename(file, ".c")}") EOS end runruby = CONFIG["prefix"] + "/bin/" + CONFIG["ruby_install_name"] dlog(`#{runruby} extconf.rb`, 2) bug("failed to generate extconf.rb: status = (#{$?})") if $? != 0 end end def gen_header_files(dir) Dir.chdir(dir) do Headers.each_pair do |k, v| File.open(k, 'wb:binary'){|f| f.write(v)} FileUtils.touch("insns.inc") end end end end end end