Skip to content

Commit

Permalink
Merge pull request #1045 from larskanis/refine-963
Browse files Browse the repository at this point in the history
Refine #963
  • Loading branch information
larskanis committed Sep 15, 2023
2 parents 9fb3e13 + b53f39d commit a6f291e
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 52 deletions.
1 change: 1 addition & 0 deletions lib/ffi/ffi.rb
Expand Up @@ -32,6 +32,7 @@
require 'ffi/platform'
require 'ffi/data_converter'
require 'ffi/types'
require 'ffi/library_path'
require 'ffi/library'
require 'ffi/errno'
require 'ffi/abstract_memory'
Expand Down
53 changes: 3 additions & 50 deletions lib/ffi/library.rb
Expand Up @@ -33,54 +33,7 @@
module FFI
CURRENT_PROCESS = USE_THIS_PROCESS_AS_LIBRARY = FFI.make_shareable(Object.new)

class LibraryPath < ::Struct.new(:name, :abi_number, :root)
PATTERN = /(#{Platform::LIBPREFIX})?(?<name>.*?)(\.|\z)/

def self.wrap(value)
# We allow instances of LibraryPath to pass through transparently:
return value if value.is_a?(self)

# We special case a library named 'c' to be the standard C library:
return Library::LIBC if value == 'c'

# If provided a relative file name we convert it into a library path:
if value && File.basename(value) == value
if match = PATTERN.match(value)
return self.new(match[:name])
end
end

# Otherwise, we assume it's a full path to a library:
return value
end

def full_name
# If the abi_number is given, we format it specifically according to platform rules:
if abi_number
if Platform.windows?
"#{Platform::LIBPREFIX}#{name}-#{abi_number}.#{Platform::LIBSUFFIX}"
elsif Platform.mac?
"#{Platform::LIBPREFIX}#{name}.#{abi_number}.#{Platform::LIBSUFFIX}"
else # Linux? BSD? etc.
"#{Platform::LIBPREFIX}#{name}.#{Platform::LIBSUFFIX}.#{abi_number}"
end
else
# Otherwise we just use a generic format:
"#{Platform::LIBPREFIX}#{name}.#{Platform::LIBSUFFIX}"
end
end

def to_s
if root
# If the root path is given, we generate the full path:
File.join(root, full_name)
else
full_name
end
end
end

# @param [#to_s] lib library name
# @param [String, FFI::LibraryPath] lib library name or LibraryPath object
# @return [String] library name formatted for current platform
# Transform a generic library name to a platform library name
# @example
Expand All @@ -90,9 +43,9 @@ def to_s
# # Windows
# FFI.map_library_name 'c' # -> "msvcrt.dll"
# FFI.map_library_name 'jpeg' # -> "jpeg.dll"
def self.map_library_name(value)
def self.map_library_name(lib)
# Mangle the library name to reflect the native library naming conventions
LibraryPath.wrap(value).to_s
LibraryPath.wrap(lib).to_s
end

# Exception raised when a function is not found in libraries
Expand Down
109 changes: 109 additions & 0 deletions lib/ffi/library_path.rb
@@ -0,0 +1,109 @@
#
# Copyright (C) 2023 Lars Kanis
#
# 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
# Transform a generic library name and ABI number to a platform library name
#
# Example:
# module LibVips
# extend FFI::Library
# ffi_lib LibraryPath.new("vips", abi_number: 42)
# end
#
# This translates to the following library file names:
# libvips-42.dll on Windows
# libvips.so.42 on Linux
# libvips.42.dylib on Macos
#
# See https://packaging.ubuntu.com/html/libraries.html for more information about library naming.
class LibraryPath
attr_reader :name
attr_reader :abi_number
attr_reader :root

# Build a new library path
#
# * <tt>name</tt> : The name of the library without file prefix or suffix.
# * <tt>abi_number</tt> : The ABI number of the library.
# * <tt>root</tt> : An optional base path prepended to the library name.
def initialize(name, abi_number: nil, root: nil)
@name = name
@abi_number = abi_number
@root = root
end

def self.wrap(value)
# We allow instances of LibraryPath to pass through transparently:
return value if value.is_a?(self)

# We special case a library named 'c' to be the standard C library:
return Library::LIBC if value == 'c'

# If provided a relative file name we convert it into a library path:
if value && File.basename(value) == value
return self.new(value)
end

# Otherwise, we assume it's a full path to a library:
return value
end

def full_name
# If the abi_number is given, we format it specifically according to platform rules:
if abi_number
if Platform.windows?
"#{Platform::LIBPREFIX}#{name}-#{abi_number}.#{Platform::LIBSUFFIX}"
elsif Platform.mac?
"#{Platform::LIBPREFIX}#{name}.#{abi_number}.#{Platform::LIBSUFFIX}"
else # Linux? BSD? etc.
"#{Platform::LIBPREFIX}#{name}.#{Platform::LIBSUFFIX}.#{abi_number}"
end
else
# Otherwise we just add prefix and suffix:
lib = name
# Add library prefix if missing
lib = Platform::LIBPREFIX + lib unless lib =~ /^#{Platform::LIBPREFIX}/
# Add library extension if missing
r = Platform.windows? || Platform.mac? ? "\\.#{Platform::LIBSUFFIX}$" : "\\.so($|\\.[1234567890]+)"
lib += ".#{Platform::LIBSUFFIX}" unless lib =~ /#{r}/
lib
end
end

def to_s
if root
# If the root path is given, we generate the full path:
File.join(root, full_name)
else
full_name
end
end
end
end
18 changes: 16 additions & 2 deletions spec/ffi/ffi_spec.rb
Expand Up @@ -12,10 +12,24 @@
let(:prefix) { FFI::Platform::LIBPREFIX }
let(:suffix) { FFI::Platform::LIBSUFFIX }

it "should add platform library preffix if not present" do
expect(FFI.map_library_name("dummy")).to eq("#{prefix}dummy.#{suffix}")
end

it "should add platform library extension if not present" do
expect(FFI.map_library_name("#{prefix}dummy")).to eq("#{prefix}dummy.#{suffix}")
end

it "should'n add platform library extension if already present" do
if FFI::Platform.windows?
expect(FFI.map_library_name("#{prefix}dummy-5.dll")).to eq("#{prefix}dummy-5.dll")
elsif FFI::Platform.mac?
expect(FFI.map_library_name("#{prefix}dummy.5.dylib")).to eq("#{prefix}dummy.5.dylib")
else # Linux? BSD? etc.
expect(FFI.map_library_name("#{prefix}dummy.so.5")).to eq("#{prefix}dummy.so.5")
end
end

it "should add platform library extension even if lib suffix is present in name" do
expect(FFI.map_library_name("#{prefix}dummy_with_#{suffix}")).to eq("#{prefix}dummy_with_#{suffix}.#{suffix}")
end
Expand All @@ -25,13 +39,13 @@
end

it "should return library path with abi version" do
expect(FFI.map_library_name(FFI::LibraryPath.new('vips', 42))).to be =~ /#{prefix}vips.*42/
expect(FFI.map_library_name(FFI::LibraryPath.new('vips', abi_number: 42))).to be =~ /#{prefix}vips.*42/
end

it "should return library path with root" do
root = "/non/existant/root"

expect(FFI.map_library_name(FFI::LibraryPath.new('vips', 42, root))).to be =~ /#{root}/#{prefix}vips.*42/
expect(FFI.map_library_name(FFI::LibraryPath.new('vips', abi_number: 42, root: root))).to be =~ /#{root}/#{prefix}vips.*42/
end
end

Expand Down

0 comments on commit a6f291e

Please sign in to comment.