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 +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
|