Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions lib/ffi/dynamic_library.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# Copyright (C) 2008-2010 Wayne Meissner
#
# This file is part of ruby-ffi.
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the Ruby FFI project nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.#

module FFI
class DynamicLibrary
SEARCH_PATH = %w[/usr/lib /usr/local/lib /opt/local/lib]
if FFI::Platform::ARCH == 'aarch64' && FFI::Platform.mac?
SEARCH_PATH << '/opt/homebrew/lib'
end
Comment on lines +33 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is something you want to handle (and I wouldn't blame you if you don't):

Some Homebrew users install brew to a path that is neither /usr/local nor /opt/homebrew (e.g. they have no sudo access so use their $HOME). For these cases, one approach that works is to check if brew is in PATH and then check the output of brew --prefix.

Copy link
Collaborator Author

@eregon eregon Aug 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems an orthogonal issue to me.
Shelling out to brew --prefix seems quite expensive (e.g. while loading the ffi gem), maybe there should be a standard HOMEBREW_PREFIX env var exported by default or so, so we could read it without the cost of a subprocess.
Could you file a separate issue if you need that? (or maybe there is already one?)

Copy link

@SampsonCrowley SampsonCrowley Dec 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eregon brew --prefix is the correct way to detect the homebrew location. or we need a configure flag for prepending an include dir to the initial build of the gem so that people can set their own configurations appropriately. /opt/homebrew and /usr/local are standard locations, but not the only options. However, this shouldn't be a blocker to get the standard fix out to the majority. people have been waiting for months and months. this is a serious breakage for rosetta and arm users.

Fix the easy standard way for the majority of users first, then we can come up with a way for the people who are in the outliers

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree this is not a blocker. I think brew --prefix is too much of a cost on require, but maybe it's OK to compute it once lazily on ffi_lib on macOS.

What we need is @larskanis to review this PR and then make a release.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking more about it, it sounds really complicated to support Homebrew not in a default location.
For instance if Homebrew is installed both in amd64 and aarch64, how do we even call the "right" brew executable? It all sounds extremely messy and easy to mess up.
Anyway, if someone needs this they can make a PR.


SEARCH_PATH_MESSAGE = "Searched in <system library path>, #{SEARCH_PATH.join(', ')}"

def self.load_library(name, flags)
if name == FFI::CURRENT_PROCESS
FFI::DynamicLibrary.open(nil, RTLD_LAZY | RTLD_LOCAL)
else
flags ||= RTLD_LAZY | RTLD_LOCAL

libnames = (name.is_a?(::Array) ? name : [name])
libnames = libnames.map(&:to_s).map { |n| [n, FFI.map_library_name(n)].uniq }.flatten.compact
errors = []

libnames.each do |libname|
lib = try_load(libname, flags, errors)
return lib if lib

unless libname.start_with?("/") || FFI::Platform.windows?
SEARCH_PATH.each do |prefix|
path = "#{prefix}/#{libname}"
if File.exist?(path)
lib = try_load(path, flags, errors)
return lib if lib
end
end
end
end

raise LoadError, [*errors, SEARCH_PATH_MESSAGE].join(".\n")
end
end
private_class_method :load_library

def self.try_load(libname, flags, errors)
begin
lib = FFI::DynamicLibrary.open(libname, flags)
return lib if lib

# LoadError for C ext & JRuby, RuntimeError for TruffleRuby
rescue LoadError, RuntimeError => ex
if ex.message =~ /(([^ \t()])+\.so([^ \t:()])*):([ \t])*(invalid ELF header|file too short|invalid file format)/
if File.binread($1) =~ /(?:GROUP|INPUT) *\( *([^ \)]+)/
return try_load($1, flags, errors)
end
end

errors << ex
nil
end
end
private_class_method :try_load
end
end
59 changes: 5 additions & 54 deletions lib/ffi/library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.#

require 'ffi/dynamic_library'

module FFI
CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = Object.new

Expand Down Expand Up @@ -95,62 +97,11 @@ def self.extended(mod)
def ffi_lib(*names)
raise LoadError.new("library names list must not be empty") if names.empty?

lib_flags = defined?(@ffi_lib_flags) ? @ffi_lib_flags : FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_LOCAL
ffi_libs = names.map do |name|

if name == FFI::CURRENT_PROCESS
FFI::DynamicLibrary.open(nil, FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_LOCAL)

else
libnames = (name.is_a?(::Array) ? name : [ name ]).map(&:to_s).map { |n| [ n, FFI.map_library_name(n) ].uniq }.flatten.compact
lib = nil
errors = {}

libnames.each do |libname|
begin
orig = libname
lib = FFI::DynamicLibrary.open(libname, lib_flags)
break if lib

rescue Exception => ex
ldscript = false
if ex.message =~ /(([^ \t()])+\.so([^ \t:()])*):([ \t])*(invalid ELF header|file too short|invalid file format)/
if File.binread($1) =~ /(?:GROUP|INPUT) *\( *([^ \)]+)/
libname = $1
ldscript = true
end
end

if ldscript
retry
else
# TODO better library lookup logic
unless libname.start_with?("/") || FFI::Platform.windows?
path = ['/usr/lib/','/usr/local/lib/','/opt/local/lib/', '/opt/homebrew/lib/'].find do |pth|
File.exist?(pth + libname)
end
if path
libname = path + libname
retry
end
end

libr = (orig == libname ? orig : "#{orig} #{libname}")
errors[libr] = ex
end
end
end

if lib.nil?
raise LoadError.new(errors.values.join(".\n"))
end
lib_flags = defined?(@ffi_lib_flags) && @ffi_lib_flags

# return the found lib
lib
end
@ffi_libs = names.map do |name|
FFI::DynamicLibrary.send(:load_library, name, lib_flags)
end

@ffi_libs = ffi_libs
end

# Set the calling convention for {#attach_function} and {#callback}
Expand Down