opsb-RubyInline 3.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +392 -0
- data/Manifest.txt +12 -0
- data/README.txt +139 -0
- data/Rakefile +38 -0
- data/demo/fastmath.rb +27 -0
- data/demo/hello.rb +13 -0
- data/example.rb +86 -0
- data/example2.rb +34 -0
- data/lib/inline.rb +878 -0
- data/test/test_inline.rb +1051 -0
- data/tutorial/example1.rb +63 -0
- data/tutorial/example2.rb +96 -0
- metadata +152 -0
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
Hoe.add_include_dirs "../../ZenTest/dev/lib"
|
7
|
+
Hoe.add_include_dirs "lib"
|
8
|
+
|
9
|
+
Hoe.plugin :seattlerb
|
10
|
+
|
11
|
+
Hoe.spec "RubyInline" do
|
12
|
+
developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
|
13
|
+
|
14
|
+
clean_globs << File.expand_path("~/.ruby_inline")
|
15
|
+
spec_extras[:requirements] =
|
16
|
+
"A POSIX environment and a compiler for your language."
|
17
|
+
extra_deps << ['ZenTest', '~> 4.3'] # for ZenTest mapping
|
18
|
+
end
|
19
|
+
|
20
|
+
task :test => :clean
|
21
|
+
|
22
|
+
desc "run all examples"
|
23
|
+
task :examples do
|
24
|
+
%w(example.rb example2.rb
|
25
|
+
tutorial/example1.rb
|
26
|
+
tutorial/example2.rb).each do |e|
|
27
|
+
rm_rf '~/.ruby_inline'
|
28
|
+
ruby "-Ilib -w #{e}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "run simple benchmarks"
|
33
|
+
task :bench do
|
34
|
+
verbose(false) do
|
35
|
+
ruby "-Ilib ./example.rb"
|
36
|
+
ruby "-Ilib ./example.rb 1000000 12" # 12 is the bignum cutoff for factorial
|
37
|
+
end
|
38
|
+
end
|
data/demo/fastmath.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
begin require 'rubygems' rescue LoadError end
|
3
|
+
require 'inline'
|
4
|
+
|
5
|
+
class FastMath
|
6
|
+
def factorial(n)
|
7
|
+
f = 1
|
8
|
+
n.downto(2) { |x| f *= x }
|
9
|
+
return f
|
10
|
+
end
|
11
|
+
inline do |builder|
|
12
|
+
builder.c "
|
13
|
+
long factorial_c(int max) {
|
14
|
+
int i=max, result=1;
|
15
|
+
while (i >= 2) { result *= i--; }
|
16
|
+
return result;
|
17
|
+
}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
math = FastMath.new
|
22
|
+
|
23
|
+
if ARGV.empty? then
|
24
|
+
30000.times do math.factorial(20); end
|
25
|
+
else
|
26
|
+
30000.times do math.factorial_c(20); end
|
27
|
+
end
|
data/demo/hello.rb
ADDED
data/example.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
$:.unshift 'lib'
|
5
|
+
require 'inline'
|
6
|
+
|
7
|
+
require 'fileutils'
|
8
|
+
FileUtils.rm_rf File.expand_path("~/.ruby_inline")
|
9
|
+
|
10
|
+
class MyTest
|
11
|
+
|
12
|
+
def factorial(n)
|
13
|
+
f = 1
|
14
|
+
n.downto(2) { |x| f *= x }
|
15
|
+
f
|
16
|
+
end
|
17
|
+
|
18
|
+
inline do |builder|
|
19
|
+
builder.c "
|
20
|
+
long factorial_c(int max) {
|
21
|
+
int i=max, result=1;
|
22
|
+
while (i >= 2) { result *= i--; }
|
23
|
+
return result;
|
24
|
+
}"
|
25
|
+
|
26
|
+
builder.c_raw "
|
27
|
+
static
|
28
|
+
VALUE
|
29
|
+
factorial_c_raw(int argc, VALUE *argv, VALUE self) {
|
30
|
+
int i=FIX2INT(argv[0]), result=1;
|
31
|
+
while (i >= 2) { result *= i--; }
|
32
|
+
return INT2NUM(result);
|
33
|
+
}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# breakeven for build run vs native doing 5 factorial:
|
38
|
+
# on a PIII/750 running FreeBSD: about 5000
|
39
|
+
# on a PPC/G4/800 running Mac OSX 10.2: always faster
|
40
|
+
|
41
|
+
require 'benchmark'
|
42
|
+
puts "RubyInline #{Inline::VERSION}" if $DEBUG
|
43
|
+
|
44
|
+
MyTest.send(:alias_method, :factorial_alias, :factorial_c_raw)
|
45
|
+
|
46
|
+
t = MyTest.new()
|
47
|
+
max = (ARGV.shift || 1_000_000).to_i
|
48
|
+
n = (ARGV.shift || 5).to_i
|
49
|
+
m = t.factorial(n)
|
50
|
+
|
51
|
+
def validate(n, m)
|
52
|
+
if n != m then raise "#{n} != #{m}"; end
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "# of iterations = #{max}, n = #{n}"
|
56
|
+
Benchmark::bm(20) do |x|
|
57
|
+
x.report("null_time") do
|
58
|
+
for i in 0..max do
|
59
|
+
# do nothing
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
x.report("c") do
|
64
|
+
for i in 0..max do
|
65
|
+
validate(t.factorial_c(n), m)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
x.report("c-raw") do
|
70
|
+
for i in 0..max do
|
71
|
+
validate(t.factorial_c_raw(n), m)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
x.report("c-alias") do
|
76
|
+
for i in 0..max do
|
77
|
+
validate(t.factorial_alias(n), m)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
x.report("pure ruby") do
|
82
|
+
for i in 0..max do
|
83
|
+
validate(t.factorial(n), m)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/example2.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/local/bin/ruby17 -w
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
rescue LoadError
|
6
|
+
$: << 'lib'
|
7
|
+
end
|
8
|
+
require 'inline'
|
9
|
+
|
10
|
+
class MyTest
|
11
|
+
|
12
|
+
inline do |builder|
|
13
|
+
|
14
|
+
builder.add_compile_flags %q(-x c++)
|
15
|
+
builder.add_link_flags %q(-lstdc++)
|
16
|
+
|
17
|
+
builder.c "
|
18
|
+
// stupid c++ comment
|
19
|
+
#include <iostream>
|
20
|
+
/* stupid c comment */
|
21
|
+
static
|
22
|
+
void
|
23
|
+
hello(int i) {
|
24
|
+
while (i-- > 0) {
|
25
|
+
std::cout << \"hello\" << std::endl;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
t = MyTest.new()
|
33
|
+
|
34
|
+
t.hello(3)
|
data/lib/inline.rb
ADDED
@@ -0,0 +1,878 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
##
|
4
|
+
# Ruby Inline is a framework for writing ruby extensions in foreign
|
5
|
+
# languages.
|
6
|
+
#
|
7
|
+
# == SYNOPSIS
|
8
|
+
#
|
9
|
+
# require 'inline'
|
10
|
+
# class MyClass
|
11
|
+
# inline do |builder|
|
12
|
+
# builder.include "<math.h>"
|
13
|
+
# builder.c %q{
|
14
|
+
# long factorial(int max) {
|
15
|
+
# int i=max, result=1;
|
16
|
+
# while (i >= 2) { result *= i--; }
|
17
|
+
# return result;
|
18
|
+
# }
|
19
|
+
# }
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# == DESCRIPTION
|
24
|
+
#
|
25
|
+
# Inline allows you to write foreign code within your ruby code. It
|
26
|
+
# automatically determines if the code in question has changed and
|
27
|
+
# builds it only when necessary. The extensions are then automatically
|
28
|
+
# loaded into the class/module that defines it.
|
29
|
+
#
|
30
|
+
# You can even write extra builders that will allow you to write
|
31
|
+
# inlined code in any language. Use Inline::C as a template and look
|
32
|
+
# at Module#inline for the required API.
|
33
|
+
#
|
34
|
+
# == PACKAGING
|
35
|
+
#
|
36
|
+
# To package your binaries into a gem, use hoe's INLINE and
|
37
|
+
# FORCE_PLATFORM env vars.
|
38
|
+
#
|
39
|
+
# Example:
|
40
|
+
#
|
41
|
+
# rake package INLINE=1
|
42
|
+
#
|
43
|
+
# or:
|
44
|
+
#
|
45
|
+
# rake package INLINE=1 FORCE_PLATFORM=mswin32
|
46
|
+
#
|
47
|
+
# See hoe for more details.
|
48
|
+
#
|
49
|
+
|
50
|
+
require "rbconfig"
|
51
|
+
require "digest/md5"
|
52
|
+
require 'fileutils'
|
53
|
+
require 'rubygems'
|
54
|
+
|
55
|
+
require 'zentest_mapping'
|
56
|
+
|
57
|
+
$TESTING = false unless defined? $TESTING
|
58
|
+
|
59
|
+
class CompilationError < RuntimeError; end
|
60
|
+
|
61
|
+
##
|
62
|
+
# The Inline module is the top-level module used. It is responsible
|
63
|
+
# for instantiating the builder for the right language used,
|
64
|
+
# compilation/linking when needed, and loading the inlined code into
|
65
|
+
# the current namespace.
|
66
|
+
|
67
|
+
module Inline
|
68
|
+
VERSION = '3.8.6'
|
69
|
+
|
70
|
+
WINDOZE = /mswin|mingw/ =~ RUBY_PLATFORM
|
71
|
+
RUBINIUS = defined? RUBY_ENGINE
|
72
|
+
DEV_NULL = (WINDOZE ? 'nul' : '/dev/null')
|
73
|
+
GEM = (WINDOZE ? 'gem.bat' : 'gem')
|
74
|
+
RAKE = if WINDOZE then
|
75
|
+
'rake.bat'
|
76
|
+
elsif RUBINIUS then
|
77
|
+
File.join(Gem.bindir, 'rake')
|
78
|
+
else
|
79
|
+
"#{Gem.ruby} -S rake"
|
80
|
+
end
|
81
|
+
|
82
|
+
warn "RubyInline v #{VERSION}" if $DEBUG
|
83
|
+
|
84
|
+
def self.register cls
|
85
|
+
registered_inline_classes << cls
|
86
|
+
registered_inline_classes.uniq!
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.registered_inline_classes
|
90
|
+
@@registered_inline_classes ||= []
|
91
|
+
end
|
92
|
+
|
93
|
+
# rootdir can be forced using INLINEDIR variable
|
94
|
+
# if not defined, it should store in user HOME folder
|
95
|
+
#
|
96
|
+
# Under Windows user data can be stored in several locations:
|
97
|
+
#
|
98
|
+
# HOME
|
99
|
+
# HOMEDRIVE + HOMEPATH
|
100
|
+
# APPDATA
|
101
|
+
# USERPROFILE
|
102
|
+
#
|
103
|
+
# Perform a check in that other to see if the environment is defined
|
104
|
+
# and if so, use it. only try this on Windows.
|
105
|
+
|
106
|
+
def self.rootdir
|
107
|
+
env = ENV['INLINEDIR'] || ENV['HOME']
|
108
|
+
|
109
|
+
if env.nil? and WINDOZE then
|
110
|
+
# try HOMEDRIVE + HOMEPATH combination
|
111
|
+
if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
|
112
|
+
env = ENV['HOMEDRIVE'] + ENV['HOMEPATH']
|
113
|
+
end
|
114
|
+
|
115
|
+
# no HOMEDRIVE? use APPDATA
|
116
|
+
env = ENV['APPDATA'] if env.nil? and ENV['APPDATA']
|
117
|
+
|
118
|
+
# bummer, still no env? then fall to USERPROFILE
|
119
|
+
env = ENV['USERPROFILE'] if env.nil? and ENV['USERPROFILE']
|
120
|
+
end
|
121
|
+
|
122
|
+
if env.nil? then
|
123
|
+
abort "Define INLINEDIR or HOME in your environment and try again"
|
124
|
+
end
|
125
|
+
|
126
|
+
unless defined? @@rootdir and env == @@rootdir and test ?d, @@rootdir then
|
127
|
+
rootdir = env
|
128
|
+
Dir.mkdir rootdir, 0700 unless test ?d, rootdir
|
129
|
+
Dir.assert_secure rootdir
|
130
|
+
@@rootdir = rootdir
|
131
|
+
end
|
132
|
+
|
133
|
+
@@rootdir
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.directory
|
137
|
+
directory = File.join(rootdir, ".ruby_inline")
|
138
|
+
unless defined? @@directory and directory == @@directory
|
139
|
+
@@directory = File.join(self.rootdir, ".ruby_inline")
|
140
|
+
end
|
141
|
+
Dir.assert_secure directory
|
142
|
+
@@directory
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Inline::C is the default builder used and the only one provided by
|
147
|
+
# Inline. It can be used as a template to write builders for other
|
148
|
+
# languages. It understands type-conversions for the basic types and
|
149
|
+
# can be extended as needed using #add_type_converter, #alias_type_converter
|
150
|
+
# and #remove_type_converter.
|
151
|
+
|
152
|
+
class C
|
153
|
+
|
154
|
+
include ZenTestMapping
|
155
|
+
|
156
|
+
MAGIC_ARITY_THRESHOLD = 15
|
157
|
+
MAGIC_ARITY = -1
|
158
|
+
|
159
|
+
##
|
160
|
+
# Default C to ruby and ruby to C type map
|
161
|
+
|
162
|
+
TYPE_MAP = {
|
163
|
+
'char' => [ 'NUM2CHR', 'CHR2FIX' ],
|
164
|
+
|
165
|
+
'char *' => [ 'StringValuePtr', 'rb_str_new2' ],
|
166
|
+
|
167
|
+
'double' => [ 'NUM2DBL', 'rb_float_new' ],
|
168
|
+
|
169
|
+
'int' => [ "FI\X2INT", 'INT2FIX' ],
|
170
|
+
'unsigned int' => [ 'NUM2UINT', 'UINT2NUM' ],
|
171
|
+
'unsigned' => [ 'NUM2UINT', 'UINT2NUM' ],
|
172
|
+
|
173
|
+
'long' => [ 'NUM2LONG', 'LONG2NUM' ],
|
174
|
+
'unsigned long' => [ 'NUM2ULONG', 'ULONG2NUM' ],
|
175
|
+
|
176
|
+
'long long' => [ 'NUM2LL', 'LL2NUM' ],
|
177
|
+
'unsigned long long' => [ 'NUM2ULL', 'ULL2NUM' ],
|
178
|
+
|
179
|
+
'off_t' => [ 'NUM2OFFT', 'OFFT2NUM' ],
|
180
|
+
|
181
|
+
'VALUE' => [ '', '' ],
|
182
|
+
# Can't do these converters because they conflict with the above:
|
183
|
+
# ID2SYM(x), SYM2ID(x), F\IX2UINT(x)
|
184
|
+
}
|
185
|
+
|
186
|
+
def strip_comments(src)
|
187
|
+
# strip c-comments
|
188
|
+
src = src.gsub(%r%\s*/\*.*?\*/%m, '')
|
189
|
+
# strip cpp-comments
|
190
|
+
src = src.gsub(%r%^\s*//.*?\n%, '')
|
191
|
+
src = src.gsub(%r%[ \t]*//[^\n]*%, '')
|
192
|
+
src
|
193
|
+
end
|
194
|
+
|
195
|
+
def parse_signature(src, raw=false)
|
196
|
+
|
197
|
+
sig = self.strip_comments(src)
|
198
|
+
# strip preprocessor directives
|
199
|
+
sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
|
200
|
+
# strip {}s
|
201
|
+
sig.gsub!(/\{[^\}]*\}/, '{ }')
|
202
|
+
# clean and collapse whitespace
|
203
|
+
sig.gsub!(/\s+/, ' ')
|
204
|
+
|
205
|
+
unless defined? @types then
|
206
|
+
@types = 'void|' + @type_map.keys.map{|x| Regexp.escape(x)}.join('|')
|
207
|
+
end
|
208
|
+
|
209
|
+
if /(#{@types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
|
210
|
+
return_type, function_name, arg_string = $1, $2, $3
|
211
|
+
args = []
|
212
|
+
arg_string.split(',').each do |arg|
|
213
|
+
|
214
|
+
# helps normalize into 'char * varname' form
|
215
|
+
arg = arg.gsub(/\s*\*\s*/, ' * ').strip
|
216
|
+
|
217
|
+
if /(((#{@types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
|
218
|
+
args.push([$4, $1])
|
219
|
+
elsif arg != "void" then
|
220
|
+
warn "WAR\NING: '#{arg}' not understood"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
arity = args.size
|
225
|
+
arity = MAGIC_ARITY if raw
|
226
|
+
|
227
|
+
return {
|
228
|
+
'return' => return_type,
|
229
|
+
'name' => function_name,
|
230
|
+
'args' => args,
|
231
|
+
'arity' => arity
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
raise SyntaxError, "Can't parse signature: #{sig}"
|
236
|
+
end # def parse_signature
|
237
|
+
|
238
|
+
def generate(src, options={})
|
239
|
+
options = {:expand_types=>options} unless Hash === options
|
240
|
+
|
241
|
+
expand_types = options[:expand_types]
|
242
|
+
singleton = options[:singleton]
|
243
|
+
result = self.strip_comments(src)
|
244
|
+
|
245
|
+
signature = parse_signature(src, !expand_types)
|
246
|
+
function_name = signature['name']
|
247
|
+
method_name = options[:method_name]
|
248
|
+
method_name ||= test_to_normal function_name
|
249
|
+
return_type = signature['return']
|
250
|
+
arity = options[:arity] || signature['arity']
|
251
|
+
|
252
|
+
raise ArgumentError, "too many arguments" if arity > MAGIC_ARITY_THRESHOLD
|
253
|
+
|
254
|
+
if expand_types then
|
255
|
+
prefix = "static VALUE #{function_name}("
|
256
|
+
if arity <= MAGIC_ARITY then
|
257
|
+
prefix += "int argc, VALUE *argv, VALUE self"
|
258
|
+
else
|
259
|
+
prefix += "VALUE self"
|
260
|
+
prefix += signature['args'].map { |arg, type| ", VALUE _#{arg}"}.join
|
261
|
+
end
|
262
|
+
prefix += ") {\n"
|
263
|
+
prefix += signature['args'].map { |arg, type|
|
264
|
+
" #{type} #{arg} = #{ruby2c(type)}(_#{arg});\n"
|
265
|
+
}.join
|
266
|
+
|
267
|
+
# replace the function signature (hopefully) with new sig (prefix)
|
268
|
+
result.sub!(/[^;\/\"\>]+#{function_name}\s*\([^\{]+\{/, "\n" + prefix)
|
269
|
+
result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
|
270
|
+
unless return_type == "void" then
|
271
|
+
raise SyntaxError, "Couldn't find return statement for #{function_name}" unless
|
272
|
+
result =~ /return/
|
273
|
+
result.gsub!(/return\s+([^\;\}]+)/) do
|
274
|
+
"return #{c2ruby(return_type)}(#{$1})"
|
275
|
+
end
|
276
|
+
else
|
277
|
+
result.sub!(/\s*\}\s*\Z/, "\nreturn Qnil;\n}")
|
278
|
+
end
|
279
|
+
else
|
280
|
+
prefix = "static #{return_type} #{function_name}("
|
281
|
+
result.sub!(/[^;\/\"\>]+#{function_name}\s*\(/, prefix)
|
282
|
+
result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
|
283
|
+
end
|
284
|
+
|
285
|
+
delta = if result =~ /\A(static.*?\{)/m then
|
286
|
+
$1.split(/\n/).size
|
287
|
+
else
|
288
|
+
warn "WAR\NING: Can't find signature in #{result.inspect}\n" unless $TESTING
|
289
|
+
0
|
290
|
+
end
|
291
|
+
|
292
|
+
file, line = caller[1].split(/:/)
|
293
|
+
result = "# line #{line.to_i + delta} \"#{file}\"\n" + result unless $DEBUG and not $TESTING
|
294
|
+
|
295
|
+
@src << result
|
296
|
+
@sig[function_name] = [arity,singleton,method_name]
|
297
|
+
|
298
|
+
return result if $TESTING
|
299
|
+
end # def generate
|
300
|
+
|
301
|
+
##
|
302
|
+
# Builds a complete C extension suitable for writing to a file and
|
303
|
+
# compiling.
|
304
|
+
|
305
|
+
def generate_ext
|
306
|
+
ext = []
|
307
|
+
|
308
|
+
if @include_ruby_first
|
309
|
+
@inc.unshift "#include \"ruby.h\""
|
310
|
+
else
|
311
|
+
@inc.push "#include \"ruby.h\""
|
312
|
+
end
|
313
|
+
|
314
|
+
ext << @inc
|
315
|
+
ext << nil
|
316
|
+
ext << @src.join("\n\n")
|
317
|
+
ext << nil
|
318
|
+
ext << nil
|
319
|
+
ext << "#ifdef __cplusplus"
|
320
|
+
ext << "extern \"C\" {"
|
321
|
+
ext << "#endif"
|
322
|
+
ext << " __declspec(dllexport)" if WINDOZE
|
323
|
+
ext << " void Init_#{module_name}() {"
|
324
|
+
ext << " VALUE c = rb_cObject;"
|
325
|
+
|
326
|
+
# TODO: use rb_class2path
|
327
|
+
# ext << " VALUE c = rb_path2class(#{@mod.name.inspect});"
|
328
|
+
ext << @mod.name.split("::").map { |n|
|
329
|
+
" c = rb_const_get(c, rb_intern(\"#{n}\"));"
|
330
|
+
}.join("\n")
|
331
|
+
|
332
|
+
ext << nil
|
333
|
+
|
334
|
+
@sig.keys.sort.each do |name|
|
335
|
+
method = ''
|
336
|
+
arity, singleton, method_name = @sig[name]
|
337
|
+
if singleton then
|
338
|
+
if method_name == 'allocate' then
|
339
|
+
raise "#{@mod}::allocate must have an arity of zero" if arity > 0
|
340
|
+
ext << " rb_define_alloc_func(c, (VALUE(*)(VALUE))#{name});"
|
341
|
+
next
|
342
|
+
end
|
343
|
+
method << " rb_define_singleton_method(c, \"#{method_name}\", "
|
344
|
+
else
|
345
|
+
method << " rb_define_method(c, \"#{method_name}\", "
|
346
|
+
end
|
347
|
+
method << "(VALUE(*)(ANYARGS))#{name}, #{arity});"
|
348
|
+
ext << method
|
349
|
+
end
|
350
|
+
|
351
|
+
ext << @init_extra.join("\n") unless @init_extra.empty?
|
352
|
+
|
353
|
+
ext << nil
|
354
|
+
ext << " }"
|
355
|
+
ext << "#ifdef __cplusplus"
|
356
|
+
ext << "}"
|
357
|
+
ext << "#endif"
|
358
|
+
ext << nil
|
359
|
+
|
360
|
+
ext.join "\n"
|
361
|
+
end
|
362
|
+
|
363
|
+
def module_name
|
364
|
+
unless defined? @module_name then
|
365
|
+
module_name = @mod.name.gsub('::','__')
|
366
|
+
md5 = Digest::MD5.new
|
367
|
+
@sig.keys.sort_by { |x| x.to_s }.each { |m| md5 << m.to_s }
|
368
|
+
@module_name = "Inline_#{module_name}_#{md5.to_s[0,4]}"
|
369
|
+
end
|
370
|
+
@module_name
|
371
|
+
end
|
372
|
+
|
373
|
+
def so_name
|
374
|
+
unless defined? @so_name then
|
375
|
+
@so_name = "#{Inline.directory}/#{module_name}.#{Config::CONFIG["DLEXT"]}"
|
376
|
+
end
|
377
|
+
@so_name
|
378
|
+
end
|
379
|
+
|
380
|
+
attr_reader :rb_file, :mod
|
381
|
+
attr_writer :mod
|
382
|
+
attr_accessor :src, :sig, :flags, :libs, :init_extra
|
383
|
+
|
384
|
+
##
|
385
|
+
# Sets the name of the C struct for generating accessors. Used with
|
386
|
+
# #accessor, #reader, #writer.
|
387
|
+
|
388
|
+
attr_accessor :struct_name
|
389
|
+
|
390
|
+
def initialize(mod)
|
391
|
+
raise ArgumentError, "Class/Module arg is required" unless Module === mod
|
392
|
+
# new (but not on some 1.8s) -> inline -> real_caller|eval
|
393
|
+
stack = caller
|
394
|
+
meth = stack.shift until meth =~ /in .(inline|test_|setup)/ or stack.empty?
|
395
|
+
raise "Couldn't discover caller" if stack.empty?
|
396
|
+
real_caller = stack.first
|
397
|
+
real_caller = stack[3] if real_caller =~ /\(eval\)/
|
398
|
+
real_caller =~ /(.*):(\d+)/
|
399
|
+
real_caller = $1
|
400
|
+
@rb_file = File.expand_path real_caller
|
401
|
+
|
402
|
+
@mod = mod
|
403
|
+
@src = []
|
404
|
+
@inc = []
|
405
|
+
@sig = {}
|
406
|
+
@flags = []
|
407
|
+
@libs = []
|
408
|
+
@init_extra = []
|
409
|
+
@include_ruby_first = true
|
410
|
+
@inherited_methods = {}
|
411
|
+
@struct_name = nil
|
412
|
+
|
413
|
+
@type_map = TYPE_MAP.dup
|
414
|
+
end
|
415
|
+
|
416
|
+
##
|
417
|
+
# Adds a #reader and #writer for a C struct member wrapped via
|
418
|
+
# Data_Wrap_Struct. +method+ is the ruby name to give the accessor,
|
419
|
+
# +type+ is the C type. Unless the C member name is overridden with
|
420
|
+
# +member+, the method name is used as the struct member.
|
421
|
+
#
|
422
|
+
# builder.struct_name = 'MyStruct'
|
423
|
+
# builder.accessor :title, 'char *'
|
424
|
+
# builder.accessor :stream_index, 'int', :index
|
425
|
+
#
|
426
|
+
# The latter accesses MyStruct->index via the stream_index method.
|
427
|
+
|
428
|
+
def accessor(method, type, member = method)
|
429
|
+
reader method, type, member
|
430
|
+
writer method, type, member
|
431
|
+
end
|
432
|
+
|
433
|
+
##
|
434
|
+
# Adds a reader for a C struct member wrapped via Data_Wrap_Struct.
|
435
|
+
# +method+ is the ruby name to give the reader, +type+ is the C type.
|
436
|
+
# Unless the C member name is overridden with +member+, the method
|
437
|
+
# name is used as the struct member. See #accessor for an example.
|
438
|
+
|
439
|
+
def reader(method, type, member = method)
|
440
|
+
raise "struct name not set for reader #{method} #{type}" unless
|
441
|
+
@struct_name
|
442
|
+
|
443
|
+
c <<-C
|
444
|
+
VALUE #{method}() {
|
445
|
+
#{@struct_name} *pointer;
|
446
|
+
|
447
|
+
Data_Get_Struct(self, #{@struct_name}, pointer);
|
448
|
+
|
449
|
+
return #{c2ruby type}(pointer->#{member});
|
450
|
+
}
|
451
|
+
C
|
452
|
+
end
|
453
|
+
|
454
|
+
##
|
455
|
+
# Adds a writer for a C struct member wrapped via Data_Get_Struct.
|
456
|
+
# +method+ is the ruby name to give the writer, +type+ is the C type.
|
457
|
+
# Unless the C member name is overridden with +member+, the method
|
458
|
+
# name is used as the struct member. See #accessor for an example.
|
459
|
+
|
460
|
+
def writer(method, type, member = method)
|
461
|
+
raise "struct name not set for writer #{method} #{type}" unless
|
462
|
+
@struct_name
|
463
|
+
|
464
|
+
c <<-C
|
465
|
+
VALUE #{method}_equals(VALUE value) {
|
466
|
+
#{@struct_name} *pointer;
|
467
|
+
|
468
|
+
Data_Get_Struct(self, #{@struct_name}, pointer);
|
469
|
+
|
470
|
+
pointer->#{member} = #{ruby2c type}(value);
|
471
|
+
|
472
|
+
return value;
|
473
|
+
}
|
474
|
+
C
|
475
|
+
end
|
476
|
+
|
477
|
+
##
|
478
|
+
# Converts ruby type +type+ to a C type
|
479
|
+
|
480
|
+
def ruby2c(type)
|
481
|
+
raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type
|
482
|
+
@type_map[type].first
|
483
|
+
end
|
484
|
+
|
485
|
+
##
|
486
|
+
# Converts C type +type+ to a ruby type
|
487
|
+
|
488
|
+
def c2ruby(type)
|
489
|
+
raise ArgumentError, "Unknown type #{type.inspect}" unless @type_map.has_key? type
|
490
|
+
@type_map[type].last
|
491
|
+
end
|
492
|
+
|
493
|
+
##
|
494
|
+
# Attempts to load pre-generated code returning true if it succeeds.
|
495
|
+
|
496
|
+
def load_cache
|
497
|
+
begin
|
498
|
+
file = File.join("inline", File.basename(so_name))
|
499
|
+
if require file then
|
500
|
+
dir = Inline.directory
|
501
|
+
warn "WAR\NING: #{dir} exists but is not being used" if test ?d, dir and $VERBOSE
|
502
|
+
return true
|
503
|
+
end
|
504
|
+
rescue LoadError
|
505
|
+
end
|
506
|
+
return false
|
507
|
+
end
|
508
|
+
|
509
|
+
##
|
510
|
+
# Loads the generated code back into ruby
|
511
|
+
|
512
|
+
def load
|
513
|
+
Kernel.require "#{so_name}" or raise LoadError, "require on #{so_name} failed"
|
514
|
+
end
|
515
|
+
|
516
|
+
##
|
517
|
+
# Builds the source file, if needed, and attempts to compile it.
|
518
|
+
|
519
|
+
def build
|
520
|
+
so_name = self.so_name
|
521
|
+
so_exists = File.file? so_name
|
522
|
+
unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then
|
523
|
+
|
524
|
+
unless File.directory? Inline.directory then
|
525
|
+
warn "NOTE: creating #{Inline.directory} for RubyInline" if $DEBUG
|
526
|
+
Dir.mkdir Inline.directory, 0700
|
527
|
+
end
|
528
|
+
|
529
|
+
src_name = "#{Inline.directory}/#{module_name}.c"
|
530
|
+
old_src_name = "#{src_name}.old"
|
531
|
+
should_compare = File.write_with_backup(src_name) do |io|
|
532
|
+
io.puts generate_ext
|
533
|
+
end
|
534
|
+
|
535
|
+
# recompile only if the files are different
|
536
|
+
recompile = true
|
537
|
+
if so_exists and should_compare and
|
538
|
+
FileUtils.compare_file(old_src_name, src_name) then
|
539
|
+
recompile = false
|
540
|
+
|
541
|
+
# Updates the timestamps on all the generated/compiled files.
|
542
|
+
# Prevents us from entering this conditional unless the source
|
543
|
+
# file changes again.
|
544
|
+
t = Time.now
|
545
|
+
File.utime(t, t, src_name, old_src_name, so_name)
|
546
|
+
end
|
547
|
+
|
548
|
+
if recompile then
|
549
|
+
|
550
|
+
hdrdir = %w(srcdir archdir rubyhdrdir).map { |name|
|
551
|
+
Config::CONFIG[name]
|
552
|
+
}.find { |dir|
|
553
|
+
dir and File.exist? File.join(dir, "/ruby.h")
|
554
|
+
} or abort "ERROR: Can't find header dir for ruby. Exiting..."
|
555
|
+
|
556
|
+
flags = @flags.join(' ')
|
557
|
+
libs = @libs.join(' ')
|
558
|
+
|
559
|
+
config_hdrdir = if RUBY_VERSION > '1.9' then
|
560
|
+
"-I #{File.join hdrdir, RbConfig::CONFIG['arch']}"
|
561
|
+
else
|
562
|
+
nil
|
563
|
+
end
|
564
|
+
|
565
|
+
cmd = [ Config::CONFIG['LDSHARED'],
|
566
|
+
flags,
|
567
|
+
Config::CONFIG['DLDFLAGS'],
|
568
|
+
Config::CONFIG['CCDLFLAGS'],
|
569
|
+
Config::CONFIG['CFLAGS'],
|
570
|
+
'-I', hdrdir,
|
571
|
+
config_hdrdir,
|
572
|
+
'-I', Config::CONFIG['includedir'],
|
573
|
+
"-L#{Config::CONFIG['libdir']}",
|
574
|
+
'-o', so_name.inspect,
|
575
|
+
File.expand_path(src_name).inspect,
|
576
|
+
libs,
|
577
|
+
crap_for_windoze ].join(' ')
|
578
|
+
|
579
|
+
# TODO: remove after osx 10.5.2
|
580
|
+
cmd += ' -flat_namespace -undefined suppress' if
|
581
|
+
RUBY_PLATFORM =~ /darwin9\.[01]/
|
582
|
+
cmd += " 2> #{DEV_NULL}" if $TESTING and not $DEBUG
|
583
|
+
|
584
|
+
warn "Building #{so_name} with '#{cmd}'" if $DEBUG
|
585
|
+
result = `#{cmd}`
|
586
|
+
warn "Output:\n#{result}" if $DEBUG
|
587
|
+
if $? != 0 then
|
588
|
+
bad_src_name = src_name + ".bad"
|
589
|
+
File.rename src_name, bad_src_name
|
590
|
+
raise CompilationError, "error executing #{cmd.inspect}: #{$?}\nRenamed #{src_name} to #{bad_src_name}"
|
591
|
+
end
|
592
|
+
|
593
|
+
# NOTE: manifest embedding is only required when using VC8 ruby
|
594
|
+
# build or compiler.
|
595
|
+
# Errors from this point should be ignored if Config::CONFIG['arch']
|
596
|
+
# (RUBY_PLATFORM) matches 'i386-mswin32_80'
|
597
|
+
if WINDOZE and RUBY_PLATFORM =~ /_80$/ then
|
598
|
+
Dir.chdir Inline.directory do
|
599
|
+
cmd = "mt /manifest lib.so.manifest /outputresource:so.dll;#2"
|
600
|
+
warn "Embedding manifest with '#{cmd}'" if $DEBUG
|
601
|
+
result = `#{cmd}`
|
602
|
+
warn "Output:\n#{result}" if $DEBUG
|
603
|
+
if $? != 0 then
|
604
|
+
raise CompilationError, "error executing #{cmd}: #{$?}"
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
warn "Built successfully" if $DEBUG
|
610
|
+
end
|
611
|
+
|
612
|
+
else
|
613
|
+
warn "#{so_name} is up to date" if $DEBUG
|
614
|
+
end # unless (file is out of date)
|
615
|
+
end # def build
|
616
|
+
|
617
|
+
##
|
618
|
+
# Returns extra compilation flags for windoze platforms. Ugh.
|
619
|
+
|
620
|
+
def crap_for_windoze
|
621
|
+
# gawd windoze land sucks
|
622
|
+
case RUBY_PLATFORM
|
623
|
+
when /mswin32/ then
|
624
|
+
" -link /LIBPATH:\"#{Config::CONFIG['libdir']}\" /DEFAULTLIB:\"#{Config::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
|
625
|
+
when /mingw32/ then
|
626
|
+
" -Wl,--enable-auto-import -L#{Config::CONFIG['libdir']} -lmsvcrt-ruby18"
|
627
|
+
when /i386-cygwin/ then
|
628
|
+
' -L/usr/local/lib -lruby.dll'
|
629
|
+
else
|
630
|
+
''
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
##
|
635
|
+
# Adds compiler options to the compiler command line. No
|
636
|
+
# preprocessing is done, so you must have all your dashes and
|
637
|
+
# everything.
|
638
|
+
|
639
|
+
def add_compile_flags(*flags)
|
640
|
+
@flags.push(*flags)
|
641
|
+
end
|
642
|
+
|
643
|
+
##
|
644
|
+
# Adds linker flags to the link command line. No preprocessing is
|
645
|
+
# done, so you must have all your dashes and everything.
|
646
|
+
|
647
|
+
def add_link_flags(*flags)
|
648
|
+
@libs.push(*flags)
|
649
|
+
end
|
650
|
+
|
651
|
+
##
|
652
|
+
# Create a static variable and initialize it to a value.
|
653
|
+
|
654
|
+
def add_static name, init, type = "VALUE"
|
655
|
+
prefix "static #{type} #{name};"
|
656
|
+
add_to_init "#{name} = #{init};"
|
657
|
+
end
|
658
|
+
|
659
|
+
##
|
660
|
+
# Adds custom content to the end of the init function.
|
661
|
+
|
662
|
+
def add_to_init(*src)
|
663
|
+
@init_extra.push(*src)
|
664
|
+
end
|
665
|
+
|
666
|
+
##
|
667
|
+
# Registers C type-casts +r2c+ and +c2r+ for +type+.
|
668
|
+
|
669
|
+
def add_type_converter(type, r2c, c2r)
|
670
|
+
warn "WAR\NING: overridding #{type} on #{caller[0]}" if @type_map.has_key? type
|
671
|
+
@type_map[type] = [r2c, c2r]
|
672
|
+
end
|
673
|
+
|
674
|
+
##
|
675
|
+
# Registers C type +alias_type+ as an alias of +existing_type+
|
676
|
+
|
677
|
+
def alias_type_converter(existing_type, alias_type)
|
678
|
+
warn "WAR\NING: overridding #{type} on #{caller[0]}" if
|
679
|
+
@type_map.has_key? alias_type
|
680
|
+
|
681
|
+
@type_map[alias_type] = @type_map[existing_type]
|
682
|
+
end
|
683
|
+
|
684
|
+
##
|
685
|
+
# Unregisters C type-casts for +type+.
|
686
|
+
|
687
|
+
def remove_type_converter(type)
|
688
|
+
@type_map.delete type
|
689
|
+
end
|
690
|
+
|
691
|
+
##
|
692
|
+
# Maps a ruby constant to C (with the same name)
|
693
|
+
|
694
|
+
def map_ruby_const(*names)
|
695
|
+
names.each do |name|
|
696
|
+
self.prefix "static VALUE #{name};"
|
697
|
+
self.add_to_init " #{name} = rb_const_get(c, rb_intern(#{name.to_s.inspect}));"
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
##
|
702
|
+
# Maps a C constant to ruby. +names_and_types+ is a hash that maps the
|
703
|
+
# name of the constant to its C type.
|
704
|
+
#
|
705
|
+
# builder.map_c_const :C_NAME => :int
|
706
|
+
#
|
707
|
+
# If you wish to give the constant a different ruby name:
|
708
|
+
#
|
709
|
+
# builder.map_c_const :C_NAME => [:int, :RUBY_NAME]
|
710
|
+
|
711
|
+
def map_c_const(names_and_types)
|
712
|
+
names_and_types.each do |name, typ|
|
713
|
+
typ, ruby_name = Array === typ ? typ : [typ, name]
|
714
|
+
self.add_to_init " rb_define_const(c, #{ruby_name.to_s.inspect}, #{c2ruby(typ.to_s)}(#{name}));"
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
##
|
719
|
+
# Adds an include to the top of the file. Don't forget to use
|
720
|
+
# quotes or angle brackets.
|
721
|
+
|
722
|
+
def include(header)
|
723
|
+
@inc << "#include #{header}"
|
724
|
+
end
|
725
|
+
|
726
|
+
##
|
727
|
+
# Specifies that the the ruby.h header should be included *after* custom
|
728
|
+
# header(s) instead of before them.
|
729
|
+
|
730
|
+
def include_ruby_last
|
731
|
+
@include_ruby_first = false
|
732
|
+
end
|
733
|
+
|
734
|
+
##
|
735
|
+
# Adds any amount of text/code to the source
|
736
|
+
|
737
|
+
def prefix(code)
|
738
|
+
@src << code
|
739
|
+
end
|
740
|
+
|
741
|
+
##
|
742
|
+
# Adds a C function to the source, including performing automatic
|
743
|
+
# type conversion to arguments and the return value. The Ruby
|
744
|
+
# method name can be overridden by providing method_name. Unknown
|
745
|
+
# type conversions can be extended by using +add_type_converter+.
|
746
|
+
|
747
|
+
def c src, options = {}
|
748
|
+
options = {
|
749
|
+
:expand_types => true,
|
750
|
+
}.merge options
|
751
|
+
self.generate src, options
|
752
|
+
end
|
753
|
+
|
754
|
+
##
|
755
|
+
# Same as +c+, but adds a class function.
|
756
|
+
|
757
|
+
def c_singleton src, options = {}
|
758
|
+
options = {
|
759
|
+
:expand_types => true,
|
760
|
+
:singleton => true,
|
761
|
+
}.merge options
|
762
|
+
self.generate src, options
|
763
|
+
end
|
764
|
+
|
765
|
+
##
|
766
|
+
# Adds a raw C function to the source. This version does not
|
767
|
+
# perform any type conversion and must conform to the ruby/C
|
768
|
+
# coding conventions. The Ruby method name can be overridden
|
769
|
+
# by providing method_name.
|
770
|
+
|
771
|
+
def c_raw src, options = {}
|
772
|
+
self.generate src, options
|
773
|
+
end
|
774
|
+
|
775
|
+
##
|
776
|
+
# Same as +c_raw+, but adds a class function.
|
777
|
+
|
778
|
+
def c_raw_singleton src, options = {}
|
779
|
+
options = {
|
780
|
+
:singleton => true,
|
781
|
+
}.merge options
|
782
|
+
self.generate src, options
|
783
|
+
end
|
784
|
+
|
785
|
+
end # class Inline::C
|
786
|
+
end # module Inline
|
787
|
+
|
788
|
+
class Module
|
789
|
+
|
790
|
+
##
|
791
|
+
# options is a hash that allows you to pass extra data to your
|
792
|
+
# builder. The only key that is guaranteed to exist is :testing.
|
793
|
+
|
794
|
+
attr_reader :options
|
795
|
+
|
796
|
+
##
|
797
|
+
# Extends the Module class to have an inline method. The default
|
798
|
+
# language/builder used is C, but can be specified with the +lang+
|
799
|
+
# parameter.
|
800
|
+
|
801
|
+
def inline(lang = :C, options={})
|
802
|
+
Inline.register self
|
803
|
+
|
804
|
+
case options
|
805
|
+
when TrueClass, FalseClass then
|
806
|
+
warn "WAR\NING: 2nd argument to inline is now a hash, changing to {:testing=>#{options}}" unless options
|
807
|
+
options = { :testing => options }
|
808
|
+
when Hash
|
809
|
+
options[:testing] ||= false
|
810
|
+
else
|
811
|
+
raise ArgumentError, "BLAH"
|
812
|
+
end
|
813
|
+
|
814
|
+
builder_class = begin
|
815
|
+
Inline.const_get(lang)
|
816
|
+
rescue NameError
|
817
|
+
require "inline/#{lang}"
|
818
|
+
Inline.const_get(lang)
|
819
|
+
end
|
820
|
+
|
821
|
+
@options = options
|
822
|
+
builder = builder_class.new self
|
823
|
+
|
824
|
+
yield builder
|
825
|
+
|
826
|
+
unless options[:testing] then
|
827
|
+
unless builder.load_cache then
|
828
|
+
builder.build
|
829
|
+
builder.load
|
830
|
+
end
|
831
|
+
end
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
class File
|
836
|
+
|
837
|
+
##
|
838
|
+
# Equivalent to +File::open+ with an associated block, but moves
|
839
|
+
# any existing file with the same name to the side first.
|
840
|
+
|
841
|
+
def self.write_with_backup(path) # returns true if file already existed
|
842
|
+
|
843
|
+
# move previous version to the side if it exists
|
844
|
+
renamed = false
|
845
|
+
if test ?f, path then
|
846
|
+
renamed = true
|
847
|
+
File.rename path, path + ".old"
|
848
|
+
end
|
849
|
+
|
850
|
+
File.open(path, "w") do |io|
|
851
|
+
yield(io)
|
852
|
+
end
|
853
|
+
|
854
|
+
return renamed
|
855
|
+
end
|
856
|
+
end # class File
|
857
|
+
|
858
|
+
class Dir
|
859
|
+
|
860
|
+
##
|
861
|
+
# +assert_secure+ checks that if a +path+ exists it has minimally
|
862
|
+
# writable permissions. If not, it prints an error and exits. It
|
863
|
+
# only works on +POSIX+ systems. Patches for other systems are
|
864
|
+
# welcome.
|
865
|
+
|
866
|
+
def self.assert_secure(path)
|
867
|
+
mode = File.stat(path).mode
|
868
|
+
unless ((mode % 01000) & 0022) == 0 then
|
869
|
+
if $TESTING then
|
870
|
+
raise SecurityError, "Directory #{path} is insecure"
|
871
|
+
else
|
872
|
+
abort "#{path} is insecure (#{'%o' % mode}). It may not be group or world writable. Exiting."
|
873
|
+
end
|
874
|
+
end
|
875
|
+
rescue Errno::ENOENT
|
876
|
+
# If it ain't there, it's certainly secure
|
877
|
+
end
|
878
|
+
end
|