ffi-inliner 0.2.2 → 0.2.3

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 CHANGED
@@ -3,4 +3,5 @@
3
3
  ^#
4
4
  pkg
5
5
  announcement.txt
6
+ cache
6
7
 
data/History.txt CHANGED
@@ -1,3 +1,18 @@
1
+ == 0.2.3 / 2009-12-07
2
+
3
+ * Minor enhancements
4
+ * Add Builder#include method to include library and user header files
5
+ * Add Builder#library method to link with custom libraries
6
+ * Add experimental support for wrapping C++ through C. When using
7
+ GPlusPlus compiler, the inliner will automatically wraps C code
8
+ inside an extern "C" block.
9
+ * Bug fix
10
+ * Fix Issue#2: TCC generated shared libraries do not work in Windows
11
+ (thanks to Luis Lavena)
12
+ * Changes in the API
13
+ * Rename #compiler in #use_compiler
14
+ * Now #compiler returns the current compiler
15
+
1
16
  == 0.2.2 / 2009-08-05
2
17
 
3
18
  * Bug fix
data/README.rdoc CHANGED
@@ -10,11 +10,11 @@ With ffi-inliner you can run C code within your ruby script.
10
10
 
11
11
  * Mix C snippets in your Ruby code and gulp it on the fly!
12
12
  * It's based on Ruby-FFI so the C code you inject is portable across
13
- Ruby implementation!
13
+ Ruby implementations!
14
14
  * Yep, it means that you can run it on JRuby too!
15
15
  * Fast compilation through tcc[http://bellard.org/tcc/]
16
16
  * But it can use the system's compiler (e.g. gcc) on those platforms
17
- that don't support tcc (e.g. OSX)
17
+ that don't support tcc (e.g. OSX) or that don't have it installed
18
18
 
19
19
  == SYNOPSIS:
20
20
 
@@ -34,7 +34,9 @@ With ffi-inliner you can run C code within your ruby script.
34
34
  Foo.new.say_hello('foos')
35
35
 
36
36
  For other hints see the examples/ folder or visit the
37
- wiki[http://wiki.github.com/remogatto/ffi-inliner/tutorial]
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].
38
40
 
39
41
  == REQUIREMENTS:
40
42
 
data/Rakefile CHANGED
@@ -26,11 +26,11 @@ PROJ.version = Inliner::VERSION
26
26
 
27
27
  PROJ.readme_file = 'README.rdoc'
28
28
 
29
- PROJ.rubyforge.name = 'ffi'
29
+ PROJ.rubyforge.name = 'ffi-inliner'
30
30
 
31
31
  PROJ.ann.paragraphs << 'FEATURES' << 'SYNOPSIS' << 'REQUIREMENTS' << 'DOWNLOAD/INSTALL' << 'CREDITS'
32
32
  PROJ.ann.email[:from] = 'andrea.fazzi@alcacoop.it'
33
- PROJ.ann.email[:to] << 'dev@ruby-ffi.kenai.com' << 'users@ruby-ffi.kenai.com'
33
+ PROJ.ann.email[:to] << 'ruby-ffi@googlegroups.com'
34
34
  PROJ.ann.email[:server] = 'smtp.gmail.com'
35
35
 
36
36
  PROJ.spec.opts << '--color' << '-fs'
@@ -59,21 +59,26 @@ module Inliner
59
59
  module Compilers
60
60
  class Compiler
61
61
  attr_reader :progname
62
- def self.check_and_create(fm = nil)
63
- compiler = new(fm)
62
+ def self.check_and_create(fm = nil, libraries = nil)
63
+ compiler = new(fm, libraries)
64
64
  unless compiler.exists?
65
65
  raise "Can't find compiler #{compiler.class}"
66
66
  else
67
67
  compiler
68
68
  end
69
69
  end
70
- def initialize(fm = nil)
70
+ def initialize(fm = nil, libraries = nil)
71
71
  @fm = fm
72
+ @libraries = libraries
72
73
  @progname = cmd.split.first
73
74
  end
74
75
  def compile
75
76
  raise "Compile error! See #{@fm.log_fn}" unless system(cmd)
76
77
  end
78
+ private
79
+ def libs
80
+ @libraries.inject("") { |str, lib| str << "-l#{lib} " } if @libraries
81
+ end
77
82
  end
78
83
 
79
84
  class GCC < Compiler
@@ -88,7 +93,17 @@ module Inliner
88
93
  end
89
94
  end
90
95
  def cmd
91
- "#{ldshared} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
96
+ "#{ldshared} #{libs} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
97
+ end
98
+ end
99
+
100
+ class GPlusPlus < GCC
101
+ def ldshared
102
+ if Config::CONFIG['target_os'] =~ /darwin/
103
+ 'g++ -dynamic -bundle -fPIC'
104
+ else
105
+ 'g++ -shared -fPIC'
106
+ end
92
107
  end
93
108
  end
94
109
 
@@ -97,29 +112,41 @@ module Inliner
97
112
  IO.popen("#{@progname}") { |f| f.gets } ? true : false
98
113
  end
99
114
  def cmd
100
- "tcc -shared -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
115
+ if Config::CONFIG['target_os'] =~ /mswin|mingw/
116
+ "tcc -rdynamic -shared #{libs} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
117
+ else
118
+ "tcc -shared #{libs} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
119
+ end
101
120
  end
102
121
  end
103
122
  end
104
123
 
105
124
  class Builder
106
- attr_reader :code
125
+ attr_reader :code, :compiler
107
126
  def initialize(mod, code = "", options = {})
108
127
  make_pointer_types
109
128
  @mod = mod
110
129
  @code = code
111
130
  @sig = [parse_signature(@code)] unless @code.empty?
112
- options = { :compiler => Compilers::GCC }.merge(options)
113
- @compiler = options[:compiler]
131
+ options = { :use_compiler => Compilers::GCC }.merge(options)
132
+ @compiler = options[:use_compiler]
114
133
  end
115
134
 
116
135
  def map(type_map)
117
136
  @types.merge!(type_map)
118
137
  end
119
138
 
139
+ def include(fn, options = {})
140
+ options[:quoted] ? @code << "#include \"#{fn}\"\n" : @code << "#include <#{fn}>\n"
141
+ end
142
+
143
+ def library(*libraries)
144
+ (@libraries ||= []).concat(libraries)
145
+ end
146
+
120
147
  def c(code)
121
148
  (@sig ||= []) << parse_signature(code)
122
- @code << code
149
+ @code << (@compiler == Compilers::GPlusPlus ? "extern \"C\" {\n#{code}\n}" : code )
123
150
  end
124
151
 
125
152
  def c_raw(code)
@@ -130,9 +157,17 @@ module Inliner
130
157
  @compiler = compiler
131
158
  end
132
159
 
160
+ def struct(ffi_struct)
161
+ @code << "typedef struct {"
162
+ ffi_struct.layout.fields.each do |field|
163
+ @code << "#{field} #{field.name};\n"
164
+ end
165
+ @code << "} #{ffi_struct.class.name}"
166
+ end
167
+
133
168
  def build
134
169
  @fm = FilenameManager.new(@mod, @code)
135
- @compiler = @compiler.check_and_create(@fm)
170
+ @compiler = @compiler.check_and_create(@fm, @libraries)
136
171
  unless @fm.cached?
137
172
  write_files(@code, @sig)
138
173
  @compiler.compile
@@ -173,14 +208,16 @@ module Inliner
173
208
  # Based on RubyInline code by Ryan Davis
174
209
  # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
175
210
  def parse_signature(code)
211
+
176
212
  sig = strip_comments(code)
213
+
177
214
  # strip preprocessor directives
178
215
  sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
179
216
  # strip {}s
180
217
  sig.gsub!(/\{[^\}]*\}/, '{ }')
181
218
  # clean and collapse whitespace
182
219
  sig.gsub!(/\s+/, ' ')
183
-
220
+
184
221
  # types = 'void|int|char|char\s\*|void\s\*'
185
222
  types = @types.keys.map{|x| Regexp.escape(x)}.join('|')
186
223
  sig = sig.gsub(/\s*\*\s*/, ' * ').strip
@@ -215,15 +252,20 @@ module Inliner
215
252
  end
216
253
 
217
254
  def generate_ffi(sig)
255
+
218
256
  ffi_code = <<PREAMBLE
219
257
  extend FFI::Library
220
258
  ffi_lib '#{@fm.so_fn}'
221
259
 
222
260
  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"
261
+
262
+ unless sig.nil?
263
+ sig.each do |s|
264
+ args = s['args'].map { |arg| ":#{to_ffi_type(arg)}" }.join(',')
265
+ ffi_code << "attach_function '#{s['name']}', [#{args}], :#{to_ffi_type(s['return'])}\n"
266
+ end
226
267
  end
268
+
227
269
  ffi_code
228
270
  end
229
271
  def write_c(code)
@@ -1,3 +1,3 @@
1
1
  module Inliner
2
- VERSION = '0.2.2'
2
+ VERSION = '0.2.3'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "../spec_helper"))
2
2
 
3
- describe Inliner do
3
+ describe 'Inliner' do
4
4
 
5
5
  before do
6
6
  module Foo
@@ -75,7 +75,7 @@ describe Inliner do
75
75
 
76
76
  end
77
77
 
78
- it 'should be configured using the block form' do
78
+ it 'should be configured using the block form' do
79
79
  module Foo
80
80
  inline do |builder|
81
81
  builder.c %q{
@@ -114,6 +114,27 @@ describe Inliner do
114
114
  my_struct = MyStruct.new
115
115
  Foo.use_my_struct(my_struct).should == my_struct.to_ptr
116
116
  end
117
+
118
+ it 'should allow users to include header files' do
119
+ module Foo
120
+ inline do |builder|
121
+ builder.include "stdio.h"
122
+ builder.include "local_header.h", :quoted => true
123
+ builder.code.should == "#include <stdio.h>\n#include \"local_header.h\"\n"
124
+ builder.stub!(:build)
125
+ end
126
+ end
127
+ end
128
+
129
+ it 'should allow users to add libraries' do
130
+ module Foo
131
+ inline do |builder|
132
+ builder.library 'foolib1', 'foolib2'
133
+ builder.stub!(:build)
134
+ end
135
+ end
136
+ end
137
+
117
138
  it 'should generate C struct from FFI::Struct' do
118
139
  pending do
119
140
  class MyStruct < FFI::Struct
@@ -138,24 +159,32 @@ EOC
138
159
  end
139
160
  end
140
161
  end
141
- # it 'should use different compiler as specified in the configuration block' do
142
- # tcc = mock('tcc', :exists? => true, :compile => nil)
143
- # Inliner::Compilers::TCC.should_receive(:new).and_return(tcc)
144
- # module Foo
145
- # inline do |builder|
146
- # builder.code = "int func_1() { return 0; }"
147
- # builder.compiler = Inliner::Compilers::TCC
148
- # end
149
- # end
150
- # end
151
-
152
- # it 'should be configured using the hash form' do
153
- # tcc = mock('tcc', :exists? => true, :compile => nil)
154
- # Inliner::Compilers::TCC.should_receive(:new).and_return(tcc)
155
- # module Foo
156
- # inline "int func_1() { return 1; }", :compiler => Inliner::Compilers::TCC
157
- # end
158
- # end
162
+
163
+ it 'should use different compiler as specified in the configuration block' do
164
+ module Foo
165
+ inline do |builder|
166
+ builder.use_compiler Inliner::Compilers::TCC
167
+ builder.c "int func_1() { return 1 + 1; }"
168
+ end
169
+ end
170
+ Foo.func_1.should == 2
171
+ end
172
+
173
+ it 'should return the current compiler' do
174
+ module Foo
175
+ inline do |builder|
176
+ builder.compiler.should == Inliner::Compilers::GCC
177
+ end
178
+ end
179
+ end
180
+
181
+ # it 'should be configured using the hash form' do
182
+ # tcc = mock('tcc', :exists? => true, :compile => nil)
183
+ # Inliner::Compilers::TCC.should_receive(:new).and_return(tcc)
184
+ # module Foo
185
+ # inline "int func_1() { return 1; }", :compiler => Inliner::Compilers::TCC
186
+ # end
187
+ # end
159
188
 
160
189
  it 'should raise errors' do
161
190
  lambda {
@@ -169,18 +198,55 @@ EOC
169
198
  end
170
199
  }.should raise_error(/Compile error/)
171
200
  end
172
-
173
- end
174
201
 
175
- describe Inliner::Compilers::Compiler do
176
- before do
177
- class DummyCC < Inliner::Compilers::Compiler
178
- def cmd
179
- "dummycc -shared"
202
+ describe 'Compiler' do
203
+ before do
204
+ class DummyCC < Inliner::Compilers::Compiler
205
+ def cmd
206
+ "dummycc -shared"
207
+ end
180
208
  end
181
209
  end
210
+ it 'should return the progname' do
211
+ DummyCC.new.progname.should == 'dummycc'
212
+ end
182
213
  end
183
- it 'should return the progname' do
184
- DummyCC.new.progname.should == 'dummycc'
214
+
215
+ describe 'GPlusPlus compiler' do
216
+
217
+ it 'should compile and link a shim C library that encapsulates C++ code' do
218
+
219
+ module Foo
220
+ inline do |builder|
221
+ builder.use_compiler Inliner::Compilers::GPlusPlus
222
+ builder.c_raw <<-code
223
+ #include <iostream>
224
+ #include <string>
225
+ using namespace std;
226
+ class Greeter {
227
+ public:
228
+ Greeter();
229
+ string say_hello();
230
+ };
231
+ Greeter::Greeter() { };
232
+ string Greeter::say_hello() {
233
+ return "Hello foos!";
234
+ };
235
+ code
236
+ builder.map 'char *' => 'string'
237
+ builder.c <<-code
238
+ const char* say_hello()
239
+ {
240
+ Greeter greeter;
241
+ return greeter.say_hello().c_str();
242
+ }
243
+ code
244
+ end
245
+ end
246
+ Foo.say_hello.should == 'Hello foos!'
247
+ end
248
+
185
249
  end
250
+
186
251
  end
252
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-inliner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Fazzi
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-05 00:00:00 +02:00
12
+ date: 2009-12-07 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -89,8 +89,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  version:
90
90
  requirements: []
91
91
 
92
- rubyforge_project: ffi
93
- rubygems_version: 1.3.4
92
+ rubyforge_project: ffi-inliner
93
+ rubygems_version: 1.3.5
94
94
  signing_key:
95
95
  specification_version: 3
96
96
  summary: With ffi-inliner you can run C code within your ruby script