Module: PyCall::LibPython::Finder

Defined in:
lib/pycall/libpython/finder.rb

Constant Summary collapse

LIBPREFIX =
libprefix || 'lib'
LIBSUFFIX =
libsuffix || 'so'
DEFAULT_PYTHON =
[
  -'python3',
  -'python',
].freeze

Class Method Summary collapse

Class Method Details

.candidate_names(python_config) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 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) {|| ... } ⇒ Object

Yields:

  • ()


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 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) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
# 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) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# 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) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# 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?) ) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# 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_pyObject



161
162
163
# File 'lib/pycall/libpython/finder.rb', line 161

def python_investigator_py
  File.expand_path('../../python/investigator.py', __FILE__)
end

.remove_suffix_apple(path) ⇒ Object

Strip off .so or .dylib



137
138
139
# File 'lib/pycall/libpython/finder.rb', line 137

def remove_suffix_apple(path)
  path.sub(/\.(?:dylib|so)\z/, '')
end