lib/pycall/libpython/finder.rb in pycall-1.3.1 vs lib/pycall/libpython/finder.rb in pycall-1.4.0

- old
+ new

@@ -1,7 +1,8 @@ require 'pycall/error' require 'fiddle' +require 'pathname' module PyCall module LibPython module Finder case RUBY_PLATFORM @@ -37,66 +38,108 @@ end def find_libpython(python = nil) debug_report("find_libpython(#{python.inspect})") python, python_config = find_python_config(python) + suffix = python_config[:SHLIB_SUFFIX] - # Try LIBPYTHON environment variable first. - if (libpython = ENV['LIBPYTHON']) - if File.file?(libpython) + use_conda = (ENV.fetch("CONDA_PREFIX", nil) == File.dirname(python_config[:executable])) + python_home = if !ENV.key?("PYTHONHOME") || use_conda + python_config[:PYTHONHOME] + else + ENV["PYTHONHOME"] + end + ENV["PYTHONHOME"] = python_home + + candidate_paths(python_config) do |path| + debug_report("Candidate: #{path}") + normalized = normalize_path(path, suffix) + if normalized + debug_report("Trying to dlopen: #{normalized}") begin - return dlopen(libpython) + return dlopen(normalized) rescue Fiddle::DLError - debug_report "#{$!.class}: #{$!.message}" - else - debug_report "Success to dlopen #{libpython.inspect} from ENV['LIBPYTHON']" + debug_report "dlopen(#{normalized.inspect}) => #{$!.class}: #{$!.message}" end + else + debug_report("Not found.") end - warn "WARNING(#{self}.#{__method__}) Ignore the wrong libpython location specified in ENV['LIBPYTHON']." end + end - # Find libpython (we hope): - set_PYTHONHOME(python_config) - libs = make_libs(python_config) - libpaths = make_libpaths(python_config) - multiarch = python_config[:MULTIARCH] || python_config[:multiarch] - libs.each do |lib| - libpaths.each do |libpath| - libpath_libs = [ File.join(libpath, lib) ] - libpath_libs << File.join(libpath, multiarch, lib) if multiarch - libpath_libs.each do |libpath_lib| - [ libpath_lib, "#{libpath_lib}.#{LIBSUFFIX}" ].each do |fullname| - unless File.file? fullname - debug_report "Unable to find #{fullname}" - next - end - begin - return dlopen(libpath_lib) - rescue Fiddle::DLError - debug_report "#{$!.class}: #{$!.message}" - else - debug_report "Success to dlopen #{libpaht_lib}" - end - end - end - end + def candidate_names(python_config) + names = [] + names << python_config[:LDLIBRARY] if python_config[:LDLIBRARY] + suffix = python_config[:SHLIB_SUFFIX] + if python_config[:LIBRARY] + ext = File.extname(python_config[:LIBRARY]) + names << python_config[:LIBRARY].delete_suffix(ext) + suffix end + dlprefix = if windows? then "" else "lib" end + sysdata = { + v_major: python_config[:version_major], + VERSION: python_config[:VERSION], + ABIFLAGS: python_config[:ABIFLAGS], + } + [ + "python%{VERSION}%{ABIFLAGS}" % sysdata, + "python%{VERSION}" % sysdata, + "python%{v_major}" % sysdata, + "python" + ].each do |stem| + names << "#{dlprefix}#{stem}#{suffix}" + end - # Find libpython in the system path - libs.each do |lib| - begin - return dlopen(lib) - rescue Fiddle::DLError - debug_report "#{$!.class}: #{$!.message}" - else - debug_report "Success to dlopen #{lib}" + names.compact! + names.uniq! + + debug_report("candidate_names: #{names}") + return names + end + + def candidate_paths(python_config) + # The candidate library that linked by executable + yield python_config[:linked_libpython] + + lib_dirs = make_libpaths(python_config) + lib_basenames = candidate_names(python_config) + + # candidates by absolute paths + lib_dirs.each do |dir| + lib_basenames.each do |name| + yield File.join(dir, name) end end - raise ::PyCall::PythonNotFound + # library names for searching in system library paths + lib_basenames.each do |name| + yield name + end end + def normalize_path(path, suffix, apple_p=apple?) + return nil if path.nil? + case + when path.nil?, + Pathname.new(path).relative? + nil + when File.exist?(path) + File.realpath(path) + when File.exist?(path + suffix) + File.realpath(path + suffix) + when apple_p + normalize_path(remove_suffix_apple(path), ".so", false) + else + nil + end + end + + # Strip off .so or .dylib + def remove_suffix_apple(path) + path.sub(/\.(?:dylib|so)\z/, '') + end + def investigate_python_config(python) python_env = { 'PYTHONIOENCODING' => 'UTF-8' } debug_report("investigate_python_config(#{python.inspect})") IO.popen(python_env, [python, python_investigator_py], 'r') do |io| {}.tap do |config| @@ -117,58 +160,44 @@ def python_investigator_py File.expand_path('../../python/investigator.py', __FILE__) end - def set_PYTHONHOME(python_config) - if !ENV.has_key?('PYTHONHOME') - case RUBY_PLATFORM - when /mingw32/, /cygwin/, /mswin/ - ENV['PYTHONHOME'] = python_config[:exec_prefix] - else - ENV['PYTHONHOME'] = python_config.values_at(:prefix, :exec_prefix).join(':') - end - end - end + def make_libpaths(python_config) + libpaths = python_config.values_at(:LIBPL, :srcdir, :LIBDIR) - def make_libs(python_config) - libs = [] - %i(INSTSONAME LDLIBRARY).each do |key| - lib = python_config[key] - libs << lib << File.basename(lib) if lib + if windows? + libpaths << File.dirname(python_config[:executable]) + else + libpaths << File.expand_path('../../lib', python_config[:executable]) end - if (lib = python_config[:LIBRARY]) - libs << File.basename(lib, File.extname(lib)) - end - v = python_config[:VERSION] - libs << "#{LIBPREFIX}python#{v}" << "#{LIBPREFIX}python" - libs.uniq! - - debug_report "libs: #{libs.inspect}" - return libs - end - - def make_libpaths(python_config) - executable = python_config[:executable] - libpaths = [ python_config[:LIBDIR] ] - if Fiddle::WINDOWS - libpaths << File.dirname(executable) - else - libpaths << File.expand_path('../../lib', executable) + if apple? + libpaths << python_config[:PYTHONFRAMEWORKPREFIX] end - libpaths << python_config[:PYTHONFRAMEWORKPREFIX] + exec_prefix = python_config[:exec_prefix] - libpaths << exec_prefix << File.join(exec_prefix, 'lib') + libpaths << exec_prefix + libpaths << File.join(exec_prefix, 'lib') + libpaths.compact! + libpaths.uniq! debug_report "libpaths: #{libpaths.inspect}" return libpaths end private + def windows? + Fiddle::WINDOWS + end + + def apple? + RUBY_PLATFORM.include?("darwin") + end + def dlopen(libname) Fiddle.dlopen(libname).tap do |handle| debug_report("dlopen(#{libname.inspect}) = #{handle.inspect}") if handle end end @@ -181,7 +210,26 @@ def debug? @debug ||= (ENV['PYCALL_DEBUG_FIND_LIBPYTHON'] == '1') end end end + end +end + +if __FILE__ == $0 + require "pp" + python, python_config = PyCall::LibPython::Finder.find_python_config + + puts "python_config:" + pp python_config + + puts "\ncandidate_names:" + p PyCall::LibPython::Finder.candidate_names(python_config) + + puts "\nlib_dirs:" + p PyCall::LibPython::Finder.make_libpaths(python_config) + + puts "\ncandidate_paths:" + PyCall::LibPython::Finder.candidate_paths(python_config) do |path| + puts "- #{path}" end end