ffi-inliner 0.2.1
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/.gitignore +6 -0
- data/History.txt +21 -0
- data/README.rdoc +85 -0
- data/Rakefile +44 -0
- data/examples/ex_1.rb +23 -0
- data/lib/ffi-inliner.rb +5 -0
- data/lib/ffi-inliner/inliner.rb +257 -0
- data/lib/ffi-inliner/version.rb +3 -0
- data/spec/ffi-inliner/inliner_spec.rb +186 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +4 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- metadata +98 -0
data/History.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
== 0.2.1 / 2009-08-05
|
2
|
+
|
3
|
+
* Bug fix
|
4
|
+
* Add fPIC option to gcc compiler to fix AMD64 compilation issue
|
5
|
+
|
6
|
+
== 0.2.0 / 2009-07-20
|
7
|
+
|
8
|
+
* Major enhancements
|
9
|
+
* Add support for different compiler backends
|
10
|
+
* Improve compatibility with Windows and OSX boxes
|
11
|
+
* Minor enhancements
|
12
|
+
* Add support for configuration block syntax
|
13
|
+
* Remove dependency from ffi-tcc and libtcc
|
14
|
+
* Improve compatibility with MRI 1.9.1
|
15
|
+
* Cleanup and code refactoring
|
16
|
+
|
17
|
+
== 0.1.0 / 2009-07-07
|
18
|
+
|
19
|
+
* 1 major enhancement
|
20
|
+
* Birthday!
|
21
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
ffi-inliner
|
2
|
+
by Andrea Fazzi
|
3
|
+
http://github.com/remogatto/ffi-inliner
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
With ffi-inliner you can run C code within your ruby script.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* Mix C snippets in your Ruby code and gulp it on the fly!
|
12
|
+
* It's based on Ruby-FFI so the C code you inject is portable across
|
13
|
+
Ruby implementation!
|
14
|
+
* Yep, it means that you can run it on JRuby too!
|
15
|
+
* Fast compilation through tcc[http://bellard.org/tcc/]
|
16
|
+
* But it can use the system's compiler (e.g. gcc) on those platforms
|
17
|
+
that don't support tcc (e.g. OSX)
|
18
|
+
|
19
|
+
== SYNOPSIS:
|
20
|
+
|
21
|
+
require 'ffi-inliner'
|
22
|
+
|
23
|
+
module MyLib
|
24
|
+
extend Inliner
|
25
|
+
inline 'void say_hello(char* name) { printf("Hello, %s\n", name); }'
|
26
|
+
end
|
27
|
+
|
28
|
+
MyLib.say_hello('boys')
|
29
|
+
|
30
|
+
class Foo
|
31
|
+
include MyLib
|
32
|
+
end
|
33
|
+
|
34
|
+
Foo.new.say_hello('foos')
|
35
|
+
|
36
|
+
For other hints see the examples/ folder or visit the
|
37
|
+
wiki[http://wiki.github.com/remogatto/ffi-inliner/tutorial]
|
38
|
+
|
39
|
+
== REQUIREMENTS:
|
40
|
+
|
41
|
+
* ffi >= 0.4.0
|
42
|
+
* gcc and/or tcc >= 0.9.25 (optional)
|
43
|
+
|
44
|
+
== DOWNLOAD/INSTALL:
|
45
|
+
|
46
|
+
From rubyforge:
|
47
|
+
|
48
|
+
[sudo] gem install ffi-inliner
|
49
|
+
|
50
|
+
or from github:
|
51
|
+
|
52
|
+
git clone git://github.com/remogatto/ffi-inliner
|
53
|
+
cd ffi-inliner
|
54
|
+
sudo rake gem:install
|
55
|
+
|
56
|
+
== CREDITS
|
57
|
+
|
58
|
+
This software is inspired to
|
59
|
+
RubyInline[http://www.zenspider.com/ZSS/Products/RubyInline/] by Ryan
|
60
|
+
Davis. Thank you Ryan.
|
61
|
+
|
62
|
+
== LICENSE:
|
63
|
+
|
64
|
+
(The MIT License)
|
65
|
+
|
66
|
+
Copyright (c) 2009 Andrea Fazzi
|
67
|
+
|
68
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
69
|
+
a copy of this software and associated documentation files (the
|
70
|
+
'Software'), to deal in the Software without restriction, including
|
71
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
72
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
73
|
+
permit persons to whom the Software is furnished to do so, subject to
|
74
|
+
the following conditions:
|
75
|
+
|
76
|
+
The above copyright notice and this permission notice shall be
|
77
|
+
included in all copies or substantial portions of the Software.
|
78
|
+
|
79
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
80
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
81
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
82
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
83
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
84
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
85
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib')))
|
6
|
+
require 'ffi-inliner/version'
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'bones'
|
10
|
+
Bones.setup
|
11
|
+
rescue LoadError
|
12
|
+
begin
|
13
|
+
load 'tasks/setup.rb'
|
14
|
+
rescue LoadError
|
15
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
CLOBBER << '*~' << '*.*~'
|
20
|
+
|
21
|
+
PROJ.name = 'ffi-inliner'
|
22
|
+
PROJ.authors = 'Andrea Fazzi'
|
23
|
+
PROJ.email = 'andrea.fazzi@alcacoop.it'
|
24
|
+
PROJ.url = 'http://github.com/remogatto/ffi-inliner'
|
25
|
+
PROJ.version = Inliner::VERSION
|
26
|
+
|
27
|
+
PROJ.readme_file = 'README.rdoc'
|
28
|
+
|
29
|
+
PROJ.rubyforge.name = 'ffi'
|
30
|
+
|
31
|
+
PROJ.ann.paragraphs << 'FEATURES' << 'SYNOPSIS' << 'REQUIREMENTS' << 'DOWNLOAD/INSTALL' << 'CREDITS'
|
32
|
+
PROJ.ann.email[:from] = 'andrea.fazzi@alcacoop.it'
|
33
|
+
PROJ.ann.email[:to] << 'dev@ruby-ffi.kenai.com' << 'users@ruby-ffi.kenai.com'
|
34
|
+
PROJ.ann.email[:server] = 'smtp.gmail.com'
|
35
|
+
|
36
|
+
PROJ.spec.opts << '--color' << '-fs'
|
37
|
+
|
38
|
+
PROJ.ruby_opts = []
|
39
|
+
|
40
|
+
depend_on 'ffi', '0.3.5'
|
41
|
+
|
42
|
+
task :default => 'spec'
|
43
|
+
|
44
|
+
# EOF
|
data/examples/ex_1.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../lib')))
|
2
|
+
require 'ffi-inliner'
|
3
|
+
|
4
|
+
module MyLib
|
5
|
+
extend Inliner
|
6
|
+
inline 'void say_hello(char* name) { printf("Hello, %s\n", name); }'
|
7
|
+
end
|
8
|
+
|
9
|
+
MyLib.say_hello('boys')
|
10
|
+
|
11
|
+
class Foo
|
12
|
+
include MyLib
|
13
|
+
end
|
14
|
+
|
15
|
+
Foo.new.say_hello('foos')
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
data/lib/ffi-inliner.rb
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
module Inliner
|
2
|
+
|
3
|
+
DEV_NULL = if Config::CONFIG['target_os'] =~ /mswin|mingw/
|
4
|
+
'nul'
|
5
|
+
else
|
6
|
+
'/dev/null'
|
7
|
+
end
|
8
|
+
|
9
|
+
LIB_EXT = if Config::CONFIG['target_os'] =~ /darwin/
|
10
|
+
'.dylib'
|
11
|
+
elsif Config::CONFIG['target_os'] =~ /mswin|mingw/
|
12
|
+
'.dll'
|
13
|
+
else
|
14
|
+
'.so'
|
15
|
+
end
|
16
|
+
|
17
|
+
C_TO_FFI = {
|
18
|
+
'void' => :void,
|
19
|
+
'char' => :char,
|
20
|
+
'unsigned char' => :uchar,
|
21
|
+
'int' => :int,
|
22
|
+
'unsigned int' => :uint,
|
23
|
+
'long' => :long,
|
24
|
+
'unsigned long' => :ulong,
|
25
|
+
'float' => :float,
|
26
|
+
'double' => :double,
|
27
|
+
}
|
28
|
+
|
29
|
+
@@__inliner_directory = File.expand_path(File.join('~/', '.ffi-inliner'))
|
30
|
+
|
31
|
+
class << self
|
32
|
+
def directory
|
33
|
+
@@__inliner_directory
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class FilenameManager
|
38
|
+
def initialize(mod, code)
|
39
|
+
@mod = mod.name.gsub('::', '__')
|
40
|
+
@code = code
|
41
|
+
end
|
42
|
+
def cached?
|
43
|
+
exists?
|
44
|
+
end
|
45
|
+
def exists?
|
46
|
+
File.exists?(c_fn)
|
47
|
+
end
|
48
|
+
def base_fn
|
49
|
+
File.join(Inliner.directory, "#{@mod}_#{(Digest::MD5.new << @code).to_s[0, 4]}")
|
50
|
+
end
|
51
|
+
%w(c rb log).each do |ext|
|
52
|
+
define_method("#{ext}_fn") { "#{base_fn}.#{ext}" }
|
53
|
+
end
|
54
|
+
def so_fn
|
55
|
+
"#{base_fn}#{LIB_EXT}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module Compilers
|
60
|
+
class Compiler
|
61
|
+
attr_reader :progname
|
62
|
+
def self.check_and_create(fm = nil)
|
63
|
+
compiler = new(fm)
|
64
|
+
unless compiler.exists?
|
65
|
+
raise "Can't find compiler #{compiler.class}"
|
66
|
+
else
|
67
|
+
compiler
|
68
|
+
end
|
69
|
+
end
|
70
|
+
def initialize(fm = nil)
|
71
|
+
@fm = fm
|
72
|
+
@progname = cmd.split.first
|
73
|
+
end
|
74
|
+
def compile
|
75
|
+
raise "Compile error! See #{@fm.log_fn}" unless system(cmd)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class GCC < Compiler
|
80
|
+
def exists?
|
81
|
+
IO.popen("#{@progname} 2>&1") { |f| f.gets } ? true : false
|
82
|
+
end
|
83
|
+
def ldshared
|
84
|
+
if Config::CONFIG['target_os'] =~ /darwin/
|
85
|
+
'gcc -dynamic -bundle -fPIC'
|
86
|
+
else
|
87
|
+
'gcc -shared -fPIC'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
def cmd
|
91
|
+
"#{ldshared} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class TCC < Compiler
|
96
|
+
def exists?
|
97
|
+
IO.popen("#{@progname}") { |f| f.gets } ? true : false
|
98
|
+
end
|
99
|
+
def cmd
|
100
|
+
"tcc -shared -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Builder
|
106
|
+
attr_reader :code
|
107
|
+
def initialize(mod, code = "", options = {})
|
108
|
+
make_pointer_types
|
109
|
+
@mod = mod
|
110
|
+
@code = code
|
111
|
+
@sig = [parse_signature(@code)] unless @code.empty?
|
112
|
+
options = { :compiler => Compilers::GCC }.merge(options)
|
113
|
+
@compiler = options[:compiler]
|
114
|
+
end
|
115
|
+
|
116
|
+
def map(type_map)
|
117
|
+
@types.merge!(type_map)
|
118
|
+
end
|
119
|
+
|
120
|
+
def c(code)
|
121
|
+
(@sig ||= []) << parse_signature(code)
|
122
|
+
@code << code
|
123
|
+
end
|
124
|
+
|
125
|
+
def c_raw(code)
|
126
|
+
@code << code
|
127
|
+
end
|
128
|
+
|
129
|
+
def use_compiler(compiler)
|
130
|
+
@compiler = compiler
|
131
|
+
end
|
132
|
+
|
133
|
+
def build
|
134
|
+
@fm = FilenameManager.new(@mod, @code)
|
135
|
+
@compiler = @compiler.check_and_create(@fm)
|
136
|
+
unless @fm.cached?
|
137
|
+
write_files(@code, @sig)
|
138
|
+
@compiler.compile
|
139
|
+
@mod.instance_eval generate_ffi(@sig)
|
140
|
+
else
|
141
|
+
@mod.instance_eval(File.read(@fm.rb_fn))
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def make_pointer_types
|
148
|
+
@types = C_TO_FFI.dup
|
149
|
+
C_TO_FFI.each_key do |k|
|
150
|
+
@types["#{k} *"] = :pointer
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def cached?(name, code)
|
155
|
+
File.exists?(cname(name, code))
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_ffi_type(c_type)
|
159
|
+
@types[c_type]
|
160
|
+
end
|
161
|
+
|
162
|
+
# Based on RubyInline code by Ryan Davis
|
163
|
+
# Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
|
164
|
+
def strip_comments(code)
|
165
|
+
# strip c-comments
|
166
|
+
src = code.gsub(%r%\s*/\*.*?\*/%m, '')
|
167
|
+
# strip cpp-comments
|
168
|
+
src = src.gsub(%r%^\s*//.*?\n%, '')
|
169
|
+
src = src.gsub(%r%[ \t]*//[^\n]*%, '')
|
170
|
+
src
|
171
|
+
end
|
172
|
+
|
173
|
+
# Based on RubyInline code by Ryan Davis
|
174
|
+
# Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
|
175
|
+
def parse_signature(code)
|
176
|
+
sig = strip_comments(code)
|
177
|
+
# strip preprocessor directives
|
178
|
+
sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
|
179
|
+
# strip {}s
|
180
|
+
sig.gsub!(/\{[^\}]*\}/, '{ }')
|
181
|
+
# clean and collapse whitespace
|
182
|
+
sig.gsub!(/\s+/, ' ')
|
183
|
+
|
184
|
+
# types = 'void|int|char|char\s\*|void\s\*'
|
185
|
+
types = @types.keys.map{|x| Regexp.escape(x)}.join('|')
|
186
|
+
sig = sig.gsub(/\s*\*\s*/, ' * ').strip
|
187
|
+
|
188
|
+
if /(#{types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
|
189
|
+
return_type, function_name, arg_string = $1, $2, $3
|
190
|
+
args = []
|
191
|
+
arg_string.split(',').each do |arg|
|
192
|
+
|
193
|
+
# helps normalize into 'char * varname' form
|
194
|
+
arg = arg.gsub(/\s*\*\s*/, ' * ').strip
|
195
|
+
|
196
|
+
if /(((#{types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
|
197
|
+
args.push($1)
|
198
|
+
elsif arg != "void" then
|
199
|
+
warn "WAR\NING: '#{arg}' not understood"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
arity = args.size
|
204
|
+
|
205
|
+
return {
|
206
|
+
'return' => return_type,
|
207
|
+
'name' => function_name,
|
208
|
+
'args' => args,
|
209
|
+
'arity' => arity
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
raise SyntaxError, "Can't parse signature: #{sig}"
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
def generate_ffi(sig)
|
218
|
+
ffi_code = <<PREAMBLE
|
219
|
+
extend FFI::Library
|
220
|
+
ffi_lib '#{@fm.so_fn}'
|
221
|
+
|
222
|
+
PREAMBLE
|
223
|
+
sig.each do |s|
|
224
|
+
args = s['args'].map { |arg| ":#{to_ffi_type(arg)}" }.join(',')
|
225
|
+
ffi_code << "attach_function '#{s['name']}', [#{args}], :#{to_ffi_type(s['return'])}\n"
|
226
|
+
end
|
227
|
+
ffi_code
|
228
|
+
end
|
229
|
+
def write_c(code)
|
230
|
+
File.open(@fm.c_fn, 'w') { |f| f << code }
|
231
|
+
end
|
232
|
+
|
233
|
+
def write_ffi(sig)
|
234
|
+
File.open(@fm.rb_fn, 'w') { |f| f << generate_ffi(sig) }
|
235
|
+
end
|
236
|
+
|
237
|
+
def write_files(code, sig)
|
238
|
+
write_c(code)
|
239
|
+
write_ffi(sig)
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
244
|
+
def inline(code = "", options = {})
|
245
|
+
__inliner_make_directory
|
246
|
+
builder = Builder.new(self, code, options)
|
247
|
+
yield builder if block_given?
|
248
|
+
builder.build
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def __inliner_make_directory
|
254
|
+
FileUtils.mkdir(Inliner.directory) unless (File.exists?(Inliner.directory) && File.directory?(Inliner.directory))
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|