Skip to content
Browse files

Reworked FFI generators.

  • Loading branch information...
1 parent 11d8e4b commit 0db919c0e637fbb0c156d86f67254198768f440c @brixen brixen committed with Brian Ford
View
172 lib/ffi/const_generator.rb
@@ -1,172 +0,0 @@
-require 'tempfile'
-require 'open3'
-
-module FFI
-
- ##
- # ConstGenerator turns C constants into ruby values.
-
- class ConstGenerator
-
- attr_reader :constants
-
- ##
- # Creates a new constant generator that uses +prefix+ as a name, and an
- # options hash.
- #
- # The only option is :required, which if set to true raises an error if a
- # constant you have requested was not found.
- #
- # When passed a block, #calculate is automatically called at the end of
- # the block, otherwise you must call it yourself.
-
- def initialize(prefix = nil, options = {})
- @includes = []
- @constants = {}
- @prefix = prefix
-
- @required = options[:required]
-
- if block_given? then
- yield self
- calculate
- end
- end
-
- def [](name)
- @constants[name].value
- end
-
- ##
- # Request the value for C constant +name+. +format+ is a printf format
- # string to print the value out, and +cast+ is a C cast for the value.
- # +ruby_name+ allows you to give the constant an alternate ruby name for
- # #to_ruby. +converter+ or +converter_proc+ allow you to convert the
- # value from a string to the appropriate type for #to_ruby.
-
- def const(name, format = nil, cast = '', ruby_name = nil, converter = nil,
- &converter_proc)
- format ||= '%d'
- cast ||= ''
-
- if converter_proc and converter then
- raise ArgumentError, "Supply only converter or converter block"
- end
-
- converter = converter_proc if converter.nil?
-
- const = Constant.new name, format, cast, ruby_name, converter
- @constants[name.to_s] = const
- return const
- end
-
- def calculate
- binary = File.join Dir.tmpdir, "rb_const_gen_bin_#{Process.pid}"
-
- Tempfile.open("#{@prefix}.const_generator") do |f|
- f.puts "#include <stdio.h>"
-
- @includes.each do |inc|
- f.puts "#include <#{inc}>"
- end
-
- f.puts "#include <stddef.h>\n\n"
- f.puts "int main(int argc, char **argv)\n{"
-
- @constants.each_value do |const|
- f.puts <<-EOF
- #ifdef #{const.name}
- printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
- #endif
- EOF
- end
-
- f.puts "\n\treturn 0;\n}"
- f.flush
-
- output = `gcc -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`
-
- unless $?.success? then
- output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
- raise "Compilation error generating constants #{@prefix}:\n#{output}"
- end
- end
-
- output = `#{binary}`
- File.unlink binary
-
- output.each_line do |line|
- line =~ /^(\S+)\s(.*)$/
- const = @constants[$1]
- const.value = $2
- end
-
- missing_constants = @constants.select do |name, constant|
- constant.value.nil?
- end.map { |name,| name }
-
- if @required and not missing_constants.empty? then
- raise "Missing required constants for #{@prefix}: #{missing_constants.join ', '}"
- end
- end
-
- def dump_constants(io)
- @constants.each do |name, constant|
- name = [@prefix, name].join '.'
- io.puts "#{name} = #{constant.converted_value}"
- end
- end
-
- ##
- # Outputs values for discovered constants. If the constant's value was
- # not discovered it is not omitted.
-
- def to_ruby
- @constants.sort_by { |name,| name }.map do |name, constant|
- if constant.value.nil? then
- "# #{name} not available"
- else
- constant.to_ruby
- end
- end.join "\n"
- end
-
- def include(i)
- @includes << i
- end
-
- end
-
- class ConstGenerator::Constant
-
- attr_reader :name, :format, :cast
- attr_accessor :value
-
- def initialize(name, format, cast, ruby_name = nil, converter=nil)
- @name = name
- @format = format
- @cast = cast
- @ruby_name = ruby_name
- @converter = converter
- @value = nil
- end
-
- def converted_value
- if @converter
- @converter.call(@value)
- else
- @value
- end
- end
-
- def ruby_name
- @ruby_name || @name
- end
-
- def to_ruby
- "#{ruby_name} = #{converted_value}"
- end
-
- end
-
-end
View
87 lib/ffi/file_processor.rb
@@ -0,0 +1,87 @@
+require File.expand_path('../generators', __FILE__)
+
+module FFI
+
+ # Processes a file containing Ruby code with blocks of FFI definitions
+ # delimited by @@@. The blocks are replaced with Ruby code produced by
+ # running the FFI generators contained in the blocks. For example:
+ #
+ # module Something
+ # @@@
+ # constants do |c|
+ # c.include 'somefile.h'
+ #
+ # c.const 'MAX'
+ # c.const 'MIN'
+ # end
+ # @@@
+ # end
+ #
+ # would be converted to:
+ #
+ # module Something
+ # MAX = 1
+ # MIN = 2
+ # end
+ #
+ # assuming that
+ #
+ # #define MAX 1
+ # #define MIN 2
+ #
+ # was contained in the file 'something.h'.
+
+ class FileProcessor
+
+ def initialize(ffi_name, rb_name)
+ @name = File.basename rb_name, '.rb'
+
+ definitions = File.read ffi_name
+
+ replacement = definitions.gsub(/^( *)@@@(.*?)@@@/m) do
+ @constants = []
+ @structs = []
+
+ indent = $1
+ line_count = $2.count("\n") + 1
+
+ instance_eval $2
+
+ lines = []
+ @constants.each { |c| lines << c.to_ruby }
+ @structs.each { |s| lines << s.generate_layout }
+
+ # expand multiline blocks
+ lines = lines.join("\n").split "\n"
+ lines = lines.map { |line| indent + line }
+
+ # preserve source line numbers in output
+ padding = line_count - lines.length
+ lines += [nil] * padding if padding >= 0
+
+ lines.join "\n"
+ end
+
+ File.open rb_name, 'wb' do |f|
+ f.puts "# This file is generated #{self.class} from #{ffi_name}."
+ f.puts
+ f.puts replacement
+ end
+ end
+
+ def constants(options={}, &block)
+ @constants << FFI::ConstGenerator.new(@name, options, &block)
+ end
+
+ def struct(&block)
+ @structs << FFI::StructGenerator.new(@name, &block)
+ end
+
+ ##
+ # Utility converter for constants
+
+ def to_s
+ proc { |obj| obj.to_s.inspect }
+ end
+ end
+end
View
56 lib/ffi/generator.rb
@@ -1,58 +1,12 @@
+require File.expand_path('../file_processor', __FILE__)
+
module FFI
class Generator
-
def initialize(ffi_name, rb_name)
- @ffi_name = ffi_name
- @rb_name = rb_name
-
- @name = File.basename rb_name, '.rb'
-
- file = File.read @ffi_name
-
- new_file = file.gsub(/^( *)@@@(.*?)@@@/m) do
- @constants = []
- @structs = []
-
- indent = $1
- original_lines = $2.count "\n"
-
- instance_eval $2
-
- new_lines = []
- @constants.each { |c| new_lines << c.to_ruby }
- @structs.each { |s| new_lines << s.generate_layout }
-
- new_lines = new_lines.join("\n").split "\n" # expand multiline blocks
- new_lines = new_lines.map { |line| indent + line }
-
- padding = original_lines - new_lines.length
- new_lines += [nil] * padding if padding >= 0
+ STDERR.puts "FFI::Generator is deprecated. Use FFI::FileProcessor"
+ STDERR.puts "Called from: #{caller(1).first}"
- new_lines.join "\n"
- end
-
- open @rb_name, 'w' do |f|
- f.puts "# This file is generated by rake. Do not edit."
- f.puts
- f.puts new_file
- end
- end
-
- def constants(options = {}, &block)
- @constants << FFI::ConstGenerator.new(@name, options, &block)
- end
-
- def struct(&block)
- @structs << FFI::StructGenerator.new(@name, &block)
+ FileProcessor.new ffi_name, rb_name
end
-
- ##
- # Utility converter for constants
-
- def to_s
- proc { |obj| obj.to_s.inspect }
- end
-
end
end
-
View
127 lib/ffi/generators.rb
@@ -0,0 +1,127 @@
+# We need to load during build when the Rubinius lib dir cannot be in
+# $LOAD_PATH because the bootstrap ruby will load other lib contents.
+base = File.expand_path "../", __FILE__
+
+require base + '/generators/structures'
+require base + '/generators/constants'
+require base + '/generators/types'
+
+module FFI
+ module Generators
+
+ ##
+ # BodyGuard does your file system dirty work and cleans up after any
+ # fallout.
+
+ class BodyGuardError < Exception; end
+
+ class BodyGuard
+ def initialize(subject, label, platform)
+ @subject = subject
+ @label = label.gsub(/\W/, '-')
+ @platform = platform
+ end
+
+ ##
+ # Orchestrates a workflow by querying the subject at each stage and cleans
+ # up if problems arise before raising an exception.
+ #
+ # Expects the subject to respond to the following methods:
+ #
+ # #source io
+ # Where io is an IO instance used to create the source for later
+ # stages.
+ #
+ # #prepare name, target
+ # Where name is the source file name and target is the file that would
+ # be created by the prepare process. The method should return the
+ # command to run.
+ #
+ # #prepare_failed
+ # The method should return the error message for #raise to which will
+ # be appended the output of running the command returned by #prepare.
+ #
+ # #process target
+ # Where target is the same as passed to #prepare. The method should
+ # return the command to run. If no further options or changes are
+ # needed, #process should just return target.
+ #
+ # #process_failed
+ # The method should return the error message for #raise to which will
+ # be appended the output of running the command returned by #process.
+ #
+ # The #perform method returns the result of running the command returned
+ # by the #process method.
+
+ def perform
+ begin
+ name = "rbx-ffi-generators-#{@label}"
+ source = File.expand_path name + @platform.source_ext
+ target = File.expand_path name + @platform.executable_ext
+
+ File.open source, "wb" do |f|
+ @subject.source f
+ end
+
+ if preparer = @subject.prepare(source, target)
+ handle preparer, :prepare_failed
+ else
+ target = source
+ end
+
+ processor = @subject.process target
+ return handle(processor, :process_failed)
+ ensure
+ remove source, target
+ end
+ end
+
+ def handle(command, failure)
+ result = `#{command}`
+
+ unless $?.success?
+ result = result.split("\n").map { |l| "\t#{l}" }.join "\n"
+ msg = "#{@subject.send failure}:\n#{result}"
+ raise BodyGuardError, msg
+ end
+
+ result
+ end
+
+ def remove(*names)
+ names.each do |name|
+ File.delete name if File.exists? name
+ end
+ end
+ end
+
+ class Platform
+ # TODO: Make these configurable to enable cross-compiling
+
+ def initialize(kind=:c)
+ @kind = kind
+ end
+
+ def source_ext
+ case @kind
+ when :c
+ ".c"
+ when :cpp
+ ".cpp"
+ end
+ end
+
+ def executable_ext
+ windows? ? ".exe" : ""
+ end
+
+ def defines
+ "-D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
+ end
+
+ def windows?
+ RUBY_PLATFORM =~ /mswin|mingw/
+ end
+ end
+ end
+end
View
173 lib/ffi/generators/constants.rb
@@ -0,0 +1,173 @@
+module FFI
+ module Generators
+ ##
+ # Constants turns C constants into ruby values.
+
+ class Constants
+
+ class Constant
+ attr_reader :name, :format, :cast
+ attr_accessor :value
+
+ def initialize(name, format, cast, ruby_name=nil, converter=nil)
+ @name = name
+ @format = format
+ @cast = cast
+ @ruby_name = ruby_name
+ @converter = converter
+ @value = nil
+ end
+
+ def value?
+ @value != nil
+ end
+
+ def converted_value
+ @converter ? @converter.call(@value) : @value
+ end
+
+ def ruby_name
+ @ruby_name || @name
+ end
+
+ def to_ruby
+ "#{ruby_name} = #{converted_value}"
+ end
+ end
+
+
+ attr_reader :constants
+
+ ##
+ # Creates a new constant generator that uses +prefix+ as a name, and an
+ # options hash.
+ #
+ # The only option is :required, which if set to true raises an error if a
+ # constant you have requested was not found.
+ #
+ # When passed a block, #calculate is automatically called at the end of
+ # the block, otherwise you must call it yourself.
+
+ def initialize(prefix=nil, options={})
+ @includes = []
+ @constants = {}
+ @prefix = prefix
+ @platform = Platform.new
+
+ @required = options[:required]
+
+ if block_given?
+ yield self
+ calculate
+ end
+ end
+
+ def [](name)
+ @constants[name].value
+ end
+
+ ##
+ # Request the value for C constant +name+. +format+ is a printf format
+ # string to print the value out, and +cast+ is a C cast for the value.
+ # +ruby_name+ allows you to give the constant an alternate ruby name for
+ # #to_ruby. +converter+ or +converter_proc+ allow you to convert the
+ # value from a string to the appropriate type for #to_ruby.
+
+ def const(name, format=nil, cast=nil, ruby_name=nil, converter=nil, &block)
+ format ||= '%ld'
+ cast ||= ''
+
+ if block and converter
+ raise ArgumentError, "Supply only converter or converter block"
+ end
+
+ converter = block if converter.nil?
+
+ const = Constant.new name, format, cast, ruby_name, converter
+ @constants[name.to_s] = const
+ return const
+ end
+
+ def source(io)
+ io.puts "#include <stdio.h>"
+
+ @includes.each do |inc|
+ io.puts "#include <#{inc}>"
+ end
+
+ io.puts "#include <stddef.h>\n\n"
+ io.puts "int main(int argc, char **argv)\n{"
+
+ @constants.each_value do |const|
+ io.puts <<-EOF
+ #ifdef #{const.name}
+ printf("#{const.name} #{const.format}\\n", (long)#{const.cast}#{const.name});
+ #endif
+ EOF
+ end
+
+ io.puts "\n\treturn 0;\n}"
+ end
+
+ def prepare(name, target)
+ "gcc #{@platform.defines} -x c -Wall -Werror #{name} -o #{target} 2>&1"
+ end
+
+ def prepare_failed
+ "Compilation error generating constants #{@prefix}"
+ end
+
+ def process(target)
+ target
+ end
+
+ def process_failed
+ "Error generating constants #{@prefix}"
+ end
+
+ def calculate
+ output = BodyGuard.new(self, @prefix, @platform).perform
+
+ output.each_line do |line|
+ line =~ /^(\S+)\s(.*)$/
+ const = @constants[$1]
+ const.value = $2
+ end
+
+ missing_constants = @constants.reject { |_, c| c.value? }.keys
+
+ if @required and not missing_constants.empty?
+ raise "Missing required constants for #{@prefix}: #{missing_constants.join ', '}"
+ end
+ end
+
+ def write_constants(io)
+ @constants.each do |name, constant|
+ io.print @prefix, "."
+ io.puts constant.to_ruby
+ end
+ end
+
+ ##
+ # Outputs values for discovered constants. If the constant's value was
+ # not discovered it is not omitted.
+
+ def to_ruby
+ @constants.sort_by { |name,| name }.map do |name, constant|
+ if constant.value?
+ constant.to_ruby
+ else
+ "# #{name} not defined"
+ end
+ end.join "\n"
+ end
+
+ def include(i)
+ @includes << i
+ end
+ end
+
+ end
+
+ ConstGenerator = Generators::Constants
+end
View
193 lib/ffi/generators/structures.rb
@@ -0,0 +1,193 @@
+module FFI
+ module Generators
+ ##
+ # Generates an FFI Struct layout.
+ #
+ # Given the @@@ portion in:
+ #
+ # module Zlib::ZStream < FFI::Struct
+ # @@@
+ # name "struct z_stream_s"
+ # include "zlib.h"
+ #
+ # field :next_in, :pointer
+ # field :avail_in, :uint
+ # field :total_in, :ulong
+ #
+ # # ...
+ # @@@
+ # end
+ #
+ # Structures will create the layout:
+ #
+ # layout :next_in, :pointer, 0,
+ # :avail_in, :uint, 4,
+ # :total_in, :ulong, 8,
+ # # ...
+ #
+ # StructGenerator does its best to pad the layout it produces to preserve
+ # line numbers. Place the struct definition as close to the top of the file
+ # for best results.
+
+ class Structures
+
+ ##
+ # A field in a Struct.
+
+ class Field
+
+ attr_reader :name
+ attr_reader :type
+ attr_reader :offset
+ attr_accessor :size
+
+ def initialize(name, type)
+ @name = name
+ @type = type
+ @offset = nil
+ @size = nil
+ end
+
+ def offset=(o)
+ @offset = o
+ end
+
+ def to_config(name)
+ buf = []
+ buf << "rbx.platform.#{name}.#{@name}.offset = #{@offset}"
+ buf << "rbx.platform.#{name}.#{@name}.size = #{@size}"
+ buf << "rbx.platform.#{name}.#{@name}.type = #{@type}" if @type
+ buf
+ end
+ end
+
+
+ attr_accessor :size
+ attr_reader :fields
+
+ def initialize(name)
+ @name = name
+ @struct_name = nil
+ @includes = []
+ @fields = []
+ @found = false
+ @size = nil
+ @platform = Platform.new
+
+ if block_given? then
+ yield self
+ calculate
+ end
+ end
+
+ def source(io)
+ io.puts "#include <stdio.h>"
+
+ @includes.each do |inc|
+ io.puts "#include <#{inc}>"
+ end
+
+ io.puts "#include <stddef.h>\n\n"
+ io.puts "int main(int argc, char **argv)\n{"
+ io.puts " #{@struct_name} s;"
+ io.puts %[ printf("sizeof(#{@struct_name}) %u\\n", (unsigned int) sizeof(#{@struct_name}));]
+
+ @fields.each do |field|
+ io.puts <<-EOF
+ printf("#{field.name} %u %u\\n", (unsigned int) offsetof(#{@struct_name}, #{field.name}),
+ (unsigned int) sizeof(s.#{field.name}));
+ EOF
+ end
+
+ io.puts "\n return 0;\n}"
+ end
+
+ def prepare(name, target)
+ "gcc #{@platform.defines} -x c -Wall -Werror #{name} -o #{target} 2>&1"
+ end
+
+ def prepare_failed
+ "Compilation error generating struct #{@name} (#{@struct_name})"
+ end
+
+ def process(target)
+ target
+ end
+
+ def process_failed
+ "Error generating struct #{@name} (#{@struct_name})"
+ end
+
+ def calculate
+ raise "struct name not set" if @struct_name.nil?
+
+ output = BodyGuard.new(self, @struct_name, @platform).perform.split "\n"
+
+ sizeof = output.shift
+ unless @size
+ m = /\s*sizeof\([^)]+\) (\d+)/.match sizeof
+ @size = m[1]
+ end
+
+ line_no = 0
+ output.each do |line|
+ md = line.match(/.+ (\d+) (\d+)/)
+ @fields[line_no].offset = md[1].to_i
+ @fields[line_no].size = md[2].to_i
+
+ line_no += 1
+ end
+
+ @found = true
+ end
+
+ def field(name, type=nil)
+ field = Field.new(name, type)
+ @fields << field
+ return field
+ end
+
+ def found?
+ @found
+ end
+
+ def write_config(io)
+ io.puts "rbx.platform.#{@name}.sizeof = #{@size}"
+
+ @fields.each { |field| io.puts field.to_config(@name) }
+ end
+
+ def generate_layout
+ buf = ""
+
+ @fields.each_with_index do |field, i|
+ if buf.empty?
+ buf << "layout :#{field.name}, :#{field.type}, #{field.offset}"
+ else
+ buf << " :#{field.name}, :#{field.type}, #{field.offset}"
+ end
+
+ if i < @fields.length - 1
+ buf << ",\n"
+ end
+ end
+
+ buf
+ end
+
+ def get_field(name)
+ @fields.find { |f| name == f.name }
+ end
+
+ def include(i)
+ @includes << i
+ end
+
+ def name(n)
+ @struct_name = n
+ end
+ end
+ end
+
+ StructGenerator = Generators::Structures
+end
View
141 lib/ffi/generators/types.rb
@@ -0,0 +1,141 @@
+module FFI
+ module Generators
+ class Types
+
+ ##
+ # Maps different C types to the C type representations we use
+
+ TYPE_MAP = {
+ "char" => :char,
+ "signed char" => :char,
+ "__signed char" => :char,
+ "unsigned char" => :uchar,
+
+ "short" => :short,
+ "signed short" => :short,
+ "signed short int" => :short,
+ "unsigned short" => :ushort,
+ "unsigned short int" => :ushort,
+
+ "int" => :int,
+ "signed int" => :int,
+ "unsigned int" => :uint,
+
+ "long" => :long,
+ "long int" => :long,
+ "signed long" => :long,
+ "signed long int" => :long,
+ "unsigned long" => :ulong,
+ "unsigned long int" => :ulong,
+ "long unsigned int" => :ulong,
+
+ "long long" => :long_long,
+ "long long int" => :long_long,
+ "signed long long" => :long_long,
+ "signed long long int" => :long_long,
+ "unsigned long long" => :ulong_long,
+ "unsigned long long int" => :ulong_long,
+
+ "char *" => :string,
+ "void *" => :pointer,
+ }
+
+ def self.generate
+ new.generate
+ end
+
+ def initialize
+ @platform = Platform.new
+ end
+
+ def source(io)
+ io.puts "#include <sys/types.h>"
+ unless @platform.windows?
+ io.puts "#include <sys/socket.h>"
+ io.puts "#include <sys/resource.h>"
+ end
+ end
+
+ def prepare(name, target)
+ # we have nothing to do in this stage
+ end
+
+ def process(target)
+ "cpp #{@platform.defines} #{target}"
+ end
+
+ def process_failed
+ "Error generating C types"
+ end
+
+ def generate
+ typedefs = BodyGuard.new(self, "ffi_types_generator", @platform).perform
+
+ code = ""
+
+ typedefs.split(/\n/).each do |type|
+ # We only care about single line typedef
+ next unless type =~ /typedef/
+ # Ignore unions or structs
+ next if type =~ /union|struct/
+
+ # strip off the starting typedef and ending ;
+ type.gsub!(/^(.*typedef\s*)/, "")
+ type.gsub!(/\s*;\s*$/, "")
+
+ parts = type.split(/\s+/)
+ def_type = parts.join(" ")
+
+ # GCC does mapping with __attribute__ stuf, also see
+ # http://hal.cs.berkeley.edu/cil/cil016.html section 16.2.7. Problem
+ # with this is that the __attribute__ stuff can either occur before or
+ # after the new type that is defined...
+ if type =~ /__attribute__/
+ if parts.last =~ /__QI__|__HI__|__SI__|__DI__|__word__/
+
+ # In this case, the new type is BEFORE __attribute__ we need to
+ # find the final_type as the type before the part that starts with
+ # __attribute__
+ final_type = ""
+ parts.each do |p|
+ break if p =~ /__attribute__/
+ final_type = p
+ end
+ else
+ final_type = parts.pop
+ end
+
+ def_type = case type
+ when /__QI__/ then "char"
+ when /__HI__/ then "short"
+ when /__SI__/ then "int"
+ when /__DI__/ then "long long"
+ when /__word__/ then "long"
+ else "int"
+ end
+
+ def_type = "unsigned #{def_type}" if type =~ /unsigned/
+ else
+ final_type = parts.pop
+ def_type = parts.join(" ")
+ end
+
+ if type = TYPE_MAP[def_type]
+ code << "rbx.platform.typedef.#{final_type} = #{type}\n"
+ TYPE_MAP[final_type] = TYPE_MAP[def_type]
+ else
+ # Fallback to an ordinary pointer if we don't know the type
+ if def_type =~ /\*/
+ code << "rbx.platform.typedef.#{final_type} = pointer\n"
+ TYPE_MAP[final_type] = :pointer
+ end
+ end
+ end
+
+ code
+ end
+ end
+ end
+
+ TypesGenerator = Generators::Types
+end
View
190 lib/ffi/struct_generator.rb
@@ -1,190 +0,0 @@
-require 'tempfile'
-
-module FFI
-
- ##
- # Generates an FFI Struct layout.
- #
- # Given the @@@ portion in:
- #
- # module Zlib::ZStream < FFI::Struct
- # @@@
- # name "struct z_stream_s"
- # include "zlib.h"
- #
- # field :next_in, :pointer
- # field :avail_in, :uint
- # field :total_in, :ulong
- #
- # # ...
- # @@@
- # end
- #
- # StructGenerator will create the layout:
- #
- # layout :next_in, :pointer, 0,
- # :avail_in, :uint, 4,
- # :total_in, :ulong, 8,
- # # ...
- #
- # StructGenerator does its best to pad the layout it produces to preserve
- # line numbers. Place the struct definition as close to the top of the file
- # for best results.
-
- class StructGenerator
-
- attr_accessor :size
- attr_reader :fields
-
- def initialize(name)
- @name = name
- @struct_name = nil
- @includes = []
- @fields = []
- @found = false
- @size = nil
-
- if block_given? then
- yield self
- calculate
- end
- end
-
- def calculate
- binary = "rb_struct_gen_bin_#{Process.pid}"
-
- raise "struct name not set" if @struct_name.nil?
-
- Tempfile.open("#{@name}.struct_generator") do |f|
- f.puts "#include <stdio.h>"
-
- @includes.each do |inc|
- f.puts "#include <#{inc}>"
- end
-
- f.puts "#include <stddef.h>\n\n"
- f.puts "int main(int argc, char **argv)\n{"
- f.puts " #{@struct_name} s;"
- f.puts %[ printf("sizeof(#{@struct_name}) %u\\n", (unsigned int) sizeof(#{@struct_name}));]
-
- @fields.each do |field|
- f.puts <<-EOF
- printf("#{field.name} %u %u\\n", (unsigned int) offsetof(#{@struct_name}, #{field.name}),
- (unsigned int) sizeof(s.#{field.name}));
- EOF
- end
-
- f.puts "\n return 0;\n}"
- f.flush
-
- output = `gcc -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`
-
- unless $?.success? then
- @found = false
- output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
- raise "Compilation error generating struct #{@name} (#{@struct_name}):\n#{output}"
- end
- end
-
- output = `./#{binary}`.split "\n"
- File.unlink(binary)
-
- sizeof = output.shift
- unless @size
- m = /\s*sizeof\([^)]+\) (\d+)/.match sizeof
- @size = m[1]
- end
-
- line_no = 0
- output.each do |line|
- md = line.match(/.+ (\d+) (\d+)/)
- @fields[line_no].offset = md[1].to_i
- @fields[line_no].size = md[2].to_i
-
- line_no += 1
- end
-
- @found = true
- end
-
- def field(name, type=nil)
- field = Field.new(name, type)
- @fields << field
- return field
- end
-
- def found?
- @found
- end
-
- def dump_config(io)
- io.puts "rbx.platform.#{@name}.sizeof = #{@size}"
-
- @fields.each { |field| io.puts field.to_config(@name) }
- end
-
- def generate_layout
- buf = ""
-
- @fields.each_with_index do |field, i|
- if buf.empty?
- buf << "layout :#{field.name}, :#{field.type}, #{field.offset}"
- else
- buf << " :#{field.name}, :#{field.type}, #{field.offset}"
- end
-
- if i < @fields.length - 1
- buf << ",\n"
- end
- end
-
- buf
- end
-
- def get_field(name)
- @fields.find { |f| name == f.name }
- end
-
- def include(i)
- @includes << i
- end
-
- def name(n)
- @struct_name = n
- end
-
- end
-
- ##
- # A field in a Struct.
-
- class StructGenerator::Field
-
- attr_reader :name
- attr_reader :type
- attr_reader :offset
- attr_accessor :size
-
- def initialize(name, type)
- @name = name
- @type = type
- @offset = nil
- @size = nil
- end
-
- def offset=(o)
- @offset = o
- end
-
- def to_config(name)
- buf = []
- buf << "rbx.platform.#{name}.#{@name}.offset = #{@offset}"
- buf << "rbx.platform.#{name}.#{@name}.size = #{@size}"
- buf << "rbx.platform.#{name}.#{@name}.type = #{@type}" if @type
- buf
- end
-
- end
-
-end
-
View
124 lib/ffi/types_generator.rb
@@ -1,124 +0,0 @@
-require 'tempfile'
-
-module FFI
- class TypesGenerator
-
- ##
- # Maps different C types to the C type representations we use
-
- TYPE_MAP = {
- "char" => :char,
- "signed char" => :char,
- "__signed char" => :char,
- "unsigned char" => :uchar,
-
- "short" => :short,
- "signed short" => :short,
- "signed short int" => :short,
- "unsigned short" => :ushort,
- "unsigned short int" => :ushort,
-
- "int" => :int,
- "signed int" => :int,
- "unsigned int" => :uint,
-
- "long" => :long,
- "long int" => :long,
- "signed long" => :long,
- "signed long int" => :long,
- "unsigned long" => :ulong,
- "unsigned long int" => :ulong,
- "long unsigned int" => :ulong,
-
- "long long" => :long_long,
- "long long int" => :long_long,
- "signed long long" => :long_long,
- "signed long long int" => :long_long,
- "unsigned long long" => :ulong_long,
- "unsigned long long int" => :ulong_long,
-
- "char *" => :string,
- "void *" => :pointer,
- }
-
- def self.generate
- typedefs = nil
- Tempfile.open 'ffi_types_generator' do |io|
- io.puts <<-C
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/resource.h>
- C
-
- io.close
-
- typedefs = `cpp -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 #{io.path}`
- end
-
- code = ""
-
- typedefs.split(/\n/).each do |type|
- # We only care about single line typedef
- next unless type =~ /typedef/
- # Ignore unions or structs
- next if type =~ /union|struct/
-
- # strip off the starting typedef and ending ;
- type.gsub!(/^(.*typedef\s*)/, "")
- type.gsub!(/\s*;\s*$/, "")
-
- parts = type.split(/\s+/)
- def_type = parts.join(" ")
-
- # GCC does mapping with __attribute__ stuf, also see
- # http://hal.cs.berkeley.edu/cil/cil016.html section 16.2.7. Problem
- # with this is that the __attribute__ stuff can either occur before or
- # after the new type that is defined...
- if type =~ /__attribute__/
- if parts.last =~ /__QI__|__HI__|__SI__|__DI__|__word__/
-
- # In this case, the new type is BEFORE __attribute__ we need to
- # find the final_type as the type before the part that starts with
- # __attribute__
- final_type = ""
- parts.each do |p|
- break if p =~ /__attribute__/
- final_type = p
- end
- else
- final_type = parts.pop
- end
-
- def_type = case type
- when /__QI__/ then "char"
- when /__HI__/ then "short"
- when /__SI__/ then "int"
- when /__DI__/ then "long long"
- when /__word__/ then "long"
- else "int"
- end
-
- def_type = "unsigned #{def_type}" if type =~ /unsigned/
- else
- final_type = parts.pop
- def_type = parts.join(" ")
- end
-
- if type = TYPE_MAP[def_type]
- code << "rbx.platform.typedef.#{final_type} = #{type}\n"
- TYPE_MAP[final_type] = TYPE_MAP[def_type]
- else
- # Fallback to an ordinary pointer if we don't know the type
- if def_type =~ /\*/
- code << "rbx.platform.typedef.#{final_type} = pointer\n"
- TYPE_MAP[final_type] = :pointer
- end
- end
- end
-
- code
- end
-
- end
-end
-
View
9 rakelib/generator_task.rb
@@ -1,16 +1,13 @@
require 'rake'
require 'rake/tasklib'
-require 'tempfile'
-require 'lib/ffi/struct_generator'
-require 'lib/ffi/const_generator'
-require 'lib/ffi/generator'
+require 'lib/ffi/file_processor'
##
# Rake task that calculates C structs for FFI::Struct.
-class FFI::Generator::Task < Rake::TaskLib
+class FFI::FileProcessor::Task < Rake::TaskLib
def initialize(rb_names)
task :clean do rm_f rb_names end
@@ -21,7 +18,7 @@ def initialize(rb_names)
file rb_name => ffi_name do |t|
puts "Generating #{rb_name}..." if Rake.application.options.trace
- FFI::Generator.new ffi_name, rb_name
+ FFI::FileProcessor.new ffi_name, rb_name
end
end
end
View
76 rakelib/platform.rake
@@ -1,13 +1,11 @@
-require 'lib/ffi/struct_generator'
-require 'lib/ffi/const_generator'
-require 'lib/ffi/types_generator'
+require File.expand_path('../../lib/ffi/generators', __FILE__)
deps = %w[Rakefile rakelib/platform.rake] + Dir['lib/ffi/*rb']
file 'runtime/platform.conf' => deps do |task|
puts "GEN runtime/platform.conf"
- addrinfo = FFI::StructGenerator.new 'addrinfo' do |s|
+ addrinfo = FFI::Generators::Structures.new 'addrinfo' do |s|
s.include 'sys/socket.h'
s.include 'netdb.h'
s.name 'struct addrinfo'
@@ -21,7 +19,7 @@ file 'runtime/platform.conf' => deps do |task|
s.field :ai_next, :pointer
end
- dirent = FFI::StructGenerator.new 'dirent' do |s|
+ dirent = FFI::Generators::Structures.new 'dirent' do |s|
s.include "sys/types.h"
s.include "dirent.h"
s.name 'struct dirent'
@@ -30,14 +28,14 @@ file 'runtime/platform.conf' => deps do |task|
s.field :d_name, :char_array
end
- timeval = FFI::StructGenerator.new 'timeval' do |s|
+ timeval = FFI::Generators::Structures.new 'timeval' do |s|
s.include "sys/time.h"
s.name 'struct timeval'
s.field :tv_sec, :time_t
s.field :tv_usec, :suseconds_t
end
- sockaddr_in = FFI::StructGenerator.new 'sockaddr_in' do |s|
+ sockaddr_in = FFI::Generators::Structures.new 'sockaddr_in' do |s|
s.include "netinet/in.h"
s.include "fcntl.h"
s.include "sys/socket.h"
@@ -49,14 +47,14 @@ file 'runtime/platform.conf' => deps do |task|
s.field :sin_zero, :char_array
end
- sockaddr_un = FFI::StructGenerator.new 'sockaddr_un' do |s|
+ sockaddr_un = FFI::Generators::Structures.new 'sockaddr_un' do |s|
s.include "sys/un.h"
s.name 'struct sockaddr_un'
s.field :sun_family, :sa_family_t
s.field :sun_path, :char_array
end
- servent = FFI::StructGenerator.new 'servent' do |s|
+ servent = FFI::Generators::Structures.new 'servent' do |s|
s.include "netdb.h"
s.name 'struct servent'
s.field :s_name, :pointer
@@ -65,7 +63,7 @@ file 'runtime/platform.conf' => deps do |task|
s.field :s_proto, :pointer
end
- stat = FFI::StructGenerator.new 'stat' do |s|
+ stat = FFI::Generators::Structures.new 'stat' do |s|
s.include "sys/types.h"
s.include "sys/stat.h"
s.include "unistd.h"
@@ -86,7 +84,7 @@ file 'runtime/platform.conf' => deps do |task|
s.field :st_ctime, :time_t
end
- rlimit = FFI::StructGenerator.new 'rlimit' do |s|
+ rlimit = FFI::Generators::Structures.new 'rlimit' do |s|
s.include "sys/types.h"
s.include "sys/time.h"
s.include "sys/resource.h"
@@ -107,7 +105,7 @@ file 'runtime/platform.conf' => deps do |task|
BINARY
}
- file_cg = FFI::ConstGenerator.new 'rbx.platform.file' do |cg|
+ file_cg = FFI::Generators::Constants.new 'rbx.platform.file' do |cg|
cg.include 'stdio.h'
cg.include 'fcntl.h'
cg.include 'sys/stat.h'
@@ -149,7 +147,7 @@ file 'runtime/platform.conf' => deps do |task|
file_constants.each { |c| cg.const c }
end
- io_cg = FFI::ConstGenerator.new 'rbx.platform.io' do |cg|
+ io_cg = FFI::Generators::Constants.new 'rbx.platform.io' do |cg|
cg.include 'stdio.h'
io_constants = %w[
@@ -162,7 +160,7 @@ file 'runtime/platform.conf' => deps do |task|
end
# Only constants needed by core are added here
- fcntl_cg = FFI::ConstGenerator.new 'rbx.platform.fcntl' do |cg|
+ fcntl_cg = FFI::Generators::Constants.new 'rbx.platform.fcntl' do |cg|
cg.include 'fcntl.h'
fcntl_constants = %w[
@@ -174,7 +172,7 @@ file 'runtime/platform.conf' => deps do |task|
fcntl_constants.each { |c| cg.const c }
end
- socket_cg = FFI::ConstGenerator.new 'rbx.platform.socket' do |cg|
+ socket_cg = FFI::Generators::Constants.new 'rbx.platform.socket' do |cg|
cg.include 'sys/types.h'
cg.include 'sys/socket.h'
cg.include 'netdb.h'
@@ -424,7 +422,7 @@ file 'runtime/platform.conf' => deps do |task|
socket_constants.each { |c| cg.const c }
end
- process_cg = FFI::ConstGenerator.new 'rbx.platform.process' do |cg|
+ process_cg = FFI::Generators::Constants.new 'rbx.platform.process' do |cg|
cg.include 'sys/wait.h'
cg.include 'sys/resource.h'
cg.include 'stdlib.h'
@@ -467,7 +465,7 @@ file 'runtime/platform.conf' => deps do |task|
# The constants come from MRI's signal.c. This means that some of them might
# be missing.
- signal_cg = FFI::ConstGenerator.new 'rbx.platform.signal' do |cg|
+ signal_cg = FFI::Generators::Constants.new 'rbx.platform.signal' do |cg|
cg.include 'signal.h'
cg.include 'sys/signal.h'
@@ -521,7 +519,7 @@ file 'runtime/platform.conf' => deps do |task|
signal_constants.each { |c| cg.const c }
end
- zlib_cg = FFI::ConstGenerator.new 'rbx.platform.zlib' do |cg|
+ zlib_cg = FFI::Generators::Constants.new 'rbx.platform.zlib' do |cg|
cg.include 'zlib.h'
zlib_constants = %w[ZLIB_VERSION]
@@ -529,7 +527,7 @@ file 'runtime/platform.conf' => deps do |task|
zlib_constants.each { |c| cg.const c, "%s", "(char *)" }
end
- dlopen_cg = FFI::ConstGenerator.new 'rbx.platform.dlopen' do |cg|
+ dlopen_cg = FFI::Generators::Constants.new 'rbx.platform.dlopen' do |cg|
cg.include 'dlfcn.h'
dlopen_constants = %w[
@@ -544,26 +542,26 @@ file 'runtime/platform.conf' => deps do |task|
puts "Generating #{task.name}..." if $verbose
- File.open task.name, "w" do |f|
- addrinfo.dump_config f
- dirent.dump_config f
- timeval.dump_config f
- sockaddr_in.dump_config f
- sockaddr_un.dump_config f if sockaddr_un.found?
- servent.dump_config f
- stat.dump_config f
- rlimit.dump_config f
-
- file_cg.dump_constants f
- io_cg.dump_constants f
- fcntl_cg.dump_constants f
- socket_cg.dump_constants f
- process_cg.dump_constants f
- signal_cg.dump_constants f
- zlib_cg.dump_constants f
- dlopen_cg.dump_constants f
-
- f.puts FFI::TypesGenerator.generate
+ File.open task.name, "wb" do |f|
+ addrinfo.write_config f
+ dirent.write_config f
+ timeval.write_config f
+ sockaddr_in.write_config f
+ sockaddr_un.write_config f if sockaddr_un.found?
+ servent.write_config f
+ stat.write_config f
+ rlimit.write_config f
+
+ file_cg.write_constants f
+ io_cg.write_constants f
+ fcntl_cg.write_constants f
+ socket_cg.write_constants f
+ process_cg.write_constants f
+ signal_cg.write_constants f
+ zlib_cg.write_constants f
+ dlopen_cg.write_constants f
+
+ f.puts FFI::Generators::Types.new.generate
end
end
View
2 rakelib/vm.rake
@@ -136,7 +136,7 @@ namespace :build do
# Generate the .rb files from lib/*.rb.ffi
task :preprocessor => FFI_PREPROCESSABLES
- FFI::Generator::Task.new FFI_PREPROCESSABLES
+ FFI::FileProcessor::Task.new FFI_PREPROCESSABLES
end

0 comments on commit 0db919c

Please sign in to comment.
Something went wrong with that request. Please try again.