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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59c39f1d0d925b12810c952af5c52fd0631ab72fab24601fb7b3b2eb9359f9a0
4
- data.tar.gz: 45b7b6b883d0e484c87789287bb722105521033c0a70a2dfa6c7aa51162e902e
3
+ metadata.gz: 9d61e5d60af4f58c5de6720bfe8c34c1d9fe1e1821d64db9d8f9e5776876aea2
4
+ data.tar.gz: 7a6f9e880348c82957e33f6d023e487fe1e608d259a381643bcab05cbacf29ef
5
5
  SHA512:
6
- metadata.gz: 3e1fafd139b595d06c04e9edcd46818b1d58a4986aa4617a84b0e2f794395f2575012b48368c0e5d019bb4914b0b882b82ac42bf33d63548bc93d42d94cccff8
7
- data.tar.gz: a58808da33f2e358d8445178dbc19aa8fea350e0a0fcbbb54f58be19fc092cbd49fc3eb238af22ae6254501c47d358eeee517bf8ee7d4cb6f3f377ece1fec884
6
+ metadata.gz: 021641dcb250942eef4a1fe21638b0a06f810f0ad0c11288d7146cab80ec4388634ab16e838a52d04767b4df3df68280bfaa270354a2132012f296315050f281
7
+ data.tar.gz: c467b06b0f8df3075e626832352d7a8aa5cce24fc0d8000d962eb57013d9157813f2f097e4f8c34e805fe94f8a73d925cdbb04fcb74300166071d1129ca08628
data/Rakefile CHANGED
@@ -1,6 +1,11 @@
1
1
  require 'rubygems'
2
2
  require 'rubygems/tasks'
3
+ require 'rspec/core/rake_task'
3
4
 
4
5
  Gem::Tasks.new do |t|
5
6
  t.scm.tag.format = '%s'
6
7
  end
8
+
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ task :default => :spec
@@ -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 defined?(Gem::Specification) &&
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]
@@ -1,7 +1,7 @@
1
1
 
2
2
  module FFI
3
3
  module Compiler
4
- VERSION = "1.4.2"
4
+ VERSION = "1.4.3"
5
5
  end
6
6
  end
7
7
 
@@ -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
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
2
+
3
+ Dir[File.join(__dir__, 'support', '**', '*.rb')].sort.each { |f| require f }
4
+
5
+ RSpec.configure do |config|
6
+ config.disable_monkey_patching!
7
+ config.order = :random
8
+ Kernel.srand config.seed
9
+ end
@@ -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.2
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.5
113
+ rubygems_version: 4.0.23
111
114
  specification_version: 4
112
115
  summary: Ruby FFI Rakefile generator
113
116
  test_files: []