ffi-inliner 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *~
2
+ *.*~
3
+ ^#
4
+ pkg
5
+ announcement.txt
6
+
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
+
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'digest/md5'
3
+ require 'rbconfig'
4
+ require 'ffi'
5
+ require 'ffi-inliner/inliner'
@@ -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