/usr/lib/ruby/vendor_ruby/ffi/tools/const_generator.rb is in ruby-ffi 1.9.10debian-1build2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | require 'tempfile'
require 'open3'
module FFI
# ConstGenerator turns C constants into ruby values.
#
# @example a simple example for stdio
# cg = FFI::ConstGenerator.new('stdio') do |gen|
# gen.const(:SEEK_SET)
# gen.const('SEEK_CUR')
# gen.const('seek_end') # this constant does not exist
# end # #calculate called automatically at the end of the block
#
# cg['SEEK_SET'] # => 0
# cg['SEEK_CUR'] # => 1
# cg['seek_end'] # => nil
# cg.to_ruby # => "SEEK_SET = 0\nSEEK_CUR = 1\n# seek_end not available"
class ConstGenerator
@options = {}
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.
#
# @param [#to_s] prefix
# @param [Hash] options
# @return
# @option options [Boolean] :required
# @overload initialize(prefix, options)
# @overload initialize(prefix, options) { |gen| ... }
# @yieldparam [ConstGenerator] gen new generator is passed to the block
# 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 = ['stdio.h', 'stddef.h']
@constants = {}
@prefix = prefix
@required = options[:required]
@options = options
if block_given? then
yield self
calculate self.class.options.merge(options)
end
end
# Set class options
# These options are merged with {#initialize} options when it is called with a block.
# @param [Hash] options
# @return [Hash] class options
def self.options=(options)
@options = options
end
# Get class options.
# @return [Hash] class options
def self.options
@options
end
# @param [String] name
# @return constant value (converted if a +converter+ was defined).
# Access a constant by name.
def [](name)
@constants[name].converted_value
end
# Request the value for C constant +name+.
#
# @param [#to_s] name C constant name
# @param [String] format a printf format string to print the value out
# @param [String] cast a C cast for the value
# @param ruby_name alternate ruby name for {#to_ruby}
#
# @overload const(name, format=nil, cast='', ruby_name=nil, converter=nil)
# +converter+ is a Method or a Proc.
# @param [#call] converter convert the value from a string to the appropriate
# type for {#to_ruby}.
# @overload const(name, format=nil, cast='', ruby_name=nil) { |value| ... }
# Use a converter block. This block convert the value from a string to the
# appropriate type for {#to_ruby}.
# @yieldparam value constant value
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
# Calculate constants values.
# @param [Hash] options
# @option options [String] :cppflags flags for C compiler
# @return [nil]
# @raise if a constant is missing and +:required+ was set to +true+ (see {#initialize})
def calculate(options = {})
binary = File.join Dir.tmpdir, "rb_const_gen_bin_#{Process.pid}"
Tempfile.open("#{@prefix}.const_generator") do |f|
@includes.each do |inc|
f.puts "#include <#{inc}>"
end
f.puts "\nint 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 #{options[:cppflags]} -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 + (FFI::Platform.windows? ? ".exe" : ""))
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
# Dump constants to +io+.
# @param [#puts] io
# @return [nil]
def dump_constants(io)
@constants.each do |name, constant|
name = [@prefix, name].join '.' if @prefix
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.
# @return [String]
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
# Add additional C include file(s) to calculate constants from.
# @note +stdio.h+ and +stddef.h+ automatically included
# @param [List<String>, Array<String>] i include file(s)
# @return [Array<String>] array of include files
def include(*i)
@includes |= i.flatten
end
end
# This class hold constants for {ConstGenerator}
class ConstGenerator::Constant
attr_reader :name, :format, :cast
attr_accessor :value
# @param [#to_s] name
# @param [String] format a printf format string to print the value out
# @param [String] cast a C cast for the value
# @param ruby_name alternate ruby name for {#to_ruby}
# @param [#call] converter convert the value from a string to the appropriate
# type for {#to_ruby}.
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
# Return constant value (converted if a +converter+ was defined).
# @return constant value.
def converted_value
if @converter
@converter.call(@value)
else
@value
end
end
# get constant ruby name
# @return [String]
def ruby_name
@ruby_name || @name
end
# Get an evaluable string from constant.
# @return [String]
def to_ruby
"#{ruby_name} = #{converted_value}"
end
end
end
|