ffi-inline 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #! /usr/bin/env ruby
2
+ require 'rake'
3
+
4
+ task :default => :test
5
+
6
+ task :test do
7
+ Dir.chdir 'spec'
8
+
9
+ sh 'rspec inliner_spec.rb --color --format doc'
10
+ end
data/examples/ex_1.rb ADDED
@@ -0,0 +1,10 @@
1
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../lib')))
2
+ require 'ffi/inliner'
3
+
4
+ class Foo
5
+ extend FFI::Inliner
6
+
7
+ inline 'void say_hello (char* name) { printf("Hello, %s\n", name); }'
8
+ end
9
+
10
+ Foo.new.say_hello('foos')
@@ -0,0 +1,21 @@
1
+ Kernel.load 'lib/ffi/inliner/version.rb'
2
+
3
+ Gem::Specification.new {|s|
4
+ s.name = 'ffi-inline'
5
+ s.version = FFI::Inliner::Version
6
+ s.authors = 'meh.'
7
+ s.email = 'meh@paranoici.org'
8
+ s.homepage = 'http://github.com/meh/ruby-ffi-inline'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'Inline C/C++ in Ruby easily and cleanly.'
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.require_paths = ['lib']
16
+
17
+ s.add_dependency 'ffi', '>=0.4.0'
18
+
19
+ s.add_development_dependency 'rake'
20
+ s.add_development_dependency 'rspec'
21
+ }
@@ -0,0 +1,146 @@
1
+ require 'ffi/inliner/compilers/tcc'
2
+ require 'ffi/inliner/compilers/gcc'
3
+
4
+ module FFI; module Inliner
5
+
6
+ Builder.define :c do
7
+ ToFFI = {
8
+ 'void' => :void,
9
+ 'char' => :char,
10
+ 'unsigned char' => :uchar,
11
+ 'int' => :int,
12
+ 'unsigned int' => :uint,
13
+ 'long' => :long,
14
+ 'unsigned long' => :ulong,
15
+ 'float' => :float,
16
+ 'double' => :double,
17
+ }
18
+
19
+ attr_reader :code, :compiler, :libraries
20
+
21
+ def initialize(code = nil, options = {})
22
+ super(code)
23
+
24
+ @types = ToFFI.dup
25
+ @libraries = options[:libraries] || []
26
+
27
+ @signatures = []
28
+
29
+ use_compiler options[:use_compiler] || options[:compiler] || :gcc
30
+
31
+ @signatures << parse_signature(code) if code && !code.empty?
32
+ end
33
+
34
+ def libraries(*libraries)
35
+ @libraries.concat(libraries)
36
+ end
37
+
38
+ def types(map = nil)
39
+ map ? @types.merge!(map) : @types
40
+ end; alias map types
41
+
42
+ def raw(code, no_line = false)
43
+ return super(code) if no_line
44
+
45
+ whole, path, line = caller.find { |line| line !~ /ffi-inliner/ }.match(/^(.*?):(\d+):in/).to_a
46
+
47
+ super "\n#line #{line.to_i} #{path.inspect}\n" << code
48
+ end; alias c_raw raw
49
+
50
+ def include(path, options = {})
51
+ delimiter = (options[:quoted] || options[:local]) ? ['"', '"'] : ['<', '>']
52
+
53
+ raw "#include #{delimiter.first}#{path}#{delimiter.last}\n", true
54
+ end
55
+
56
+ def typedef (from, to)
57
+ raw "typedef #{from} #{to};"
58
+ end
59
+
60
+ def function(code, signature = nil)
61
+ parsed = parse_signature(code)
62
+
63
+ if signature
64
+ parsed[:arguments] = signature[:arguments] if signature[:arguments]
65
+ parsed[:return] = signature[:return] if signature[:return]
66
+ end
67
+
68
+ @signatures << parsed
69
+
70
+ raw code
71
+ end; alias c function
72
+
73
+ def struct(ffi_struct)
74
+ raw %{
75
+ typedef struct {#{
76
+ ffi_struct.layout.fields.map {|field|
77
+ "#{field} #{field.name};"
78
+ }.join("\n")
79
+ }} #{ffi_struct.class.name}
80
+ }, true
81
+ end
82
+
83
+ def to_ffi_type(type, mod = nil)
84
+ raise ArgumentError, 'type is nil' if type.nil?
85
+
86
+ if type.is_a?(Symbol) || type.is_a?(FFI::Type) || (type.is_a?(Class) && type.ancestors.include?(FFI::Struct))
87
+ type
88
+ elsif @types[type]
89
+ @types[type]
90
+ elsif type.to_s.include? ?*
91
+ :pointer
92
+ elsif ((mod || FFI).find_type(type.to_sym) rescue false)
93
+ type.to_sym
94
+ else
95
+ raise "type #{type} not supported"
96
+ end
97
+ end
98
+
99
+ def shared_object
100
+ @compiler.compile(@code, @libraries)
101
+ end
102
+
103
+ def signatures
104
+ @signatures
105
+ end
106
+
107
+ private
108
+
109
+ # Based on RubyInline code by Ryan Davis
110
+ # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
111
+ def strip_comments(code)
112
+ code.gsub(%r(\s*/\*.*?\*/)m, '').
113
+ gsub(%r(^\s*//.*?\n), '').
114
+ gsub(%r([ \t]*//[^\n]*), '')
115
+ end
116
+
117
+ # Based on RubyInline code by Ryan Davis
118
+ # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
119
+ def parse_signature(code)
120
+ sig = strip_comments(code)
121
+
122
+ sig.gsub!(/^\s*\#.*(\\\n.*)*/, '') # strip preprocessor directives
123
+ sig.gsub!(/\s*\{.*/m, '') # strip function body
124
+ sig.gsub!(/\s+/, ' ') # clean and collapse whitespace
125
+ sig.gsub!(/\s*\*\s*/, ' * ') # clean pointers
126
+ sig.gsub!(/\s*const\s*/, '') # remove const
127
+ sig.strip!
128
+
129
+ whole, return_type, function_name, arg_string = sig.match(/(.*?(?:\ \*)?)\s*(\w+)\s*\(([^)]*)\)/).to_a
130
+
131
+ raise SyntaxError, "cannot parse signature: #{sig}" unless whole
132
+
133
+ args = arg_string.split(',').map {|arg|
134
+ # helps normalize into 'char * varname' form
135
+ arg = arg.gsub(/\s*\*\s*/, ' * ').strip
136
+
137
+ whole, type = arg.gsub(/\s*\*\s*/, ' * ').strip.match(/(((.*?(?:\ \*)?)\s*\*?)+)\s+(\w+)\s*$/).to_a
138
+
139
+ type
140
+ }
141
+
142
+ Signature.new(return_type, function_name, args, args.empty? ? -1 : args.length)
143
+ end
144
+ end
145
+
146
+ end; end
@@ -0,0 +1,27 @@
1
+ require 'ffi/inliner/builders/c'
2
+ require 'ffi/inliner/compilers/gxx'
3
+
4
+ module FFI; module Inliner
5
+
6
+ Builder.define Builder[:c], :cplusplus, :cxx, :cpp, 'c++' do
7
+ def initialize(code = "", options = {})
8
+ super(code, options) rescue nil
9
+
10
+ use_compiler options[:use_compiler] || options[:compiler] || :gxx
11
+ end
12
+
13
+ def function(code, signature = nil)
14
+ parsed = parse_signature(code)
15
+
16
+ if signature
17
+ parsed[:arguments] = signature[:arguments] if signature[:arguments]
18
+ parsed[:return] = signature[:return] if signature[:return]
19
+ end
20
+
21
+ @signatures << parsed
22
+
23
+ raw 'extern "C" {' << code << '}' << "\n"
24
+ end
25
+ end
26
+
27
+ end; end
@@ -0,0 +1,98 @@
1
+ module FFI; module Inliner
2
+
3
+ Signature = ::Struct.new(:return, :name, :arguments, :arity)
4
+
5
+ class Builder
6
+ @builders = []
7
+
8
+ def self.[](name)
9
+ return name if name.is_a?(Builder)
10
+
11
+ @builders.find {|builder|
12
+ builder.name.downcase == name.downcase ||
13
+ builder.aliases.any? { |ali| ali.downcase == name.downcase }
14
+ }
15
+ end
16
+
17
+ def self.define (name, *aliases, &block)
18
+ inherit_from = self
19
+
20
+ if name.is_a?(Builder)
21
+ name = name.class
22
+ end
23
+
24
+ if name.is_a?(Class)
25
+ inherit_from = name
26
+ name = aliases.shift
27
+ end
28
+
29
+ @builders << Class.new(inherit_from, &block).tap {|k|
30
+ k.instance_eval {
31
+ define_singleton_method :name do name end
32
+ define_singleton_method :aliases do aliases end
33
+ }
34
+ }
35
+ end
36
+
37
+ attr_reader :code, :compiler
38
+
39
+ def initialize(code = "")
40
+ @code = code
41
+ @evals = []
42
+ end
43
+
44
+ def use_compiler(compiler)
45
+ @compiler = Compiler[compiler]
46
+ end
47
+
48
+ def raw(code)
49
+ @code << code
50
+ end
51
+
52
+ def eval(&block)
53
+ @evals << block
54
+ end
55
+
56
+ def to_ffi_type(type)
57
+ raise 'the Builder has not been specialized'
58
+ end
59
+
60
+ def shared_object
61
+ raise 'the Builder has not been specialized'
62
+ end
63
+
64
+ def signatures
65
+ raise 'the Builder has not been specialized'
66
+ end
67
+
68
+ def symbols
69
+ signatures.map { |s| s.name.to_sym }
70
+ end
71
+
72
+ def build
73
+ builder = self
74
+ blocks = @evals
75
+
76
+ mod = Module.new
77
+ mod.instance_eval {
78
+ extend FFI::Library
79
+
80
+ ffi_lib builder.shared_object
81
+
82
+ blocks.each { |block| instance_eval &block }
83
+
84
+ builder.signatures.each {|s|
85
+ attach_function s.name, s.arguments.compact.map {|a|
86
+ builder.to_ffi_type(a, self)
87
+ }, builder.to_ffi_type(s.return, self)
88
+ }
89
+ }
90
+
91
+ mod
92
+ end
93
+ end
94
+
95
+ end; end
96
+
97
+ require 'ffi/inliner/builders/c'
98
+ require 'ffi/inliner/builders/cpp'
@@ -0,0 +1,57 @@
1
+ module FFI; module Inliner
2
+
3
+ Compiler.define :gcc do
4
+ def exists?
5
+ `gcc -v 2>&1'`; $?.success?
6
+ end
7
+
8
+ def compile (code, libraries = [])
9
+ @code = code
10
+ @libraries = libraries
11
+
12
+ return output if File.exists?(output)
13
+
14
+ unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
15
+ "sh -c '#{ldshared} -o #{output.shellescape} #{input.shellescape} #{libs}' 2>#{log.shellescape}"
16
+ else
17
+ "#{ldshared} -o #{output.shellescape} #{input.shellescape} #{libs} 2>#{log.shellescape}"
18
+ end)
19
+ raise CompilationError.new(log)
20
+ end
21
+
22
+ output
23
+ end
24
+
25
+ private
26
+ def digest
27
+ Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
28
+ end
29
+
30
+ def input
31
+ File.join(Inliner.directory, "#{digest}.c").tap {|path|
32
+ File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
33
+ }
34
+ end
35
+
36
+ def output
37
+ File.join(Inliner.directory, "#{digest}.#{Compiler::Extension}")
38
+ end
39
+
40
+ def log
41
+ File.join(Inliner.directory, "#{digest}.log")
42
+ end
43
+
44
+ def ldshared
45
+ if RbConfig::CONFIG['target_os'] =~ /darwin/
46
+ "gcc -dynamic -bundle -fPIC #{options}"
47
+ else
48
+ "gcc -shared -fPIC #{options}"
49
+ end
50
+ end
51
+
52
+ def libs
53
+ @libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
54
+ end
55
+ end
56
+
57
+ end; end
@@ -0,0 +1,23 @@
1
+ module FFI; module Inliner
2
+
3
+ Compiler.define Compiler[:gcc], :gxx, 'g++' do
4
+ def exists?
5
+ `g++ -v 2>&1'`; $?.success?
6
+ end
7
+
8
+ def input
9
+ File.join(Inliner.directory, "#{digest}.cpp").tap {|path|
10
+ File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
11
+ }
12
+ end
13
+
14
+ def ldshared
15
+ if RbConfig::CONFIG['target_os'] =~ /darwin/
16
+ "g++ -dynamic -bundle -fPIC #{options}"
17
+ else
18
+ "g++ -shared -fPIC #{options}"
19
+ end
20
+ end
21
+ end
22
+
23
+ end; end
@@ -0,0 +1,57 @@
1
+ module FFI; module Inliner
2
+
3
+ Compiler.define :tcc do
4
+ def exists?
5
+ `tcc -v 2>&1'`; $?.success?
6
+ end
7
+
8
+ def compile (code, libraries = [])
9
+ @code = code
10
+ @libraries = libraries
11
+
12
+ return output if File.exists?(output)
13
+
14
+ unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
15
+ "sh -c '#{ldshared} #{libs} -o #{output.shellescape} #{input.shellescape}' 2>#{log.shellescape}"
16
+ else
17
+ "#{ldshared} #{libs} -o #{output.shellescape} #{input.shellescape} 2>#{log.shellescape}"
18
+ end)
19
+ raise CompilationError.new(log)
20
+ end
21
+
22
+ output
23
+ end
24
+
25
+ private
26
+ def digest
27
+ Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
28
+ end
29
+
30
+ def input
31
+ File.join(Inliner.directory, "#{digest}.c").tap {|path|
32
+ File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
33
+ }
34
+ end
35
+
36
+ def output
37
+ File.join(Inliner.directory, "#{digest}.#{Compiler::Extension}")
38
+ end
39
+
40
+ def log
41
+ File.join(Inliner.directory, "#{digest}.log")
42
+ end
43
+
44
+ def ldshared
45
+ if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
46
+ "tcc -rdynamic -shared -fPIC #{options}"
47
+ else
48
+ "tcc -shared #{options}"
49
+ end
50
+ end
51
+
52
+ def libs
53
+ @libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
54
+ end
55
+ end
56
+
57
+ end; end
@@ -0,0 +1,53 @@
1
+ module FFI; module Inliner
2
+
3
+ class Compiler
4
+ Extension = case RbConfig::CONFIG['target_os']
5
+ when /darwin/ then 'dylib'
6
+ when /mswin|mingw/ then 'dll'
7
+ else 'so'
8
+ end
9
+
10
+ @compilers = []
11
+
12
+ def self.[] (name)
13
+ return name if name.is_a?(Compiler)
14
+
15
+ @compilers.find {|compiler|
16
+ compiler.name.downcase == name.downcase ||
17
+ compiler.aliases.any? { |ali| ali.downcase == name.downcase }
18
+ }
19
+ end
20
+
21
+ def self.define (name, *aliases, &block)
22
+ inherit_from = self
23
+
24
+ if name.is_a?(Compiler)
25
+ name = name.class
26
+ end
27
+
28
+ if name.is_a?(Class)
29
+ inherit_from = name
30
+ name = aliases.shift
31
+ end
32
+
33
+ @compilers << Class.new(inherit_from, &block).new(name, *aliases)
34
+ end
35
+
36
+ attr_reader :name, :aliases
37
+ attr_accessor :options
38
+
39
+ def initialize(name, *aliases)
40
+ @name = name
41
+ @aliases = aliases
42
+ end
43
+
44
+ def exists?
45
+ raise 'the Compiler has not been specialized'
46
+ end
47
+
48
+ def compile
49
+ raise 'the Compiler has not been specialized'
50
+ end
51
+ end
52
+
53
+ end; end
@@ -0,0 +1,11 @@
1
+ class CompilationError < RuntimeError
2
+ def initialize (path)
3
+ @path = path
4
+
5
+ super "compile error: see logs at #{@path}"
6
+ end
7
+
8
+ def log
9
+ File.read(@path)
10
+ end
11
+ end
@@ -0,0 +1,73 @@
1
+ require 'ffi/inliner/error'
2
+
3
+ module FFI
4
+
5
+ module Inliner
6
+ def self.directory
7
+ if ENV['FFI_INLINER_PATH'] && !ENV['FFI_INLINER_PATH'].empty?
8
+ @directory = ENV['FFI_INLINER_PATH']
9
+ else
10
+ require 'tmpdir'
11
+ @directory ||= File.expand_path(File.join(Dir.tmpdir, ".ffi-inliner-#{Process.uid}"))
12
+ end
13
+
14
+ if File.exists?(@directory) && !File.directory?(@directory)
15
+ raise 'the FFI_INLINER_PATH exists and is not a directory'
16
+ end
17
+
18
+ if !File.exists?(@directory)
19
+ FileUtils.mkdir(@directory)
20
+ end
21
+
22
+ @directory
23
+ end
24
+
25
+ def inline(*args, &block)
26
+ if self.class == Class
27
+ instance_inline(*args, &block)
28
+ else
29
+ singleton_inline(*args, &block)
30
+ end
31
+ end
32
+
33
+ def singleton_inline(*args)
34
+ options = args.last.is_a?(Hash) ? args.pop : {}
35
+
36
+ language, code = if args.length == 2
37
+ args
38
+ else
39
+ block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
40
+ end
41
+
42
+ builder = Builder[language].new(code, options)
43
+ yield builder if block_given?
44
+ mod = builder.build
45
+
46
+ builder.symbols.each {|sym|
47
+ define_singleton_method sym, &mod.method(sym)
48
+ }
49
+ end
50
+
51
+ def instance_inline(*args)
52
+ options = args.last.is_a?(Hash) ? args.pop : {}
53
+
54
+ language, code = if args.length == 2
55
+ args
56
+ else
57
+ block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
58
+ end
59
+
60
+ builder = Builder[language].new(code, options)
61
+ yield builder if block_given?
62
+ mod = builder.build
63
+
64
+ builder.symbols.each {|sym|
65
+ define_method sym, &mod.method(sym)
66
+ }
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ require 'ffi/inliner/compilers'
73
+ require 'ffi/inliner/builders'
@@ -0,0 +1,7 @@
1
+ module FFI
2
+
3
+ module Inliner
4
+ Version = '0.0.1'
5
+ end
6
+
7
+ end
@@ -0,0 +1,8 @@
1
+ require 'digest/sha1'
2
+ require 'fileutils'
3
+ require 'rbconfig'
4
+ require 'shellwords'
5
+
6
+ require 'rubygems'
7
+ require 'ffi'
8
+ require 'ffi/inliner/inliner'
@@ -0,0 +1,273 @@
1
+ require 'ffi/inliner'
2
+
3
+ describe FFI::Inliner do
4
+ before do
5
+ module Foo
6
+ extend FFI::Inliner
7
+ end
8
+ end
9
+
10
+ it 'should extend the module with inline methods' do
11
+ module Foo
12
+ inline %{
13
+ long factorial (int max) {
14
+ int i = max, result = 1;
15
+
16
+ while (i >= 2) {
17
+ result *= i--;
18
+ }
19
+
20
+ return result;
21
+ }
22
+ }
23
+
24
+ inline 'int simple_math() { return 1 + 1; }'
25
+ end
26
+
27
+ Foo.factorial(4).should == 24
28
+ Foo.simple_math.should == 2
29
+ end
30
+
31
+ it 'should correctly parse function signature' do
32
+ module Foo
33
+ inline %{
34
+ void* func_1 (void* ptr, unsigned int i, unsigned long l, char *c) {
35
+ return ptr;
36
+ }
37
+ }
38
+ end
39
+
40
+ ptr = FFI::MemoryPointer.new(:int)
41
+ Foo.func_1(ptr, 0xff, 0xffff, FFI::MemoryPointer.from_string('c')).should == ptr
42
+ end
43
+
44
+ it 'should load cached libraries' do
45
+ File.should_receive(:open).once
46
+
47
+ module Foo
48
+ inline "void* cached_func() {}"
49
+ end
50
+
51
+ module Foo
52
+ inline "void* cached_func() {}"
53
+ end
54
+ end
55
+
56
+ it 'should recompile if the code is updated' do
57
+ module Foo
58
+ inline "int updated_func() { return 1 + 1; }"
59
+ end
60
+
61
+ Foo.updated_func.should == 2
62
+
63
+ module Foo
64
+ inline "int updated_func() { return 2 + 2; }"
65
+ end
66
+
67
+ Foo.updated_func.should == 4
68
+ end
69
+
70
+ it 'should recompile if the code is changed after a failure' do
71
+ # unfortunately this doesn't check the real functionality, which is that if a dll is deleted, it isn't re-produced
72
+ begin
73
+ module Foo
74
+ inline "int updated_func2() { asdf }"
75
+ end
76
+ rescue
77
+ begin
78
+ Foo.updated_func2
79
+ raise 'should have failed'
80
+ rescue NoMethodError
81
+ end
82
+ end
83
+
84
+ module Foo
85
+ inline "int updated_func2() { return 2 + 2; }"
86
+ end
87
+
88
+ Foo.updated_func2.should == 4
89
+ end
90
+
91
+ it 'should be configured using the block form' do
92
+ module Foo
93
+ inline do |builder|
94
+ builder.function %{
95
+ int func_1 () {
96
+ return 0;
97
+ }
98
+ }
99
+
100
+ builder.function %{
101
+ int func_2 () {
102
+ return 1;
103
+ }
104
+ }
105
+ end
106
+ end
107
+
108
+ Foo.func_1.should == 0
109
+ Foo.func_2.should == 1
110
+ end
111
+
112
+ it 'should allow users to add type maps' do
113
+ class MyStruct < FFI::Struct
114
+ layout :dummy, :int
115
+ end
116
+ module Foo
117
+ inline do |builder|
118
+ builder.map 'my_struct_t *' => 'pointer'
119
+
120
+ builder.raw %q{
121
+ typedef struct {
122
+ int dummy;
123
+ } my_struct_t;
124
+ }
125
+
126
+ builder.function 'my_struct_t* use_my_struct (my_struct_t* my_struct) { return my_struct; }'
127
+ end
128
+ end
129
+ my_struct = MyStruct.new
130
+ Foo.use_my_struct(my_struct).should == my_struct.to_ptr
131
+ end
132
+
133
+ it 'should allow users to include header files' do
134
+ module Foo
135
+ inline do |builder|
136
+ builder.include "stdio.h"
137
+ builder.include "local_header.h", :quoted => true
138
+ builder.code.should == "#include <stdio.h>\n#include \"local_header.h\"\n"
139
+ builder.stub!(:build)
140
+ end
141
+ end
142
+ end
143
+
144
+ it 'should allow users to add external libraries' do
145
+ module Foo
146
+ inline do |builder|
147
+ builder.libraries 'foolib1', 'foolib2'
148
+ builder.stub!(:build)
149
+ builder.stub!(:symbols) { [] }
150
+ builder.libraries.should == ['foolib1', 'foolib2']
151
+ end
152
+
153
+ inline "int func() { return 0; }", :libraries => ['foolib1', 'foolib2'] do |builder|
154
+ builder.stub!(:build)
155
+ builder.stub!(:symbols) { [] }
156
+ builder.libraries.should == ['foolib1', 'foolib2']
157
+ end
158
+ end
159
+ end
160
+
161
+ if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
162
+ it "should put library links at the end in mingw" do
163
+ module Foo
164
+
165
+ code = <<-CODE
166
+ #include <windows.h>
167
+ #include <mmsystem.h>
168
+ int go() {
169
+ mixerOpen(0, 0,0,0,0);
170
+ return 3;
171
+ }
172
+ CODE
173
+
174
+ inline do |builder|
175
+ builder.library 'Winmm'
176
+ builder.raw code
177
+ end
178
+
179
+ inline do |builder|
180
+ builder.use_compiler :gxx
181
+ builder.library 'Winmm'
182
+ builder.raw code
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ it 'should generate C struct from FFI::Struct' do
189
+ pending do
190
+ class MyStruct < FFI::Struct
191
+ layout :a, :int, \
192
+ :b, :char,
193
+ :c, :pointer
194
+ end
195
+ module Foo
196
+ extend FFI::Inliner
197
+ inline do |builder|
198
+ builder.struct MyStruct
199
+ builder.code.should == <<EOC
200
+ typedef struct
201
+ {
202
+ int a;
203
+ char b;
204
+ void* c;
205
+ } my_struct_t;
206
+
207
+ EOC
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ it 'should return the current compiler' do
214
+ module Foo
215
+ inline do |builder|
216
+ builder.compiler.should == FFI::Inliner::Compiler[:gcc]
217
+ end
218
+ end
219
+ end
220
+
221
+ it 'should raise errors' do
222
+ proc {
223
+ module Foo
224
+ inline "int boom("
225
+ end
226
+ }.should raise_error(/cannot parse/)
227
+
228
+ proc {
229
+ module Foo
230
+ inline 'int boom() { printf "Hello" }'
231
+ end
232
+ }.should raise_error(/compile error/)
233
+ end
234
+
235
+ describe 'GXX compiler' do
236
+ it 'should compile and link a shim C library that encapsulates C++ code' do
237
+ module Foo
238
+ inline :cpp do |builder|
239
+ builder.raw %{
240
+ #include <iostream>
241
+ #include <string>
242
+
243
+ using namespace std;
244
+
245
+ class Greeter
246
+ {
247
+ public:
248
+ Greeter();
249
+ string say_hello();
250
+ };
251
+
252
+ Greeter::Greeter () { };
253
+ string Greeter::say_hello ()
254
+ {
255
+ return "Hello foos!";
256
+ };
257
+ }
258
+
259
+ builder.function %{
260
+ const char* say_hello () {
261
+ Greeter greeter;
262
+
263
+ return greeter.say_hello().c_str();
264
+ }
265
+ }, return: :string
266
+ end
267
+ end
268
+
269
+ Foo.say_hello.should == 'Hello foos!'
270
+ end
271
+ end
272
+ end
273
+
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi-inline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - meh.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi
16
+ requirement: &10074040 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.4.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *10074040
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &10073120 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *10073120
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &10072380 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *10072380
47
+ description:
48
+ email: meh@paranoici.org
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - Rakefile
54
+ - examples/ex_1.rb
55
+ - ffi-inline.gemspec
56
+ - lib/ffi/inliner.rb
57
+ - lib/ffi/inliner/builders.rb
58
+ - lib/ffi/inliner/builders/c.rb
59
+ - lib/ffi/inliner/builders/cpp.rb
60
+ - lib/ffi/inliner/compilers.rb
61
+ - lib/ffi/inliner/compilers/gcc.rb
62
+ - lib/ffi/inliner/compilers/gxx.rb
63
+ - lib/ffi/inliner/compilers/tcc.rb
64
+ - lib/ffi/inliner/error.rb
65
+ - lib/ffi/inliner/inliner.rb
66
+ - lib/ffi/inliner/version.rb
67
+ - spec/inliner_spec.rb
68
+ homepage: http://github.com/meh/ruby-ffi-inline
69
+ licenses: []
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 1.8.15
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Inline C/C++ in Ruby easily and cleanly.
92
+ test_files:
93
+ - spec/inliner_spec.rb