ffi-inline 0.0.1

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