pycall 1.2.0 → 1.4.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.
@@ -356,11 +356,16 @@ PyRuby_getattro_with_gvl(PyRubyObject *pyro, PyObject *pyobj_name)
356
356
 
357
357
  VALUE cPyRubyPtr;
358
358
 
359
- const rb_data_type_t pycall_pyrubyptr_data_type = {
359
+ static rb_data_type_t pycall_pyrubyptr_data_type = {
360
360
  "PyCall::PyRubyPtr",
361
361
  { 0, pycall_pyptr_free, pycall_pyptr_memsize, },
362
362
  #ifdef RUBY_TYPED_FREE_IMMEDIATELY
363
- &pycall_pyptr_data_type, 0, RUBY_TYPED_FREE_IMMEDIATELY
363
+ # if defined _WIN32 && !defined __CYGWIN__
364
+ 0,
365
+ # else
366
+ &pycall_pyptr_data_type,
367
+ # endif
368
+ 0, RUBY_TYPED_FREE_IMMEDIATELY
364
369
  #endif
365
370
  };
366
371
 
@@ -462,6 +467,10 @@ pycall_init_ruby_wrapper(void)
462
467
 
463
468
  /* PyCall::PyRubyPtr */
464
469
 
470
+ #if defined _WIN32 && !defined __CYGWIN__
471
+ pycall_pyrubyptr_data_type.parent = &pycall_pyptr_data_type;
472
+ #endif
473
+
465
474
  cPyRubyPtr = rb_define_class_under(mPyCall, "PyRubyPtr", cPyPtr);
466
475
  rb_define_alloc_func(cPyRubyPtr, pycall_pyruby_allocate);
467
476
  rb_define_method(cPyRubyPtr, "__ruby_object_id__", pycall_pyruby_get_ruby_object_id, 0);
@@ -0,0 +1,36 @@
1
+ #include "pycall_internal.h"
2
+
3
+ #if defined(PYCALL_THREAD_WIN32)
4
+ int pycall_tls_create(pycall_tls_key *tls_key)
5
+ {
6
+ *tls_key = TlsAlloc();
7
+ return *tls_key == TLS_OUT_OF_INDEXES;
8
+ }
9
+
10
+ void *pycall_tls_get(pycall_tls_key tls_key)
11
+ {
12
+ return TlsGetValue(tls_key);
13
+ }
14
+
15
+ int pycall_tls_set(pycall_tls_key tls_key, void *ptr)
16
+ {
17
+ return 0 == TlsSetValue(tls_key, ptr);
18
+ }
19
+ #endif
20
+
21
+ #if defined(PYCALL_THREAD_PTHREAD)
22
+ int pycall_tls_create(pycall_tls_key *tls_key)
23
+ {
24
+ return pthread_key_create(tls_key, NULL);
25
+ }
26
+
27
+ void *pycall_tls_get(pycall_tls_key tls_key)
28
+ {
29
+ return pthread_getspecific(tls_key);
30
+ }
31
+
32
+ int pycall_tls_set(pycall_tls_key tls_key, void *ptr)
33
+ {
34
+ return pthread_setspecific(tls_key, ptr);
35
+ }
36
+ #endif
data/lib/pycall.rb CHANGED
@@ -58,6 +58,17 @@ module PyCall
58
58
  LibPython::Helpers.hasattr?(obj.__pyptr__, name)
59
59
  end
60
60
 
61
+ def same?(left, right)
62
+ case left
63
+ when PyObjectWrapper
64
+ case right
65
+ when PyObjectWrapper
66
+ return left.__pyptr__ == right.__pyptr__
67
+ end
68
+ end
69
+ false
70
+ end
71
+
61
72
  def import_module(name)
62
73
  LibPython::Helpers.import_module(name)
63
74
  end
data/lib/pycall/dict.rb CHANGED
@@ -33,9 +33,9 @@ module PyCall
33
33
  v
34
34
  end
35
35
 
36
- def each
36
+ def each(&block)
37
37
  return enum_for unless block_given?
38
- LibPython::Helpers.dict_each(__pyptr__, &proc)
38
+ LibPython::Helpers.dict_each(__pyptr__, &block)
39
39
  self
40
40
  end
41
41
 
data/lib/pycall/init.rb CHANGED
@@ -30,20 +30,15 @@ module PyCall
30
30
  remove_method :const_missing
31
31
  end
32
32
 
33
- ENV['PYTHONPATH'] = [ File.expand_path('../python', __FILE__), ENV['PYTHONPATH'] ].compact.join(File::PATH_SEPARATOR)
34
-
35
33
  LibPython.instance_variable_set(:@handle, LibPython::Finder.find_libpython(python))
36
34
  class << LibPython
37
35
  undef_method :handle
38
36
  attr_reader :handle
39
37
  end
40
38
 
41
- begin
42
- major, minor, _ = RUBY_VERSION.split('.')
43
- require "#{major}.#{minor}/pycall.so"
44
- rescue LoadError
45
- require 'pycall.so'
46
- end
39
+ require 'pycall.so'
40
+
41
+ PyCall.sys.path.append(File.expand_path('../python', __FILE__))
47
42
 
48
43
  require 'pycall/dict'
49
44
  require 'pycall/list'
@@ -1,4 +1,4 @@
1
- require 'pycall'
1
+ require 'pycall' unless defined?(::PyCall)
2
2
  require 'iruby'
3
3
 
4
4
  module PyCall
@@ -1,5 +1,6 @@
1
1
  require 'pycall/error'
2
2
  require 'fiddle'
3
+ require 'pathname'
3
4
 
4
5
  module PyCall
5
6
  module LibPython
@@ -27,73 +28,114 @@ module PyCall
27
28
  def find_python_config(python = nil)
28
29
  python ||= DEFAULT_PYTHON
29
30
  Array(python).each do |python_cmd|
30
- python_config = investigate_python_config(python_cmd)
31
- return [python_cmd, python_config] unless python_config.empty?
31
+ begin
32
+ python_config = investigate_python_config(python_cmd)
33
+ return [python_cmd, python_config] unless python_config.empty?
34
+ rescue
35
+ end
32
36
  end
33
- rescue
34
- raise ::PyCall::PythonNotFound
35
- else
36
37
  raise ::PyCall::PythonNotFound
37
38
  end
38
39
 
39
40
  def find_libpython(python = nil)
40
41
  debug_report("find_libpython(#{python.inspect})")
41
42
  python, python_config = find_python_config(python)
42
-
43
- set_PYTHONHOME(python_config)
44
- libs = make_libs(python_config)
45
- libpaths = make_libpaths(python_config)
46
-
47
- # Try LIBPYTHON environment variable first.
48
- if (libpython = ENV['LIBPYTHON'])
49
- if File.file?(libpython)
43
+ suffix = python_config[:SHLIB_SUFFIX]
44
+
45
+ use_conda = (ENV.fetch("CONDA_PREFIX", nil) == File.dirname(python_config[:executable]))
46
+ python_home = if !ENV.key?("PYTHONHOME") || use_conda
47
+ python_config[:PYTHONHOME]
48
+ else
49
+ ENV["PYTHONHOME"]
50
+ end
51
+ ENV["PYTHONHOME"] = python_home
52
+
53
+ candidate_paths(python_config) do |path|
54
+ debug_report("Candidate: #{path}")
55
+ normalized = normalize_path(path, suffix)
56
+ if normalized
57
+ debug_report("Trying to dlopen: #{normalized}")
50
58
  begin
51
- return dlopen(libpython)
59
+ return dlopen(normalized)
52
60
  rescue Fiddle::DLError
53
- debug_report "#{$!.class}: #{$!.message}"
54
- else
55
- debug_report "Success to dlopen #{libpython.inspect} from ENV['LIBPYTHON']"
61
+ debug_report "dlopen(#{normalized.inspect}) => #{$!.class}: #{$!.message}"
56
62
  end
63
+ else
64
+ debug_report("Not found.")
57
65
  end
58
- warn "WARNING(#{self}.#{__method__}) Ignore the wrong libpython location specified in ENV['LIBPYTHON']."
59
66
  end
67
+ end
60
68
 
61
- # Find libpython (we hope):
62
- multiarch = python_config[:MULTIARCH] || python_config[:multiarch]
63
- libs.each do |lib|
64
- libpaths.each do |libpath|
65
- libpath_libs = [ File.join(libpath, lib) ]
66
- libpath_libs << File.join(libpath, multiarch, lib) if multiarch
67
- libpath_libs.each do |libpath_lib|
68
- [ libpath_lib, "#{libpath_lib}.#{LIBSUFFIX}" ].each do |fullname|
69
- unless File.file? fullname
70
- debug_report "Unable to find #{fullname}"
71
- next
72
- end
73
- begin
74
- return dlopen(libpath_lib)
75
- rescue Fiddle::DLError
76
- debug_report "#{$!.class}: #{$!.message}"
77
- else
78
- debug_report "Success to dlopen #{libpaht_lib}"
79
- end
80
- end
81
- end
82
- end
69
+ def candidate_names(python_config)
70
+ names = []
71
+ names << python_config[:LDLIBRARY] if python_config[:LDLIBRARY]
72
+ suffix = python_config[:SHLIB_SUFFIX]
73
+ if python_config[:LIBRARY]
74
+ ext = File.extname(python_config[:LIBRARY])
75
+ names << python_config[:LIBRARY].delete_suffix(ext) + suffix
76
+ end
77
+ dlprefix = if windows? then "" else "lib" end
78
+ sysdata = {
79
+ v_major: python_config[:version_major],
80
+ VERSION: python_config[:VERSION],
81
+ ABIFLAGS: python_config[:ABIFLAGS],
82
+ }
83
+ [
84
+ "python%{VERSION}%{ABIFLAGS}" % sysdata,
85
+ "python%{VERSION}" % sysdata,
86
+ "python%{v_major}" % sysdata,
87
+ "python"
88
+ ].each do |stem|
89
+ names << "#{dlprefix}#{stem}#{suffix}"
83
90
  end
84
91
 
85
- # Find libpython in the system path
86
- libs.each do |lib|
87
- begin
88
- return dlopen(lib)
89
- rescue Fiddle::DLError
90
- debug_report "#{$!.class}: #{$!.message}"
91
- else
92
- debug_report "Success to dlopen #{lib}"
92
+ names.compact!
93
+ names.uniq!
94
+
95
+ debug_report("candidate_names: #{names}")
96
+ return names
97
+ end
98
+
99
+ def candidate_paths(python_config)
100
+ # The candidate library that linked by executable
101
+ yield python_config[:linked_libpython]
102
+
103
+ lib_dirs = make_libpaths(python_config)
104
+ lib_basenames = candidate_names(python_config)
105
+
106
+ # candidates by absolute paths
107
+ lib_dirs.each do |dir|
108
+ lib_basenames.each do |name|
109
+ yield File.join(dir, name)
93
110
  end
94
111
  end
95
112
 
96
- raise ::PyCall::PythonNotFound
113
+ # library names for searching in system library paths
114
+ lib_basenames.each do |name|
115
+ yield name
116
+ end
117
+ end
118
+
119
+ def normalize_path(path, suffix, apple_p=apple?)
120
+ return nil if path.nil?
121
+ case
122
+ when path.nil?,
123
+ Pathname.new(path).relative?
124
+ nil
125
+ when File.exist?(path)
126
+ File.realpath(path)
127
+ when File.exist?(path + suffix)
128
+ File.realpath(path + suffix)
129
+ when apple_p
130
+ normalize_path(remove_suffix_apple(path), ".so", false)
131
+ else
132
+ nil
133
+ end
134
+ end
135
+
136
+ # Strip off .so or .dylib
137
+ def remove_suffix_apple(path)
138
+ path.sub(/\.(?:dylib|so)\z/, '')
97
139
  end
98
140
 
99
141
  def investigate_python_config(python)
@@ -120,47 +162,25 @@ module PyCall
120
162
  File.expand_path('../../python/investigator.py', __FILE__)
121
163
  end
122
164
 
123
- def set_PYTHONHOME(python_config)
124
- if !ENV.has_key?('PYTHONHOME') && python_config[:conda]
125
- case RUBY_PLATFORM
126
- when /mingw32/, /cygwin/, /mswin/
127
- ENV['PYTHONHOME'] = python_config[:exec_prefix]
128
- else
129
- ENV['PYTHONHOME'] = python_config.values_at(:prefix, :exec_prefix).join(':')
130
- end
131
- end
132
- end
165
+ def make_libpaths(python_config)
166
+ libpaths = python_config.values_at(:LIBPL, :srcdir, :LIBDIR)
133
167
 
134
- def make_libs(python_config)
135
- libs = []
136
- %i(INSTSONAME LDLIBRARY).each do |key|
137
- lib = python_config[key]
138
- libs << lib << File.basename(lib) if lib
139
- end
140
- if (lib = python_config[:LIBRARY])
141
- libs << File.basename(lib, File.extname(lib))
168
+ if windows?
169
+ libpaths << File.dirname(python_config[:executable])
170
+ else
171
+ libpaths << File.expand_path('../../lib', python_config[:executable])
142
172
  end
143
173
 
144
- v = python_config[:VERSION]
145
- libs << "#{LIBPREFIX}python#{v}" << "#{LIBPREFIX}python"
146
- libs.uniq!
147
-
148
- debug_report "libs: #{libs.inspect}"
149
- return libs
150
- end
151
-
152
- def make_libpaths(python_config)
153
- executable = python_config[:executable]
154
- libpaths = [ python_config[:LIBDIR] ]
155
- if Fiddle::WINDOWS
156
- libpaths << File.dirname(executable)
157
- else
158
- libpaths << File.expand_path('../../lib', executable)
174
+ if apple?
175
+ libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
159
176
  end
160
- libpaths << python_config[:PYTHONFRAMEWORKPREFIX]
177
+
161
178
  exec_prefix = python_config[:exec_prefix]
162
- libpaths << exec_prefix << File.join(exec_prefix, 'lib')
179
+ libpaths << exec_prefix
180
+ libpaths << File.join(exec_prefix, 'lib')
181
+
163
182
  libpaths.compact!
183
+ libpaths.uniq!
164
184
 
165
185
  debug_report "libpaths: #{libpaths.inspect}"
166
186
  return libpaths
@@ -168,6 +188,14 @@ module PyCall
168
188
 
169
189
  private
170
190
 
191
+ def windows?
192
+ Fiddle::WINDOWS
193
+ end
194
+
195
+ def apple?
196
+ RUBY_PLATFORM.include?("darwin")
197
+ end
198
+
171
199
  def dlopen(libname)
172
200
  Fiddle.dlopen(libname).tap do |handle|
173
201
  debug_report("dlopen(#{libname.inspect}) = #{handle.inspect}") if handle
@@ -186,3 +214,22 @@ module PyCall
186
214
  end
187
215
  end
188
216
  end
217
+
218
+ if __FILE__ == $0
219
+ require "pp"
220
+ python, python_config = PyCall::LibPython::Finder.find_python_config
221
+
222
+ puts "python_config:"
223
+ pp python_config
224
+
225
+ puts "\ncandidate_names:"
226
+ p PyCall::LibPython::Finder.candidate_names(python_config)
227
+
228
+ puts "\nlib_dirs:"
229
+ p PyCall::LibPython::Finder.make_libpaths(python_config)
230
+
231
+ puts "\ncandidate_paths:"
232
+ PyCall::LibPython::Finder.candidate_paths(python_config) do |path|
233
+ puts "- #{path}"
234
+ end
235
+ end
data/lib/pycall/list.rb CHANGED
@@ -13,9 +13,9 @@ module PyCall
13
13
  PyCall.len(self)
14
14
  end
15
15
 
16
- def each
16
+ def each(&block)
17
17
  return enum_for unless block_given?
18
- LibPython::Helpers.sequence_each(__pyptr__, &proc)
18
+ LibPython::Helpers.sequence_each(__pyptr__, &block)
19
19
  self
20
20
  end
21
21
 
@@ -178,7 +178,7 @@ module PyCall
178
178
  def check_isclass(pyptr)
179
179
  pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper
180
180
  return if pyptr.kind_of? LibPython::API::PyType_Type
181
- return defined?(LibPython::API::PyClass_Type) && pyptr.kind_of?(LibPython::API::PyClass_Type)
181
+ return if defined?(LibPython::API::PyClass_Type) && pyptr.kind_of?(LibPython::API::PyClass_Type)
182
182
  raise TypeError, "PyType object is required"
183
183
  end
184
184
  end
@@ -1,12 +1,85 @@
1
- from distutils.sysconfig import get_config_var
1
+ #!/usr/bin/env python
2
+
3
+ import ctypes.util
4
+ from distutils.sysconfig import get_config_var, get_python_version
5
+ import os
2
6
  import sys
3
7
 
4
- def conda():
5
- return 'conda' in sys.version or 'Continuum' in sys.version
8
+ is_windows = os.name == "nt"
9
+
10
+ def linked_libpython():
11
+ if is_windows:
12
+ return _linked_libpython_windows()
13
+ return _linked_libpython_unix()
14
+
15
+ class Dl_info(ctypes.Structure):
16
+ _fields_ = [
17
+ ("dli_fname", ctypes.c_char_p),
18
+ ("dli_fbase", ctypes.c_void_p),
19
+ ("dli_sname", ctypes.c_char_p),
20
+ ("dli_saddr", ctypes.c_void_p),
21
+ ]
22
+
23
+ def _linked_libpython_unix():
24
+ libdl = ctypes.CDLL(ctypes.util.find_library("dl"))
25
+ libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)]
26
+ libdl.dladdr.restype = ctypes.c_int
27
+
28
+ dlinfo = Dl_info()
29
+ retcode = libdl.dladdr(
30
+ ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p),
31
+ ctypes.pointer(dlinfo))
32
+ if retcode == 0: # means error
33
+ return None
34
+ path = os.path.realpath(dlinfo.dli_fname.decode())
35
+ if path == os.path.realpath(sys.executable):
36
+ return None
37
+ return path
38
+
39
+ def _linked_libpython_windows():
40
+ # Based on: https://stackoverflow.com/a/16659821
41
+ from ctypes.wintypes import HANDLE, LPWSTR, DWORD
42
+
43
+ GetModuleFileName = ctypes.windll.kernel32.GetModuleFileNameW
44
+ GetModuleFileName.argtypes = [HANDLE, LPWSTR, DWORD]
45
+ GetModuleFileName.restype = DWORD
46
+
47
+ MAX_PATH = 260
48
+ try:
49
+ buf = ctypes.create_unicode_buffer(MAX_PATH)
50
+ GetModuleFileName(ctypes.pythonapi._handle, buf, MAX_PATH)
51
+ return buf.value
52
+ except (ValueError, OSError):
53
+ return None
54
+
55
+ print("linked_libpython: {val}".format(val=(linked_libpython() or "None")))
56
+
57
+ sys_keys = [ "executable", "exec_prefix", "prefix" ]
58
+
59
+ for var in sys_keys:
60
+ print("{var}: {val}".format(var=var, val=(getattr(sys, var) or "None")))
61
+
62
+ config_keys = [ "INSTSONAME", "LIBDIR", "LIBPL", "LIBRARY", "LDLIBRARY",
63
+ "MULTIARCH", "PYTHONFRAMEWORKPREFIX", "SHLIB_SUFFIX", "srcdir" ]
64
+
65
+ for var in config_keys:
66
+ print("{var}: {val}".format(var=var, val=(get_config_var(var) or "None")))
67
+
68
+ print("ABIFLAGS: {val}".format(val=get_config_var("ABIFLAGS") or get_config_var("abiflags") or "None"))
69
+
70
+ version = get_python_version() or \
71
+ "{v.major}.{v.minor}".format(v=sys.version_info) or \
72
+ get_config_var("VERSION")
73
+ print("VERSION: {val}".format(val=version))
6
74
 
7
- for var in ('executable', 'exec_prefix', 'prefix'):
8
- print(var + ': ' + str(getattr(sys, var)))
9
- print('conda: ' + ('true' if conda() else 'false'))
10
- print('multiarch: ' + str(getattr(getattr(sys, 'implementation', sys), '_multiarch', None)))
11
- for var in ('VERSION', 'INSTSONAME', 'LIBRARY', 'LDLIBRARY', 'LIBDIR', 'PYTHONFRAMEWORKPREFIX', 'MULTIARCH'):
12
- print(var + ': ' + str(get_config_var(var)))
75
+ if is_windows:
76
+ if hasattr(sys, "base_exec_prefix"):
77
+ PYTHONHOME = sys.base_exec_prefix
78
+ else:
79
+ PYTHONHOME = sys.exec_prefix
80
+ else:
81
+ if hasattr(sys, "base_exec_prefix"):
82
+ PYTHONHOME = ":".join([sys.base_prefix, sys.base_exec_prefix])
83
+ else:
84
+ PYTHONHOME = ":".join([sys.prefix, sys.exec_prefix])
85
+ print("PYTHONHOME: {val}".format(val=PYTHONHOME))