From 596a629d0f1fbe38fc35cc6df0afe50f1fdbfde9 Mon Sep 17 00:00:00 2001 From: Jonathan Barquero Date: Fri, 15 Aug 2025 09:54:17 -0600 Subject: [PATCH 1/4] feat: loading from extensions dir if possible then fallback to legacy search and paths --- lib/ffi-compiler/loader.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/ffi-compiler/loader.rb b/lib/ffi-compiler/loader.rb index 08c5fe8..ea4620c 100644 --- a/lib/ffi-compiler/loader.rb +++ b/lib/ffi-compiler/loader.rb @@ -7,6 +7,26 @@ module Compiler module Loader def self.find(name, start_path = nil) library = Platform.system.map_library_name(name) + + # Try RubyGems extension_dir first (RubyGems 4.0+) + begin + if defined?(Gem::Specification) + begin + # Try to find the gemspec for the calling gem + spec = Gem::Specification.find_by_name(name) + if spec && spec.respond_to?(:extension_dir) + ext_dir = spec.extension_dir + ext_path = File.join(ext_dir, library) + return ext_path if File.exist?(ext_path) + end + rescue Gem::MissingSpecError + # Not an installed gem, fall through to legacy search + end + end + rescue + # Ignore and fall back to legacy search + end + root = false Pathname.new(start_path || caller_path(caller[0])).ascend do |path| Dir.glob("#{path}/**/#{FFI::Platform::ARCH}-#{FFI::Platform::OS}/#{library}") do |f| From 2c23c8819890eebd484ed7ca113db46a5e401683 Mon Sep 17 00:00:00 2001 From: Jonathan Barquero Date: Mon, 18 Aug 2025 12:31:43 -0600 Subject: [PATCH 2/4] loader will only fall back to legacy search if env ffi compiler dev mode flag is set --- lib/ffi-compiler/loader.rb | 52 ++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/lib/ffi-compiler/loader.rb b/lib/ffi-compiler/loader.rb index ea4620c..5c03a54 100644 --- a/lib/ffi-compiler/loader.rb +++ b/lib/ffi-compiler/loader.rb @@ -8,41 +8,37 @@ module Loader def self.find(name, start_path = nil) library = Platform.system.map_library_name(name) - # Try RubyGems extension_dir first (RubyGems 4.0+) - begin - if defined?(Gem::Specification) - begin - # Try to find the gemspec for the calling gem - spec = Gem::Specification.find_by_name(name) - if spec && spec.respond_to?(:extension_dir) - ext_dir = spec.extension_dir - ext_path = File.join(ext_dir, library) - return ext_path if File.exist?(ext_path) - end - rescue Gem::MissingSpecError - # Not an installed gem, fall through to legacy search + if defined?(Gem::Specification) + begin + spec = Gem::Specification.find_by_name(name) + if spec && spec.respond_to?(:extension_dir) + ext_dir = spec.extension_dir + ext_path = File.join(ext_dir, library) + return ext_path if File.exist?(ext_path) end + rescue Gem::MissingSpecError => e + raise LoadError, "Could not find gem specification for '#{name}'. Ensure the gem is installed and named correctly. (#{e.message})" end - rescue - # Ignore and fall back to legacy search end - root = false - Pathname.new(start_path || caller_path(caller[0])).ascend do |path| - Dir.glob("#{path}/**/#{FFI::Platform::ARCH}-#{FFI::Platform::OS}/#{library}") do |f| - return f - end - - Dir.glob("#{path}/**/#{library}") do |f| - return f - end + # Only fallback in explicit dev/test mode + if ENV['FFI_COMPILER_DEV_MODE'] == '1' + root = false + Pathname.new(start_path || caller_path(caller[0])).ascend do |path| + Dir.glob("#{path}/**/#{FFI::Platform::ARCH}-#{FFI::Platform::OS}/#{library}") do |f| + return f + end - break if root + Dir.glob("#{path}/**/#{library}") do |f| + return f + end - # Next iteration will be the root of the gem if this is the lib/ dir - stop after that - root = File.basename(path) == 'lib' + break if root + root = File.basename(path) == 'lib' + end end - raise LoadError.new("cannot find '#{name}' library") + + raise LoadError, "Could not find extension for '#{name}'. Ensure the gem is installed and named correctly." end def self.caller_path(line = caller[0]) From 1de9375dae1b698aa132aa34271e6f59317f3ef3 Mon Sep 17 00:00:00 2001 From: Jonathan Barquero Date: Mon, 18 Aug 2025 12:37:52 -0600 Subject: [PATCH 3/4] adding comments --- lib/ffi-compiler/loader.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ffi-compiler/loader.rb b/lib/ffi-compiler/loader.rb index 5c03a54..39d0955 100644 --- a/lib/ffi-compiler/loader.rb +++ b/lib/ffi-compiler/loader.rb @@ -17,6 +17,8 @@ def self.find(name, start_path = nil) return ext_path if File.exist?(ext_path) end rescue Gem::MissingSpecError => e + # Provide a clearer, actionable error for users/gem authors if the gem is not installed + # or named incorrectly, rather than letting a RubyGems error bubble up. raise LoadError, "Could not find gem specification for '#{name}'. Ensure the gem is installed and named correctly. (#{e.message})" end end From 44affbfab7c66467f36870cbe21191709c758030 Mon Sep 17 00:00:00 2001 From: Jonathan Barquero Date: Tue, 19 Aug 2025 17:30:12 -0600 Subject: [PATCH 4/4] Fix: undo changes but remove extra block bigin/rescue --- lib/ffi-compiler/loader.rb | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/lib/ffi-compiler/loader.rb b/lib/ffi-compiler/loader.rb index 39d0955..984c3c5 100644 --- a/lib/ffi-compiler/loader.rb +++ b/lib/ffi-compiler/loader.rb @@ -5,9 +5,13 @@ module FFI module Compiler module Loader + # Finds the native extension for the given library name. + # Tries RubyGems extension_dir using the library name as the gem name (best effort), + # then always falls back to the legacy recursive search for compatibility. def self.find(name, start_path = nil) library = Platform.system.map_library_name(name) + # Try RubyGems extension_dir using name as gem name (best effort) if defined?(Gem::Specification) begin spec = Gem::Specification.find_by_name(name) @@ -16,31 +20,24 @@ def self.find(name, start_path = nil) ext_path = File.join(ext_dir, library) return ext_path if File.exist?(ext_path) end - rescue Gem::MissingSpecError => e - # Provide a clearer, actionable error for users/gem authors if the gem is not installed - # or named incorrectly, rather than letting a RubyGems error bubble up. - raise LoadError, "Could not find gem specification for '#{name}'. Ensure the gem is installed and named correctly. (#{e.message})" + rescue Gem::MissingSpecError + # Ignore and fall back to legacy search end end - # Only fallback in explicit dev/test mode - if ENV['FFI_COMPILER_DEV_MODE'] == '1' - root = false - Pathname.new(start_path || caller_path(caller[0])).ascend do |path| - Dir.glob("#{path}/**/#{FFI::Platform::ARCH}-#{FFI::Platform::OS}/#{library}") do |f| - return f - end - - Dir.glob("#{path}/**/#{library}") do |f| - return f - end - - break if root - root = File.basename(path) == 'lib' + # Legacy search + root = false + Pathname.new(start_path || caller_path(caller[0])).ascend do |path| + Dir.glob("#{path}/**/#{FFI::Platform::ARCH}-#{FFI::Platform::OS}/#{library}") do |f| + return f + end + Dir.glob("#{path}/**/#{library}") do |f| + return f end + break if root + root = File.basename(path) == 'lib' end - - raise LoadError, "Could not find extension for '#{name}'. Ensure the gem is installed and named correctly." + raise LoadError, "cannot find '#{name}' library" end def self.caller_path(line = caller[0])