pycall 0.1.0.alpha.20170711 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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