module PyCall::LibPython::Finder

Constants

DEFAULT_PYTHON
LIBPREFIX
LIBSUFFIX

Public Class Methods

candidate_names(python_config) click to toggle source
# File lib/pycall/libpython/finder.rb, line 69
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

  names.compact!
  names.uniq!

  debug_report("candidate_names: #{names}")
  return names
end
candidate_paths(python_config) { |python_config| ... } click to toggle source
# File lib/pycall/libpython/finder.rb, line 99
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

  # library names for searching in system library paths
  lib_basenames.each do |name|
    yield name
  end
end
find_libpython(python = nil) click to toggle source
# File lib/pycall/libpython/finder.rb, line 40
def find_libpython(python = nil)
  debug_report("find_libpython(#{python.inspect})")
  python, python_config = find_python_config(python)
  suffix = python_config[:SHLIB_SUFFIX]

  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(normalized)
      rescue Fiddle::DLError
        debug_report "dlopen(#{normalized.inspect}) => #{$!.class}: #{$!.message}"
      end
    else
      debug_report("Not found.")
    end
  end
end
find_python_config(python = nil) click to toggle source
# File lib/pycall/libpython/finder.rb, line 28
def find_python_config(python = nil)
  python ||= DEFAULT_PYTHON
  Array(python).each do |python_cmd|
    begin
      python_config = investigate_python_config(python_cmd)
      return [python_cmd, python_config] unless python_config.empty?
    rescue
    end
  end
  raise ::PyCall::PythonNotFound
end
investigate_python_config(python) click to toggle source
# File lib/pycall/libpython/finder.rb, line 141
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|
      io.each_line do |line|
        next unless line =~ /: /
        key, value = line.chomp.split(': ', 2)
        case value
        when 'True', 'true', 'False', 'false'
          value = (value == 'True' || value == 'true')
        end
        config[key.to_sym] = value if value != 'None'
      end
    end
  end
rescue Errno::ENOENT
  raise PyCall::PythonInvestigationFailed
end
make_libpaths(python_config) click to toggle source
# File lib/pycall/libpython/finder.rb, line 165
def make_libpaths(python_config)
  libpaths = python_config.values_at(:LIBPL, :srcdir, :LIBDIR)

  if windows?
    libpaths << File.dirname(python_config[:executable])
  else
    libpaths << File.expand_path('../../lib', python_config[:executable])
  end

  if apple?
    libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
  end

  exec_prefix = python_config[:exec_prefix]
  libpaths << exec_prefix
  libpaths << File.join(exec_prefix, 'lib')

  libpaths.compact!
  libpaths.uniq!

  debug_report "libpaths: #{libpaths.inspect}"
  return libpaths
end
normalize_path(path, suffix, apple_p=apple?) click to toggle source
# File lib/pycall/libpython/finder.rb, line 119
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
python_investigator_py() click to toggle source
# File lib/pycall/libpython/finder.rb, line 161
def python_investigator_py
  File.expand_path('../../python/investigator.py', __FILE__)
end
remove_suffix_apple(path) click to toggle source

Strip off .so or .dylib

# File lib/pycall/libpython/finder.rb, line 137
def remove_suffix_apple(path)
  path.sub(/\.(?:dylib|so)\z/, '')
end

Private Class Methods

apple?() click to toggle source
# File lib/pycall/libpython/finder.rb, line 195
def apple?
  RUBY_PLATFORM.include?("darwin")
end
debug?() click to toggle source
# File lib/pycall/libpython/finder.rb, line 210
def debug?
  @debug ||= (ENV['PYCALL_DEBUG_FIND_LIBPYTHON'] == '1')
end
debug_report(message) click to toggle source
# File lib/pycall/libpython/finder.rb, line 205
def debug_report(message)
  return unless debug?
  $stderr.puts "DEBUG(find_libpython) #{message}"
end
dlopen(libname) click to toggle source
# File lib/pycall/libpython/finder.rb, line 199
def dlopen(libname)
  Fiddle.dlopen(libname).tap do |handle|
    debug_report("dlopen(#{libname.inspect}) = #{handle.inspect}") if handle
  end
end
windows?() click to toggle source
# File lib/pycall/libpython/finder.rb, line 191
def windows?
  Fiddle::WINDOWS
end