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