ffi-inliner 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/History.txt +15 -0
- data/README.rdoc +5 -3
- data/Rakefile +2 -2
- data/lib/ffi-inliner/inliner.rb +56 -14
- data/lib/ffi-inliner/version.rb +1 -1
- data/spec/ffi-inliner/inliner_spec.rb +95 -29
- metadata +4 -4
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
|
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] << '
|
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'
|
data/lib/ffi-inliner/inliner.rb
CHANGED
@@ -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
|
-
|
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 = { :
|
113
|
-
@compiler = options[:
|
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
|
-
|
224
|
-
|
225
|
-
|
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)
|
data/lib/ffi-inliner/version.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
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
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
184
|
-
|
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.
|
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-
|
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.
|
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
|