pycall 0.1.0.alpha.20170711 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +13 -1
  4. data/CHANGES.md +35 -0
  5. data/Gemfile +0 -5
  6. data/README.md +41 -49
  7. data/Rakefile +22 -1
  8. data/appveyor.yml +9 -26
  9. data/examples/classifier_comparison.rb +52 -52
  10. data/examples/hist.rb +11 -11
  11. data/examples/notebooks/classifier_comparison.ipynb +51 -66
  12. data/examples/notebooks/forest_importances.ipynb +26 -49
  13. data/examples/notebooks/iruby_integration.ipynb +15 -36
  14. data/examples/notebooks/lorenz_attractor.ipynb +16 -47
  15. data/examples/notebooks/polar_axes.ipynb +29 -64
  16. data/examples/notebooks/sum_benchmarking.ipynb +109 -103
  17. data/examples/notebooks/xkcd_style.ipynb +12 -12
  18. data/examples/plot_forest_importances_faces.rb +8 -8
  19. data/examples/sum_benchmarking.rb +15 -19
  20. data/ext/pycall/extconf.rb +3 -0
  21. data/ext/pycall/gc.c +74 -0
  22. data/ext/pycall/libpython.c +217 -0
  23. data/ext/pycall/pycall.c +2184 -0
  24. data/ext/pycall/pycall_internal.h +700 -0
  25. data/ext/pycall/range.c +69 -0
  26. data/ext/pycall/ruby_wrapper.c +432 -0
  27. data/lib/pycall.rb +91 -19
  28. data/lib/pycall/dict.rb +28 -82
  29. data/lib/pycall/error.rb +10 -0
  30. data/lib/pycall/import.rb +45 -40
  31. data/lib/pycall/init.rb +44 -20
  32. data/lib/pycall/libpython.rb +6 -380
  33. data/lib/pycall/libpython/finder.rb +170 -0
  34. data/lib/pycall/list.rb +21 -51
  35. data/lib/pycall/pretty_print.rb +9 -0
  36. data/lib/pycall/pyerror.rb +14 -20
  37. data/lib/pycall/pyobject_wrapper.rb +157 -158
  38. data/lib/pycall/python/PyCall/__init__.py +1 -0
  39. data/lib/pycall/python/PyCall/six.py +23 -0
  40. data/lib/pycall/pytypeobject_wrapper.rb +79 -0
  41. data/lib/pycall/slice.rb +3 -22
  42. data/lib/pycall/tuple.rb +1 -7
  43. data/lib/pycall/version.rb +1 -1
  44. data/lib/pycall/wrapper_object_cache.rb +61 -0
  45. data/pycall.gemspec +4 -2
  46. data/tasks/pycall.rake +7 -0
  47. metadata +65 -27
  48. data/lib/pycall/eval.rb +0 -57
  49. data/lib/pycall/exception.rb +0 -13
  50. data/lib/pycall/pyobject.rb +0 -58
  51. data/lib/pycall/ruby_wrapper.rb +0 -137
  52. data/lib/pycall/type_object.rb +0 -11
  53. data/lib/pycall/types.rb +0 -19
  54. data/lib/pycall/utils.rb +0 -106
@@ -1,386 +1,12 @@
1
- require 'ffi'
2
- require 'pycall/libpython/pyobject_struct'
3
- require 'pycall/libpython/pytypeobject_struct'
4
-
5
1
  module PyCall
6
2
  module LibPython
7
- extend FFI::Library
8
-
9
- private_class_method
10
-
11
- def self.find_libpython(python = nil)
12
- debug = (ENV['DEBUG_FIND_LIBPYTHON'] == '1')
13
- dir_sep = File::ALT_SEPARATOR || File::SEPARATOR
14
- python ||= 'python'
15
- python_config = investigate_python_config(python)
16
-
17
- v = python_config[:VERSION]
18
- libprefix = FFI::Platform::LIBPREFIX
19
- libs = []
20
- %i(INSTSONAME LDLIBRARY).each do |key|
21
- lib = python_config[key]
22
- libs << lib << File.basename(lib) if lib
23
- end
24
- if (lib = python_config[:LIBRARY])
25
- libs << File.basename(lib, File.extname(lib))
26
- end
27
- libs << "#{libprefix}python#{v}" << "#{libprefix}python"
28
- libs.uniq!
29
-
30
- $stderr.puts "DEBUG(find_libpython) libs: #{libs.inspect}" if debug
31
-
32
- executable = python_config[:executable]
33
- libpaths = [ python_config[:LIBDIR] ]
34
- if FFI::Platform.windows?
35
- libpaths << File.dirname(executable)
36
- else
37
- libpaths << File.expand_path('../../lib', executable)
38
- end
39
- libpaths << python_config[:PYTHONFRAMEWORKPREFIX] if FFI::Platform.mac?
40
- exec_prefix = python_config[:exec_prefix]
41
- libpaths << exec_prefix << [exec_prefix, 'lib'].join(dir_sep)
42
- libpaths.compact!
43
-
44
- $stderr.puts "DEBUG(find_libpython) libpaths: #{libpaths.inspect}" if debug
45
-
46
- unless ENV['PYTHONHOME']
47
- # PYTHONHOME tells python where to look for both pure python and binary modules.
48
- # When it is set, it replaces both `prefix` and `exec_prefix`
49
- # and we thus need to set it to both in case they differ.
50
- # This is also what the documentation recommends.
51
- # However, they are documented to always be the same on Windows,
52
- # where it causes problems if we try to include both.
53
- if FFI::Platform.windows?
54
- ENV['PYTHONHOME'] = exec_prefix
55
- else
56
- ENV['PYTHONHOME'] = [python_config[:prefix], exec_prefix].join(':')
57
- end
58
-
59
- # Unfortunately, setting PYTHONHOME screws up Canopy's Python distribution?
60
- unless system(python, '-c', 'import site', out: File::NULL, err: File::NULL)
61
- ENV['PYTHONHOME'] = nil
62
- end
63
- end
64
-
65
- # Try LIBPYTHON environment variable first.
66
- if ENV['LIBPYTHON']
67
- if File.file?(ENV['LIBPYTHON'])
68
- begin
69
- libs = ffi_lib(ENV['LIBPYTHON'])
70
- return libs.first
71
- rescue LoadError
72
- end
73
- end
74
- $stderr.puts '[WARN] Ignore the wrong libpython location specified in LIBPYTHON environment variable.'
75
- end
76
-
77
- # Find libpython (we hope):
78
- libsuffix = FFI::Platform::LIBSUFFIX
79
- multiarch = python_config[:MULTIARCH] || python_config[:multiarch]
80
- libs.each do |lib|
81
- libpaths.each do |libpath|
82
- # NOTE: File.join doesn't use File::ALT_SEPARATOR
83
- libpath_libs = [ [libpath, lib].join(dir_sep) ]
84
- libpath_libs << [libpath, multiarch, lib].join(dir_sep) if multiarch
85
- libpath_libs.each do |libpath_lib|
86
- [
87
- libpath_lib,
88
- "#{libpath_lib}.#{libsuffix}"
89
- ].each do |fullname|
90
- unless File.file?(fullname)
91
- $stderr.puts "DEBUG(find_libpython) Unable to find #{fullname}" if debug
92
- next
93
- end
94
- begin
95
- dynlibs = ffi_lib(fullname)
96
- $stderr.puts "DEBUG(find_libpython) ffi_lib(#{fullname.inspect}) = #{dynlibs.inspect}" if debug
97
- return dynlibs.first
98
- rescue LoadError
99
- # skip load error
100
- end
101
- end
102
- end
103
- end
104
- end
105
-
106
- # Find libpython in the system path
107
- libs.each do |lib|
108
- begin
109
- dynlibs = ffi_lib(lib)
110
- $stderr.puts "DEBUG(find_libpython) ffi_lib(#{lib.inspect}) = #{dynlibs.inspect}" if debug
111
- return dynlibs.first
112
- rescue LoadError
113
- # skip load error
114
- end
115
- end
116
- end
117
-
118
- def self.investigate_python_config(python)
119
- python_env = { 'PYTHONIOENCODING' => 'UTF-8' }
120
- IO.popen(python_env, [python, python_investigator_py], 'r') do |io|
121
- {}.tap do |config|
122
- io.each_line do |line|
123
- key, value = line.chomp.split(': ', 2)
124
- config[key.to_sym] = value if value != 'None'
125
- end
126
- end
127
- end
128
- end
129
-
130
- def self.python_investigator_py
131
- File.expand_path('../python/investigator.py', __FILE__)
132
- end
133
-
134
- ffi_lib_flags :lazy, :global
135
- libpython = find_libpython ENV['PYTHON']
136
-
137
- define_singleton_method(:find_symbol) {|name| libpython.find_symbol(name.to_s) }
138
-
139
- attach_function :Py_GetVersion, [], :string
140
- PYTHON_DESCRIPTION = LibPython.Py_GetVersion().freeze
141
- PYTHON_VERSION = PYTHON_DESCRIPTION.split(' ', 2)[0].freeze
142
-
143
- # --- types ---
144
-
145
- if PYTHON_VERSION < '3.2'
146
- typedef :long, :Py_hash_t
147
- else
148
- typedef :ssize_t, :Py_hash_t
149
- end
150
-
151
- # --- global variables ---
152
-
153
- attach_variable :_Py_NoneStruct, PyObjectStruct
154
-
155
- def self.Py_None
156
- _Py_NoneStruct
157
- end
158
-
159
- attach_variable :PyType_Type, PyTypeObjectStruct
160
-
161
- if libpython.find_variable('PyInt_Type')
162
- has_PyInt_Type = true
163
- attach_variable :PyInt_Type, PyTypeObjectStruct
164
- else
165
- has_PyInt_Type = false
166
- attach_variable :PyInt_Type, :PyLong_Type, PyTypeObjectStruct
167
- end
168
-
169
- attach_variable :PyLong_Type, PyTypeObjectStruct
170
- attach_variable :PyBool_Type, PyTypeObjectStruct
171
- attach_variable :PyFloat_Type, PyTypeObjectStruct
172
- attach_variable :PyComplex_Type, PyTypeObjectStruct
173
- attach_variable :PyUnicode_Type, PyTypeObjectStruct
174
-
175
- if libpython.find_symbol('PyString_FromStringAndSize')
176
- string_as_bytes = false
177
- attach_variable :PyString_Type, PyTypeObjectStruct
178
- else
179
- string_as_bytes = true
180
- attach_variable :PyString_Type, :PyBytes_Type, PyTypeObjectStruct
181
- end
182
-
183
- attach_variable :PyList_Type, PyTypeObjectStruct
184
- attach_variable :PyTuple_Type, PyTypeObjectStruct
185
- attach_variable :PyDict_Type, PyTypeObjectStruct
186
- attach_variable :PySet_Type, PyTypeObjectStruct
187
-
188
- attach_variable :PyFunction_Type, PyTypeObjectStruct
189
- attach_variable :PyMethod_Type, PyTypeObjectStruct
190
-
191
- # --- exceptions ---
192
-
193
- attach_variable :PyExc_RuntimeError, PyObjectStruct.ptr
194
- attach_variable :PyExc_TypeError, PyObjectStruct.ptr
195
-
196
- # --- functions ---
197
-
198
- attach_function :Py_InitializeEx, [:int], :void
199
- attach_function :Py_IsInitialized, [], :int
200
- attach_function :PySys_SetArgvEx, [:int, :pointer, :int], :void
201
-
202
- # Reference count
203
-
204
- attach_function :Py_IncRef, [PyObjectStruct.by_ref], :void
205
- attach_function :Py_DecRef, [PyObjectStruct.by_ref], :void
206
-
207
- # Object
208
-
209
- attach_function :_PyObject_New, [PyTypeObjectStruct.ptr], PyObjectStruct.ptr
210
- attach_function :PyObject_RichCompare, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, :int], PyObjectStruct.by_ref
211
- attach_function :PyObject_GetAttrString, [PyObjectStruct.by_ref, :string], PyObjectStruct.by_ref
212
- attach_function :PyObject_SetAttrString, [PyObjectStruct.by_ref, :string, PyObjectStruct.by_ref], :int
213
- attach_function :PyObject_HasAttrString, [PyObjectStruct.by_ref, :string], :int
214
- attach_function :PyObject_GetItem, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
215
- attach_function :PyObject_SetItem, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
216
- attach_function :PyObject_Call, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
217
- attach_function :PyObject_IsInstance, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
218
- attach_function :PyObject_Dir, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
219
- attach_function :PyObject_Hash, [PyObjectStruct.by_ref], :Py_hash_t
220
- attach_function :PyObject_Repr, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
221
- attach_function :PyObject_Str, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
222
- attach_function :PyObject_Type, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
223
- attach_function :PyCallable_Check, [PyObjectStruct.by_ref], :int
224
-
225
- # Type
226
-
227
- attach_function :PyType_Ready, [PyTypeObjectStruct.ptr], :int
228
-
229
- # Bool
230
-
231
- attach_function :PyBool_FromLong, [:long], PyObjectStruct.by_ref
3
+ require 'pycall/libpython/finder'
232
4
 
233
- # Integer
234
-
235
- if has_PyInt_Type
236
- attach_function :PyInt_AsSsize_t, [PyObjectStruct.by_ref], :ssize_t
237
- else
238
- attach_function :PyInt_AsSsize_t, :PyLong_AsSsize_t, [PyObjectStruct.by_ref], :ssize_t
239
- end
240
-
241
- if has_PyInt_Type
242
- attach_function :PyInt_FromSsize_t, [:ssize_t], PyObjectStruct.by_ref
243
- else
244
- attach_function :PyInt_FromSsize_t, :PyLong_FromSsize_t, [:ssize_t], PyObjectStruct.by_ref
245
- end
246
-
247
- # Float
248
-
249
- attach_function :PyFloat_FromDouble, [:double], PyObjectStruct.by_ref
250
- attach_function :PyFloat_AsDouble, [PyObjectStruct.by_ref], :double
251
-
252
- # Complex
253
-
254
- attach_function :PyComplex_RealAsDouble, [PyObjectStruct.by_ref], :double
255
- attach_function :PyComplex_ImagAsDouble, [PyObjectStruct.by_ref], :double
256
-
257
- # String
258
-
259
- if string_as_bytes
260
- attach_function :PyString_FromStringAndSize, :PyBytes_FromStringAndSize, [:string, :ssize_t], PyObjectStruct.by_ref
261
- else
262
- attach_function :PyString_FromStringAndSize, [:string, :ssize_t], PyObjectStruct.by_ref
263
- end
264
-
265
- # PyString_AsStringAndSize :: (PyPtr, char**, int*) -> int
266
- if string_as_bytes
267
- attach_function :PyString_AsStringAndSize, :PyBytes_AsStringAndSize, [PyObjectStruct.by_ref, :pointer, :pointer], :int
268
- else
269
- attach_function :PyString_AsStringAndSize, [PyObjectStruct.by_ref, :pointer, :pointer], :int
5
+ def self.handle
6
+ # NOTE: PyCall.init redefine this method.
7
+ # See pycall/init.rb for the detail.
8
+ PyCall.init
9
+ handle
270
10
  end
271
-
272
- # Unicode
273
-
274
- # PyUnicode_DecodeUTF8
275
- case
276
- when libpython.find_symbol('PyUnicode_DecodeUTF8')
277
- attach_function :PyUnicode_DecodeUTF8, [:string, :ssize_t, :string], PyObjectStruct.by_ref
278
- when libpython.find_symbol('PyUnicodeUCS4_DecodeUTF8')
279
- attach_function :PyUnicode_DecodeUTF8, :PyUnicodeUCS4_DecodeUTF8, [:string, :ssize_t, :string], PyObjectStruct.by_ref
280
- when libpython.find_symbol('PyUnicodeUCS2_DecodeUTF8')
281
- attach_function :PyUnicode_DecodeUTF8, :PyUnicodeUCS2_DecodeUTF8, [:string, :ssize_t, :string], PyObjectStruct.by_ref
282
- end
283
-
284
- # PyUnicode_AsUTF8String
285
- case
286
- when libpython.find_symbol('PyUnicode_AsUTF8String')
287
- attach_function :PyUnicode_AsUTF8String, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
288
- when libpython.find_symbol('PyUnicodeUCS4_AsUTF8String')
289
- attach_function :PyUnicode_AsUTF8String, :PyUnicodeUCS4_AsUTF8String, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
290
- when libpython.find_symbol('PyUnicodeUCS2_AsUTF8String')
291
- attach_function :PyUnicode_AsUTF8String, :PyUnicodeUCS2_AsUTF8String, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
292
- end
293
-
294
- # Tuple
295
-
296
- attach_function :PyTuple_New, [:ssize_t], PyObjectStruct.by_ref
297
- attach_function :PyTuple_GetItem, [PyObjectStruct.by_ref, :ssize_t], PyObjectStruct.by_ref
298
- attach_function :PyTuple_SetItem, [PyObjectStruct.by_ref, :ssize_t, PyObjectStruct.by_ref], :int
299
- attach_function :PyTuple_Size, [PyObjectStruct.by_ref], :ssize_t
300
-
301
- # Slice
302
-
303
- attach_function :PySlice_New, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
304
-
305
- # List
306
-
307
- attach_function :PyList_New, [:ssize_t], PyObjectStruct.by_ref
308
- attach_function :PyList_Size, [PyObjectStruct.by_ref], :ssize_t
309
- attach_function :PyList_Append, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
310
-
311
- # Sequence
312
-
313
- attach_function :PySequence_Size, [PyObjectStruct.by_ref], :ssize_t
314
- attach_function :PySequence_GetItem, [PyObjectStruct.by_ref, :ssize_t], PyObjectStruct.by_ref
315
- attach_function :PySequence_Contains, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
316
-
317
- # Dict
318
-
319
- attach_function :PyDict_New, [], PyObjectStruct.by_ref
320
- attach_function :PyDict_GetItem, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
321
- attach_function :PyDict_GetItemString, [PyObjectStruct.by_ref, :string], PyObjectStruct.by_ref
322
- attach_function :PyDict_SetItem, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
323
- attach_function :PyDict_SetItemString, [PyObjectStruct.by_ref, :string, PyObjectStruct.by_ref], :int
324
- attach_function :PyDict_DelItem, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
325
- attach_function :PyDict_DelItemString, [PyObjectStruct.by_ref, :string], :int
326
- attach_function :PyDict_Size, [PyObjectStruct.by_ref], :ssize_t
327
- attach_function :PyDict_Keys, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
328
- attach_function :PyDict_Values, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
329
- attach_function :PyDict_Items, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
330
- attach_function :PyDict_Contains, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
331
-
332
- # Set
333
-
334
- attach_function :PySet_Size, [PyObjectStruct.by_ref], :ssize_t
335
- attach_function :PySet_Contains, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], :int
336
-
337
- # Method
338
-
339
- attach_function :PyCFunction_NewEx, [PyMethodDef.ptr, :pointer, :pointer], PyObjectStruct.ptr
340
-
341
- # Weakref
342
-
343
- attach_function :PyWeakref_NewRef, [PyObjectStruct.ptr, PyObjectStruct.ptr], PyObjectStruct.ptr
344
-
345
- # Module
346
-
347
- attach_function :PyModule_GetDict, [PyObjectStruct.by_ref], PyObjectStruct.by_ref
348
-
349
- # Import
350
-
351
- attach_function :PyImport_GetModuleDict, [], PyObjectStruct.by_ref
352
- attach_function :PyImport_ImportModule, [:string], PyObjectStruct.by_ref
353
- attach_function :PyImport_ImportModuleLevel, [:string, PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref, :int], PyObjectStruct.by_ref
354
-
355
- # Operators
356
-
357
- attach_function :PyNumber_Add, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
358
- attach_function :PyNumber_Subtract, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
359
- attach_function :PyNumber_Multiply, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
360
- attach_function :PyNumber_TrueDivide, [PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
361
- attach_function :PyNumber_Power, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
362
-
363
- # Compiler
364
-
365
- attach_function :Py_CompileString, [:string, :string, :int], PyObjectStruct.by_ref
366
- attach_function :PyEval_EvalCode, [PyObjectStruct.by_ref, PyObjectStruct.by_ref, PyObjectStruct.by_ref], PyObjectStruct.by_ref
367
-
368
- # Error
369
-
370
- attach_function :PyErr_Clear, [], :void
371
- attach_function :PyErr_Print, [], :void
372
- attach_function :PyErr_Occurred, [], PyObjectStruct.by_ref
373
- attach_function :PyErr_Fetch, [:pointer, :pointer, :pointer], :void
374
- attach_function :PyErr_NormalizeException, [:pointer, :pointer, :pointer], :void
375
- attach_function :PyErr_SetString, [PyObjectStruct.ptr, :string], :void
376
-
377
- public_class_method
378
- end
379
-
380
- PYTHON_DESCRIPTION = LibPython::PYTHON_DESCRIPTION
381
- PYTHON_VERSION = LibPython::PYTHON_VERSION
382
-
383
- def self.unicode_literals?
384
- @unicode_literals ||= (PYTHON_VERSION >= '3.0')
385
11
  end
386
12
  end
@@ -0,0 +1,170 @@
1
+ require 'pycall/error'
2
+ require 'fiddle'
3
+
4
+ module PyCall
5
+ module LibPython
6
+ module Finder
7
+ case RUBY_PLATFORM
8
+ when /cygwin/
9
+ libprefix = 'cyg'
10
+ libsuffix = 'dll'
11
+ when /mingw/, /mswin/
12
+ libprefix = ''
13
+ libsuffix = 'dll'
14
+ when /darwin/
15
+ libsuffix = 'dylib'
16
+ end
17
+
18
+ LIBPREFIX = libprefix || 'lib'
19
+ LIBSUFFIX = libsuffix || 'so'
20
+
21
+ class << self
22
+ def find_libpython(python = nil)
23
+ debug_report("find_libpython(#{python.inspect})")
24
+ if python
25
+ begin
26
+ python_config = investigate_python_config(python)
27
+ rescue
28
+ raise ::PyCall::PythonNotFound
29
+ end
30
+ else
31
+ %w[python python3].each do |python_cmd|
32
+ begin
33
+ python_config = investigate_python_config(python_cmd)
34
+ python = python_cmd
35
+ break
36
+ rescue
37
+ raise ::PyCall::PythonNotFound
38
+ end
39
+ end
40
+ end
41
+
42
+ libs = make_libs(python_config)
43
+ libpaths = make_libpaths(python_config)
44
+
45
+ # Try LIBPYTHON environment variable first.
46
+ if (libpython = ENV['LIBPYTHON'])
47
+ if File.file?(libpython)
48
+ begin
49
+ return dlopen(libpython)
50
+ rescue Fiddle::DLError
51
+ debug_report "#{$!.class}: #{$!.message}"
52
+ else
53
+ debug_report "Success to dlopen #{libpython.inspect} from ENV['LIBPYTHON']"
54
+ end
55
+ end
56
+ warn "WARNING(#{self}.#{__method__}) Ignore the wrong libpython location specified in ENV['LIBPYTHON']."
57
+ end
58
+
59
+ # Find libpython (we hope):
60
+ multiarch = python_config[:MULTIARCH] || python_config[:multiarch]
61
+ libs.each do |lib|
62
+ libpaths.each do |libpath|
63
+ libpath_libs = [ File.join(libpath, lib) ]
64
+ libpath_libs << File.join(libpath, multiarch, lib) if multiarch
65
+ libpath_libs.each do |libpath_lib|
66
+ [ libpath_lib, "#{libpath_lib}.#{LIBSUFFIX}" ].each do |fullname|
67
+ unless File.file? fullname
68
+ debug_report "Unable to find #{fullname}"
69
+ next
70
+ end
71
+ begin
72
+ return dlopen(libpath_lib)
73
+ rescue Fiddle::DLError
74
+ debug_report "#{$!.class}: #{$!.message}"
75
+ else
76
+ debug_report "Success to dlopen #{libpaht_lib}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ # Find libpython in the system path
84
+ libs.each do |lib|
85
+ begin
86
+ return dlopen(lib)
87
+ rescue Fiddle::DLError
88
+ debug_report "#{$!.class}: #{$!.message}"
89
+ else
90
+ debug_report "Success to dlopen #{lib}"
91
+ end
92
+ end
93
+
94
+ raise ::PyCall::PythonNotFound
95
+ end
96
+
97
+ def investigate_python_config(python)
98
+ python_env = { 'PYTHONIOENCODING' => 'UTF-8' }
99
+ debug_report("investigate_python_config(#{python.inspect})")
100
+ IO.popen(python_env, [python, python_investigator_py], 'r') do |io|
101
+ {}.tap do |config|
102
+ io.each_line do |line|
103
+ key, value = line.chomp.split(': ', 2)
104
+ config[key.to_sym] = value if value != 'None'
105
+ end
106
+ end
107
+ end
108
+ rescue Errno::ENOENT
109
+ raise PyCall::PythonInvestigationFailed
110
+ end
111
+
112
+ def python_investigator_py
113
+ File.expand_path('../../python/investigator.py', __FILE__)
114
+ end
115
+
116
+ def make_libs(python_config)
117
+ libs = []
118
+ %i(INSTSONAME LDLIBRARY).each do |key|
119
+ lib = python_config[key]
120
+ libs << lib << File.basename(lib) if lib
121
+ end
122
+ if (lib = python_config[:LIBRARY])
123
+ libs << File.basename(lib, File.extname(lib))
124
+ end
125
+
126
+ v = python_config[:VERSION]
127
+ libs << "#{LIBPREFIX}python#{v}" << "#{LIBPREFIX}python"
128
+ libs.uniq!
129
+
130
+ debug_report "libs: #{libs.inspect}"
131
+ return libs
132
+ end
133
+
134
+ def make_libpaths(python_config)
135
+ executable = python_config[:executable]
136
+ libpaths = [ python_config[:LIBDIR] ]
137
+ if Fiddle::WINDOWS
138
+ libpaths << File.dirname(executable)
139
+ else
140
+ libpaths << File.expand_path('../../lib', executable)
141
+ end
142
+ libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
143
+ exec_prefix = python_config[:exec_prefix]
144
+ libpaths << exec_prefix << File.join(exec_prefix, 'lib')
145
+ libpaths.compact!
146
+
147
+ debug_report "libpaths: #{libpaths.inspect}"
148
+ return libpaths
149
+ end
150
+
151
+ private
152
+
153
+ def dlopen(libname)
154
+ Fiddle.dlopen(libname).tap do |handle|
155
+ debug_report("dlopen(#{libname.inspect}) = #{handle.inspect}") if handle
156
+ end
157
+ end
158
+
159
+ def debug_report(message)
160
+ return unless debug?
161
+ $stderr.puts "DEBUG(find_libpython) #{message}"
162
+ end
163
+
164
+ def debug?
165
+ @debug ||= (ENV['PYCALL_DEBUG_FIND_LIBPYTHON'] == '1')
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end