ffi-inliner 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|