rdp-ffi-inliner 0.2.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *~
2
+ *.*~
3
+ ^#
4
+ pkg
5
+ announcement.txt
6
+ cache
7
+
data/History.txt ADDED
@@ -0,0 +1,47 @@
1
+ == 0.2.4 / 2010-06-09
2
+
3
+ * Bug fix
4
+ * Fix Issue #3: Added missing 'require' for FileUtils
5
+ * Fix Issue #5: instance variable @libraries not initialized warning
6
+
7
+ == 0.2.3 / 2009-12-07
8
+
9
+ * Minor enhancements
10
+ * Add Builder#include method to include library and user header files
11
+ * Add Builder#library method to link with custom libraries
12
+ * Add experimental support for wrapping C++ through C. When using
13
+ GPlusPlus compiler, the inliner will automatically wraps C code
14
+ inside an extern "C" block.
15
+ * Bug fix
16
+ * Fix Issue#2: TCC generated shared libraries do not work in Windows
17
+ (thanks to Luis Lavena)
18
+ * Changes in the API
19
+ * Rename #compiler in #use_compiler
20
+ * Now #compiler returns the current compiler
21
+
22
+ == 0.2.2 / 2009-08-05
23
+
24
+ * Bug fix
25
+ * Fix ffi gem version dependency issue
26
+
27
+ == 0.2.1 / 2009-08-05
28
+
29
+ * Bug fix
30
+ * Add fPIC option to gcc compiler to fix AMD64 compilation issue
31
+
32
+ == 0.2.0 / 2009-07-20
33
+
34
+ * Major enhancements
35
+ * Add support for different compiler backends
36
+ * Improve compatibility with Windows and OSX boxes
37
+ * Minor enhancements
38
+ * Add support for configuration block syntax
39
+ * Remove dependency from ffi-tcc and libtcc
40
+ * Improve compatibility with MRI 1.9.1
41
+ * Cleanup and code refactoring
42
+
43
+ == 0.1.0 / 2009-07-07
44
+
45
+ * 1 major enhancement
46
+ * Birthday!
47
+
data/README.rdoc ADDED
@@ -0,0 +1,87 @@
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 implementations!
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) or that don't have it installed
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]. For a
38
+ "real" world example you may be interested to
39
+ ffi-life[http://github.com/remogatto/ffi-life].
40
+
41
+ == REQUIREMENTS:
42
+
43
+ * ffi >= 0.4.0
44
+ * gcc and/or tcc >= 0.9.25 (optional)
45
+
46
+ == DOWNLOAD/INSTALL:
47
+
48
+ From rubyforge:
49
+
50
+ [sudo] gem install ffi-inliner
51
+
52
+ or from github:
53
+
54
+ git clone git://github.com/remogatto/ffi-inliner
55
+ cd ffi-inliner
56
+ sudo rake gem:install
57
+
58
+ == CREDITS
59
+
60
+ This software is inspired to
61
+ RubyInline[http://www.zenspider.com/ZSS/Products/RubyInline/] by Ryan
62
+ Davis. Thank you Ryan.
63
+
64
+ == LICENSE:
65
+
66
+ (The MIT License)
67
+
68
+ Copyright (c) 2009 Andrea Fazzi
69
+
70
+ Permission is hereby granted, free of charge, to any person obtaining
71
+ a copy of this software and associated documentation files (the
72
+ 'Software'), to deal in the Software without restriction, including
73
+ without limitation the rights to use, copy, modify, merge, publish,
74
+ distribute, sublicense, and/or sell copies of the Software, and to
75
+ permit persons to whom the Software is furnished to do so, subject to
76
+ the following conditions:
77
+
78
+ The above copyright notice and this permission notice shall be
79
+ included in all copies or substantial portions of the Software.
80
+
81
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
82
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
83
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
84
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
85
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
86
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
87
+ 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 = 'rdp-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-inliner'
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] << 'ruby-ffi@googlegroups.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.4.0'
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,6 @@
1
+ require 'rubygems'
2
+ require 'digest/md5'
3
+ require 'fileutils'
4
+ require 'rbconfig'
5
+ require 'ffi'
6
+ require 'ffi-inliner/inliner'
@@ -0,0 +1,308 @@
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, libraries)
39
+ @mod = mod.name.gsub(/[:#<>\/]/, '_')
40
+ @code = code
41
+ @libraries = libraries
42
+ end
43
+ def cached?
44
+ exists?
45
+ end
46
+ def exists?
47
+ File.exists?(c_fn)
48
+ end
49
+ def base_fn
50
+ File.join(Inliner.directory, "#{@mod}_#{(Digest::MD5.new << @code << @libraries.to_s).to_s[0, 4]}")
51
+ end
52
+ %w(c rb log).each do |ext|
53
+ define_method("#{ext}_fn") { "#{base_fn}.#{ext}" }
54
+ end
55
+ def so_fn
56
+ "#{base_fn}#{LIB_EXT}"
57
+ end
58
+ end
59
+
60
+ module Compilers
61
+ class Compiler
62
+ attr_reader :progname
63
+ def self.check_and_create(fm = nil, libraries = nil)
64
+ compiler = new(fm, libraries)
65
+ unless compiler.exists?
66
+ raise "Can't find compiler #{compiler.class}"
67
+ else
68
+ compiler
69
+ end
70
+ end
71
+ def initialize(fm = nil, libraries = nil)
72
+ @fm = fm
73
+ @libraries = libraries
74
+ @progname = cmd.split.first
75
+ end
76
+ def compile
77
+ puts 'running:' + cmd if $VERBOSE
78
+ raise "Compile error! See #{@fm.log_fn}" unless system(cmd)
79
+ end
80
+ private
81
+ def libs
82
+ @libraries.inject("") { |str, lib| str << "-l#{lib} " } if @libraries
83
+ end
84
+ end
85
+
86
+ class GCC < Compiler
87
+ def exists?
88
+ IO.popen("#{@progname} 2>&1") { |f| f.gets } ? true : false
89
+ end
90
+ def ldshared
91
+ if Config::CONFIG['target_os'] =~ /darwin/
92
+ 'gcc -dynamic -bundle -fPIC'
93
+ else
94
+ 'gcc -shared -fPIC'
95
+ end
96
+ end
97
+ def cmd
98
+ "#{ldshared} #{libs} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
99
+ end
100
+ end
101
+
102
+ class GPlusPlus < GCC
103
+ def ldshared
104
+ if Config::CONFIG['target_os'] =~ /darwin/
105
+ 'g++ -dynamic -bundle -fPIC'
106
+ elsif ENV['OS'] == 'Windows_NT'
107
+ # windows requires use of sh first
108
+ def cmd
109
+ "sh -c 'g++ -shared -fPIC #{libs} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\"' 2>\"#{@fm.log_fn}\""
110
+ end
111
+ else
112
+ # Linux
113
+ 'g++ -shared -fPIC'
114
+ end
115
+ end
116
+ end
117
+
118
+ class TCC < Compiler
119
+ def exists?
120
+ IO.popen("#{@progname}") { |f| f.gets } ? true : false
121
+ end
122
+ def cmd
123
+ if Config::CONFIG['target_os'] =~ /mswin|mingw/
124
+ "tcc -rdynamic -shared #{libs} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
125
+ else
126
+ "tcc -shared #{libs} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ class Builder
133
+ attr_reader :code, :compiler
134
+ def initialize(mod, code = "", options = {})
135
+ make_pointer_types
136
+ @mod = mod
137
+ @code = code
138
+ @sig = [parse_signature(@code)] unless @code.empty?
139
+ options = { :use_compiler => Compilers::GCC }.merge(options)
140
+ @compiler = options[:use_compiler]
141
+ @libraries = []
142
+ end
143
+
144
+ def map(type_map)
145
+ @types.merge!(type_map)
146
+ end
147
+
148
+ def include(fn, options = {})
149
+ options[:quoted] ? @code << "#include \"#{fn}\"\n" : @code << "#include <#{fn}>\n"
150
+ end
151
+
152
+ def library(*libraries)
153
+ (@libraries ||= []).concat(libraries)
154
+ end
155
+
156
+ def c(code)
157
+ (@sig ||= []) << parse_signature(code)
158
+ @code << (@compiler == Compilers::GPlusPlus ? "extern \"C\" {\n#{code}\n}" : code )
159
+ end
160
+
161
+ def c_raw(code)
162
+ @code << code
163
+ end
164
+
165
+ def use_compiler(compiler)
166
+ @compiler = compiler
167
+ end
168
+
169
+ def struct(ffi_struct)
170
+ @code << "typedef struct {"
171
+ ffi_struct.layout.fields.each do |field|
172
+ @code << "#{field} #{field.name};\n"
173
+ end
174
+ @code << "} #{ffi_struct.class.name}"
175
+ end
176
+
177
+ def build
178
+ @fm = FilenameManager.new(@mod, @code, @libraries)
179
+ @compiler = @compiler.check_and_create(@fm, @libraries)
180
+ unless @fm.cached?
181
+ write_files(@code, @sig)
182
+ @compiler.compile
183
+ @mod.instance_eval generate_ffi(@sig)
184
+ else
185
+ @mod.instance_eval(File.read(@fm.rb_fn))
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ def make_pointer_types
192
+ @types = C_TO_FFI.dup
193
+ C_TO_FFI.each_key do |k|
194
+ @types["#{k} *"] = :pointer
195
+ end
196
+ end
197
+
198
+ def cached?(name, code)
199
+ File.exists?(cname(name, code))
200
+ end
201
+
202
+ def to_ffi_type(c_type)
203
+ @types[c_type]
204
+ end
205
+
206
+ # Based on RubyInline code by Ryan Davis
207
+ # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
208
+ def strip_comments(code)
209
+ # strip c-comments
210
+ src = code.gsub(%r%\s*/\*.*?\*/%m, '')
211
+ # strip cpp-comments
212
+ src = src.gsub(%r%^\s*//.*?\n%, '')
213
+ src = src.gsub(%r%[ \t]*//[^\n]*%, '')
214
+ src
215
+ end
216
+
217
+ # Based on RubyInline code by Ryan Davis
218
+ # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
219
+ def parse_signature(code)
220
+
221
+ sig = strip_comments(code)
222
+
223
+ # strip preprocessor directives
224
+ sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
225
+ # strip {}s
226
+ sig.gsub!(/\{[^\}]*\}/, '{ }')
227
+ # clean and collapse whitespace
228
+ sig.gsub!(/\s+/, ' ')
229
+
230
+ # types = 'void|int|char|char\s\*|void\s\*'
231
+ types = @types.keys.map{|x| Regexp.escape(x)}.join('|')
232
+ sig = sig.gsub(/\s*\*\s*/, ' * ').strip
233
+
234
+ if /(#{types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
235
+ return_type, function_name, arg_string = $1, $2, $3
236
+ args = []
237
+ arg_string.split(',').each do |arg|
238
+
239
+ # helps normalize into 'char * varname' form
240
+ arg = arg.gsub(/\s*\*\s*/, ' * ').strip
241
+
242
+ if /(((#{types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
243
+ args.push($1)
244
+ elsif arg != "void" then
245
+ warn "WAR\NING: '#{arg}' not understood"
246
+ end
247
+ end
248
+
249
+ arity = args.size
250
+
251
+ return {
252
+ 'return' => return_type,
253
+ 'name' => function_name,
254
+ 'args' => args,
255
+ 'arity' => arity
256
+ }
257
+ end
258
+
259
+ raise SyntaxError, "Can't parse signature: #{sig}"
260
+
261
+ end
262
+
263
+ def generate_ffi(sig)
264
+
265
+ ffi_code = <<PREAMBLE
266
+ extend FFI::Library
267
+ ffi_lib '#{@fm.so_fn}'
268
+
269
+ PREAMBLE
270
+
271
+ unless sig.nil?
272
+ sig.each do |s|
273
+ args = s['args'].map { |arg| ":#{to_ffi_type(arg)}" }.join(',')
274
+ ffi_code << "attach_function '#{s['name']}', [#{args}], :#{to_ffi_type(s['return'])}\n"
275
+ end
276
+ end
277
+
278
+ ffi_code
279
+ end
280
+ def write_c(code)
281
+ File.open(@fm.c_fn, 'w') { |f| f << code }
282
+ end
283
+
284
+ def write_ffi(sig)
285
+ File.open(@fm.rb_fn, 'w') { |f| f << generate_ffi(sig) }
286
+ end
287
+
288
+ def write_files(code, sig)
289
+ write_c(code)
290
+ write_ffi(sig)
291
+ end
292
+
293
+ end
294
+
295
+ def inline(code = "", options = {})
296
+ __inliner_make_directory
297
+ builder = Builder.new(self, code, options)
298
+ yield builder if block_given?
299
+ builder.build
300
+ end
301
+
302
+ private
303
+
304
+ def __inliner_make_directory
305
+ FileUtils.mkdir(Inliner.directory) unless (File.exists?(Inliner.directory) && File.directory?(Inliner.directory))
306
+ end
307
+
308
+ end