ffi-compiler 1.4.2 → 1.4.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.
- checksums.yaml +4 -4
- data/Rakefile +5 -0
- data/lib/ffi-compiler/loader.rb +12 -5
- data/lib/ffi-compiler/version.rb +1 -1
- data/spec/loader_spec.rb +152 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/gem_home.rb +79 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d61e5d60af4f58c5de6720bfe8c34c1d9fe1e1821d64db9d8f9e5776876aea2
|
|
4
|
+
data.tar.gz: 7a6f9e880348c82957e33f6d023e487fe1e608d259a381643bcab05cbacf29ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 021641dcb250942eef4a1fe21638b0a06f810f0ad0c11288d7146cab80ec4388634ab16e838a52d04767b4df3df68280bfaa270354a2132012f296315050f281
|
|
7
|
+
data.tar.gz: c467b06b0f8df3075e626832352d7a8aa5cce24fc0d8000d962eb57013d9157813f2f097e4f8c34e805fe94f8a73d925cdbb04fcb74300166071d1129ca08628
|
data/Rakefile
CHANGED
data/lib/ffi-compiler/loader.rb
CHANGED
|
@@ -10,11 +10,7 @@ module FFI
|
|
|
10
10
|
start_path ||= caller_path(caller[0])
|
|
11
11
|
|
|
12
12
|
# Load from modern extension fast-path when called from an active gem spec
|
|
13
|
-
if
|
|
14
|
-
(spec = Gem::Specification.find_active_stub_by_path(library)) &&
|
|
15
|
-
spec.respond_to?(:extension_dir) &&
|
|
16
|
-
start_path.start_with?(spec.gem_dir)
|
|
17
|
-
|
|
13
|
+
if (spec = active_spec_containing(start_path)) && spec.respond_to?(:extension_dir)
|
|
18
14
|
ext_path = File.join(spec.extension_dir, library)
|
|
19
15
|
return ext_path if File.exist?(ext_path)
|
|
20
16
|
end
|
|
@@ -38,6 +34,17 @@ module FFI
|
|
|
38
34
|
raise LoadError.new("cannot find '#{name}' library")
|
|
39
35
|
end
|
|
40
36
|
|
|
37
|
+
def self.active_spec_containing(path)
|
|
38
|
+
return unless defined?(Gem) && Gem.respond_to?(:loaded_specs)
|
|
39
|
+
|
|
40
|
+
path = path.to_s
|
|
41
|
+
Gem.loaded_specs.each_value.find do |spec|
|
|
42
|
+
gem_dir = spec.full_gem_path # not #gem_dir: Bundler's stub specs read the whole gemspec from disk for it
|
|
43
|
+
path == gem_dir || path.start_with?(gem_dir + File::SEPARATOR)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
private_class_method :active_spec_containing
|
|
47
|
+
|
|
41
48
|
def self.caller_path(line = caller[0])
|
|
42
49
|
if FFI::Platform::OS == 'windows'
|
|
43
50
|
drive = line[0..1]
|
data/lib/ffi-compiler/version.rb
CHANGED
data/spec/loader_spec.rb
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'tmpdir'
|
|
3
|
+
require 'ffi-compiler/loader'
|
|
4
|
+
|
|
5
|
+
RSpec.describe FFI::Compiler::Loader do
|
|
6
|
+
let(:library) { FFI::Compiler::Platform.system.map_library_name('foo') }
|
|
7
|
+
let(:arch_dir) { "#{FFI::Platform::ARCH}-#{FFI::Platform::OS}" }
|
|
8
|
+
|
|
9
|
+
around do |example|
|
|
10
|
+
Dir.mktmpdir('ffi-compiler-loader') do |dir|
|
|
11
|
+
@root = File.realpath(dir)
|
|
12
|
+
example.run
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def touch(*parts)
|
|
17
|
+
path = File.join(@root, *parts)
|
|
18
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
19
|
+
FileUtils.touch(path)
|
|
20
|
+
path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '.find' do
|
|
24
|
+
context 'when searching the legacy lib path' do
|
|
25
|
+
it 'finds a library below start_path' do
|
|
26
|
+
expected = touch('mygem', 'lib', 'mygem', library)
|
|
27
|
+
|
|
28
|
+
expect(described_class.find('foo', File.join(@root, 'mygem', 'lib'))).to eq(expected)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'prefers a library in the platform directory over a generic one' do
|
|
32
|
+
touch('mygem', 'ext', library)
|
|
33
|
+
expected = touch('mygem', 'ext', arch_dir, library)
|
|
34
|
+
|
|
35
|
+
expect(described_class.find('foo', File.join(@root, 'mygem', 'lib'))).to eq(expected)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'ascends from start_path to the gem root' do
|
|
39
|
+
expected = touch('mygem', 'ext', library)
|
|
40
|
+
|
|
41
|
+
expect(described_class.find('foo', File.join(@root, 'mygem', 'lib', 'mygem'))).to eq(expected)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'does not search above the gem root' do
|
|
45
|
+
touch(library)
|
|
46
|
+
FileUtils.mkdir_p(File.join(@root, 'mygem', 'lib'))
|
|
47
|
+
|
|
48
|
+
expect { described_class.find('foo', File.join(@root, 'mygem', 'lib')) }
|
|
49
|
+
.to raise_error(LoadError, "cannot find 'foo' library")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'accepts start_path as a Pathname' do
|
|
53
|
+
expected = touch('mygem', 'ext', library)
|
|
54
|
+
|
|
55
|
+
expect(described_class.find('foo', Pathname.new(File.join(@root, 'mygem', 'lib')))).to eq(expected)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'raises LoadError when the library does not exist' do
|
|
59
|
+
FileUtils.mkdir_p(File.join(@root, 'mygem', 'lib'))
|
|
60
|
+
|
|
61
|
+
expect { described_class.find('foo', File.join(@root, 'mygem', 'lib')) }
|
|
62
|
+
.to raise_error(LoadError, "cannot find 'foo' library")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "defaults start_path to the caller's directory" do
|
|
66
|
+
expected = touch('mygem', 'ext', library)
|
|
67
|
+
caller_file = touch('mygem', 'lib', 'mygem.rb')
|
|
68
|
+
|
|
69
|
+
# Evaluate the call as if it were made from lib/mygem.rb
|
|
70
|
+
expect(eval("FFI::Compiler::Loader.find('foo')", binding, caller_file)).to eq(expected)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context 'when called from an activated gem' do
|
|
75
|
+
let(:gem_home) { GemHome.new(@root) }
|
|
76
|
+
|
|
77
|
+
# Runs Loader.find in a child process with +activate+ gems activated.
|
|
78
|
+
# Returns the path found, or :load_error.
|
|
79
|
+
def find_in_gem_home(start_path, activate:)
|
|
80
|
+
result = gem_home.run(<<~RUBY, activate: activate)
|
|
81
|
+
require 'ffi-compiler/loader'
|
|
82
|
+
begin
|
|
83
|
+
print FFI::Compiler::Loader.find('foo', #{start_path.inspect})
|
|
84
|
+
rescue LoadError
|
|
85
|
+
print 'LoadError'
|
|
86
|
+
end
|
|
87
|
+
RUBY
|
|
88
|
+
result == 'LoadError' ? :load_error : result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "finds the library in the gem's extension_dir" do
|
|
92
|
+
gem_dir = gem_home.install('mygem', ext_files: [library])
|
|
93
|
+
|
|
94
|
+
expect(find_in_gem_home(File.join(gem_dir, 'lib'), activate: ['mygem']))
|
|
95
|
+
.to eq(File.join(gem_home.extension_dir('mygem-1.0'), library))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "prefers the gem's extension_dir over a copy in the gem directory" do
|
|
99
|
+
gem_dir = gem_home.install('mygem', ext_files: [library], gem_files: ["ext/#{arch_dir}/#{library}"])
|
|
100
|
+
|
|
101
|
+
expect(find_in_gem_home(File.join(gem_dir, 'lib'), activate: ['mygem']))
|
|
102
|
+
.to eq(File.join(gem_home.extension_dir('mygem-1.0'), library))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "falls back to the gem directory when the extension_dir lacks the library" do
|
|
106
|
+
gem_dir = gem_home.install('mygem', ext_files: ['other.so'], gem_files: ["ext/#{arch_dir}/#{library}"])
|
|
107
|
+
|
|
108
|
+
expect(find_in_gem_home(File.join(gem_dir, 'lib'), activate: ['mygem']))
|
|
109
|
+
.to eq(File.join(gem_dir, 'ext', arch_dir, library))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "does not return a library from another gem's extension_dir" do
|
|
113
|
+
gem_home.install('other', ext_files: [library])
|
|
114
|
+
gem_dir = gem_home.install('mygem', gem_files: ["ext/#{library}"])
|
|
115
|
+
|
|
116
|
+
expect(find_in_gem_home(File.join(gem_dir, 'lib'), activate: %w[other mygem]))
|
|
117
|
+
.to eq(File.join(gem_dir, 'ext', library))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "raises LoadError when only another gem's extension_dir has the library" do
|
|
121
|
+
gem_home.install('other', ext_files: [library])
|
|
122
|
+
gem_dir = gem_home.install('mygem')
|
|
123
|
+
|
|
124
|
+
expect(find_in_gem_home(File.join(gem_dir, 'lib'), activate: %w[other mygem])).to eq(:load_error)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "finds the library in its own extension_dir when another gem's extension_dir has one too" do
|
|
128
|
+
gem_home.install('another', ext_files: [library])
|
|
129
|
+
gem_dir = gem_home.install('mygem', ext_files: [library])
|
|
130
|
+
|
|
131
|
+
expect(find_in_gem_home(File.join(gem_dir, 'lib'), activate: %w[another mygem]))
|
|
132
|
+
.to eq(File.join(gem_home.extension_dir('mygem-1.0'), library))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'finds the library when start_path is the gem directory itself' do
|
|
136
|
+
gem_dir = gem_home.install('mygem', ext_files: [library], gem_files: ["ext/#{library}"])
|
|
137
|
+
|
|
138
|
+
expect(find_in_gem_home(gem_dir, activate: ['mygem']))
|
|
139
|
+
.to eq(File.join(gem_home.extension_dir('mygem-1.0'), library))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'does not match a gem whose directory is only a prefix of start_path' do
|
|
143
|
+
# mygem-1.0 is a string prefix of mygem-1.0-2, the directory of gem "mygem-1.0" version 2
|
|
144
|
+
gem_home.install('mygem', ext_files: [library])
|
|
145
|
+
gem_dir = gem_home.install('mygem-1.0', version: '2', gem_files: ["ext/#{library}"])
|
|
146
|
+
|
|
147
|
+
expect(find_in_gem_home(File.join(gem_dir, 'lib'), activate: %w[mygem mygem-1.0]))
|
|
148
|
+
.to eq(File.join(gem_dir, 'ext', library))
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'open3'
|
|
3
|
+
require 'rbconfig'
|
|
4
|
+
|
|
5
|
+
# Builds a throwaway GEM_HOME with installed gems and runs Ruby code against it
|
|
6
|
+
# in a child process, so specs can activate real gems without touching the
|
|
7
|
+
# bundle the spec run itself is using.
|
|
8
|
+
class GemHome
|
|
9
|
+
attr_reader :root
|
|
10
|
+
|
|
11
|
+
def initialize(root)
|
|
12
|
+
@root = root
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Installs a gem named +name+. Each of +ext_files+ is placed in the gem's
|
|
16
|
+
# extension_dir, as RubyGems does for a compiled extension, and each of
|
|
17
|
+
# +gem_files+ is placed in the gem directory. Returns the gem directory.
|
|
18
|
+
def install(name, version: '1.0', ext_files: [], gem_files: [])
|
|
19
|
+
full_name = "#{name}-#{version}"
|
|
20
|
+
gem_dir = File.join(root, 'gems', full_name)
|
|
21
|
+
|
|
22
|
+
write(File.join(gem_dir, 'lib', "#{name}.rb"))
|
|
23
|
+
gem_files.each { |f| write(File.join(gem_dir, f)) }
|
|
24
|
+
|
|
25
|
+
unless ext_files.empty?
|
|
26
|
+
write(File.join(gem_dir, 'ext', 'extconf.rb'))
|
|
27
|
+
ext_dir = extension_dir(full_name)
|
|
28
|
+
ext_files.each { |f| write(File.join(ext_dir, f)) }
|
|
29
|
+
write(File.join(ext_dir, 'gem.build_complete'))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
write(File.join(root, 'specifications', "#{full_name}.gemspec"), <<~RUBY)
|
|
33
|
+
Gem::Specification.new do |s|
|
|
34
|
+
s.name = #{name.inspect}
|
|
35
|
+
s.version = #{version.inspect}
|
|
36
|
+
s.summary = 'fake'
|
|
37
|
+
s.authors = ['fake']
|
|
38
|
+
s.files = ['lib/#{name}.rb']
|
|
39
|
+
s.extensions = #{ext_files.empty? ? [] : ['ext/extconf.rb']}
|
|
40
|
+
end
|
|
41
|
+
RUBY
|
|
42
|
+
|
|
43
|
+
gem_dir
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def extension_dir(full_name)
|
|
47
|
+
File.join(root, 'extensions', Gem::Platform.local.to_s, Gem.extension_api_version, full_name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Activates +gems+ and then runs +code+ in a child process using this
|
|
51
|
+
# GEM_HOME. Returns the child's stdout; raises if it fails.
|
|
52
|
+
def run(code, activate: [])
|
|
53
|
+
script = activate.map { |g| "gem #{g.inspect}\n" }.join + code
|
|
54
|
+
stdout, stderr, status = Open3.capture3(child_env, RbConfig.ruby, *load_path_args, '-e', script)
|
|
55
|
+
raise "child process failed:\n#{stderr}" unless status.success?
|
|
56
|
+
|
|
57
|
+
stdout
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def write(path, content = '')
|
|
63
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
64
|
+
File.write(path, content)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def child_env
|
|
68
|
+
env = { 'GEM_HOME' => root, 'GEM_PATH' => root, 'RUBYOPT' => nil, 'RUBYLIB' => nil }
|
|
69
|
+
ENV.each_key { |k| env[k] = nil if k.start_with?('BUNDLE_', 'BUNDLER_') }
|
|
70
|
+
env
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ffi-compiler and ffi are not installed in this GEM_HOME; put them on the
|
|
74
|
+
# load path directly.
|
|
75
|
+
def load_path_args
|
|
76
|
+
paths = [File.expand_path('../../lib', __dir__)] + Gem.loaded_specs.fetch('ffi').full_require_paths
|
|
77
|
+
paths.flat_map { |p| ['-I', p] }
|
|
78
|
+
end
|
|
79
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ffi-compiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wayne Meissner
|
|
@@ -89,6 +89,9 @@ files:
|
|
|
89
89
|
- lib/ffi-compiler/shell.rb
|
|
90
90
|
- lib/ffi-compiler/task.rb
|
|
91
91
|
- lib/ffi-compiler/version.rb
|
|
92
|
+
- spec/loader_spec.rb
|
|
93
|
+
- spec/spec_helper.rb
|
|
94
|
+
- spec/support/gem_home.rb
|
|
92
95
|
homepage: https://github.com/ffi/ffi-compiler
|
|
93
96
|
licenses:
|
|
94
97
|
- Apache-2.0
|
|
@@ -107,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
107
110
|
- !ruby/object:Gem::Version
|
|
108
111
|
version: '0'
|
|
109
112
|
requirements: []
|
|
110
|
-
rubygems_version: 4.0.
|
|
113
|
+
rubygems_version: 4.0.23
|
|
111
114
|
specification_version: 4
|
|
112
115
|
summary: Ruby FFI Rakefile generator
|
|
113
116
|
test_files: []
|