diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2015-04-14 15:45:35 -0700 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2015-04-14 15:45:35 -0700 |
commit | fa032335b91203e38eaf39bdc873fb8398deba1d (patch) | |
tree | 02e6017a0b1d22fdb8694c67f718289c9cebb21a /lib | |
parent | 10c7da3b6002a59e21c9c8ee202f6f713c3959f9 (diff) | |
parent | e87c4faaa6f838815e6f3a7fcb6722a0aa744cf9 (diff) | |
download | ffi-yajl-fa032335b91203e38eaf39bdc873fb8398deba1d.tar.gz |
Merge pull request #52 from chef/lcg/dlopen-extension
add DLopen extension
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ffi_yajl.rb | 4 | ||||
-rw-r--r-- | lib/ffi_yajl/ext.rb | 54 | ||||
-rw-r--r-- | lib/ffi_yajl/ffi.rb | 10 | ||||
-rw-r--r-- | lib/ffi_yajl/map_library_name.rb | 129 |
4 files changed, 105 insertions, 92 deletions
diff --git a/lib/ffi_yajl.rb b/lib/ffi_yajl.rb index 7f39189..5edc4f6 100644 --- a/lib/ffi_yajl.rb +++ b/lib/ffi_yajl.rb @@ -19,10 +19,6 @@ elsif ENV['FORCE_FFI_YAJL'] == "ffi" require 'ffi_yajl/ffi' elsif RUBY_PLATFORM == "java" require 'ffi_yajl/ffi' -elsif defined?(Yajl) - warn "the ffi-yajl and yajl-ruby gems have incompatible C libyajl libs and should not be loaded in the same Ruby VM" - warn "falling back to ffi which might work (or might not, no promises)" - require 'ffi_yajl/ffi' else begin require 'ffi_yajl/ext' diff --git a/lib/ffi_yajl/ext.rb b/lib/ffi_yajl/ext.rb index bc4d410..7ecf172 100644 --- a/lib/ffi_yajl/ext.rb +++ b/lib/ffi_yajl/ext.rb @@ -2,62 +2,14 @@ require 'rubygems' require 'ffi_yajl/encoder' require 'ffi_yajl/parser' -require 'libyajl2' +require 'ffi_yajl/ext/dlopen' require 'ffi_yajl/map_library_name' module FFI_Yajl extend FFI_Yajl::MapLibraryName + extend FFI_Yajl::Ext::Dlopen - libname = map_library_name - libpath = File.expand_path(File.join(Libyajl2.opt_path, libname)) - - # - # FFS, what exactly was so wrong with DL.dlopen that ruby had to get rid of it??? - # - - def self.try_fiddle_dlopen(libpath) - require 'fiddle' - if defined?(Fiddle) && Fiddle.respond_to?(:dlopen) - ::Fiddle.dlopen(libpath) - true - else - false - end - rescue LoadError - return false - end - - def self.try_dl_dlopen(libpath) - require 'dl' - if defined?(DL) && DL.respond_to?(:dlopen) - ::DL.dlopen(libpath) - true - else - false - end - rescue LoadError - return false - end - - def self.try_ffi_dlopen(libpath) - require 'ffi' - require 'rbconfig' - extend ::FFI::Library - ffi_lib 'dl' - attach_function 'dlopen', :dlopen, [:string, :int], :void - if RbConfig::CONFIG['host_os'] =~ /linux/i - dlopen libpath, 0x102 # linux: RTLD_GLOBAL | RTLD_NOW - else - dlopen libpath, 0 - end - true - rescue LoadError - return false - end - - unless try_fiddle_dlopen(libpath) || try_dl_dlopen(libpath) || try_ffi_dlopen(libpath) - raise "cannot find dlopen via Fiddle, DL or FFI, what am I supposed to do?" - end + dlopen_yajl_library class Parser require 'ffi_yajl/ext/parser' diff --git a/lib/ffi_yajl/ffi.rb b/lib/ffi_yajl/ffi.rb index 32335ac..b38d5fb 100644 --- a/lib/ffi_yajl/ffi.rb +++ b/lib/ffi_yajl/ffi.rb @@ -16,15 +16,7 @@ module FFI_Yajl extend FFI_Yajl::MapLibraryName - libname = map_library_name - libpath = File.expand_path(File.join(Libyajl2.opt_path, libname)) - - if File.file?(libpath) - # use our vendored version of libyajl2 if we find it installed - ffi_lib libpath - else - ffi_lib 'yajl' - end + ffi_open_yajl_library class YajlCallbacks < ::FFI::Struct layout :yajl_null, :pointer, diff --git a/lib/ffi_yajl/map_library_name.rb b/lib/ffi_yajl/map_library_name.rb index 3518d07..8723424 100644 --- a/lib/ffi_yajl/map_library_name.rb +++ b/lib/ffi_yajl/map_library_name.rb @@ -1,37 +1,110 @@ +# Copyright (c) 2015 Lamont Granquist +# Copyright (c) 2015 Chef Software, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -require 'ffi_yajl/platform' +require 'libyajl2' + +# Mixin for use in finding the right yajl library on the system. The 'caller' +# needs to also mixin either the FFI module or the DLopen module. Those are +# deliberately not mixed in to avoid loading the dlopen module in the ffi +# codepath (which fails on jruby which does not have that C extension). module FFI_Yajl module MapLibraryName - include FFI_Yajl::Platform - def map_library_name - # this is the right answer for the internally built libyajl on windows - return "libyajl.so" if windows? - - # this is largely copied from the FFI.map_library_name algorithm, we most likely need - # the windows code eventually to support not using the embedded libyajl2-gem - libprefix = - case RbConfig::CONFIG['host_os'].downcase - when /mingw|mswin/ - '' - when /cygwin/ - 'cyg' - else - 'lib' + + private + + # Stub for tests to override the host_os + # + # @api private + # @return Array<String> lower case ruby host_os string + def host_os + RbConfig::CONFIG['host_os'].downcase + end + + # Array of yajl library names on the platform. Some platforms like Windows + # and Mac may have different names/extensions. + # + # @api private + # @return Array<String> Array of yajl library names for platform + def library_names + case host_os + when /mingw|mswin/ + [ "libyajl.so", "yajl.dll" ] + when /cygwin/ + [ "libyajl.so", "cygyajl.dll" ] + when /darwin/ + [ "libyajl.bundle", "libyajl.dylib" ] + else + [ "libyajl.so" ] + end + end + + # Array of yajl library names prepended with the libyajl2 path to use to + # load those directly and bypass the system libyajl by default. Since + # these are full paths, this API checks to ensure that the file exists on + # the filesystem. May return an empty array. + # + # @api private + # @return Array<String> Array of full paths to libyajl2 gem libraries + def expanded_library_names + library_names.map do |libname| + pathname = File.expand_path(File.join(Libyajl2.opt_path, libname)) + pathname if File.file?(pathname) + end.compact + end + + # Iterate across the expanded library names in the libyajl2-gem and then + # attempt to load the system libraries. Uses the native dlopen extension + # that ships in this gem. + # + # @api private + def dlopen_yajl_library + found = false + ( expanded_library_names + library_names ).each do |libname| + begin + dlopen(libname) + found = true + break + rescue ArgumentError end - libsuffix = - case RbConfig::CONFIG['host_os'].downcase - when /darwin/ - 'bundle' - when /linux|bsd|solaris|sunos/ - 'so' - when /mingw|mswin|cygwin/ - 'dll' - else - # Punt and just assume a sane unix (i.e. anything but AIX) - 'so' + end + raise "cannot find yajl library for platform" unless found + end + + # Iterate across the expanded library names in the libyajl2-gem and attempt + # to load them. If they are missing just use `ffi_lib 'yajl'` to accept + # the FFI default algorithm to find the library. + # + # @api private + def ffi_open_yajl_library + found = false + expanded_library_names.each do |libname| + begin + ffi_lib libname + found = true + rescue LoadError end - libprefix + "yajl" + ".#{libsuffix}" + end + ffi_lib 'yajl' unless found end end end |