pycall 1.2.1 → 1.4.1

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.
@@ -11,10 +11,19 @@ extern "C" {
11
11
  #include <ruby.h>
12
12
  #include <ruby/encoding.h>
13
13
  #include <ruby/thread.h>
14
+
14
15
  #include <assert.h>
15
16
  #include <inttypes.h>
16
17
  #include <limits.h>
17
18
 
19
+ #if defined(_WIN32)
20
+ # define PYCALL_THREAD_WIN32
21
+ # include <ruby/win32.h>
22
+ #elif defined(HAVE_PTHREAD_H)
23
+ # define PYCALL_THREAD_PTHREAD
24
+ # include <pthread.h>
25
+ #endif
26
+
18
27
  #if SIZEOF_LONG == SIZEOF_VOIDP
19
28
  # define PTR2NUM(x) (LONG2NUM((long)(x)))
20
29
  # define NUM2PTR(x) ((void*)(NUM2ULONG(x)))
@@ -492,6 +501,23 @@ extern PyTypeObject PyRuby_Type;
492
501
 
493
502
  PyObject * PyRuby_New(VALUE ruby_object);
494
503
 
504
+ /* ==== thread support ==== */
505
+
506
+ #if defined(PYCALL_THREAD_WIN32)
507
+ typedef DWORD pycall_tls_key;
508
+ #elif defined(PYCALL_THREAD_PTHREAD)
509
+ typedef pthread_key_t pycall_tls_key;
510
+ #else
511
+ # error "unsupported thread type"
512
+ #endif
513
+
514
+ int pycall_tls_create(pycall_tls_key* tls_key);
515
+ void *pycall_tls_get(pycall_tls_key tls_key);
516
+ int pycall_tls_set(pycall_tls_key tls_key, void *ptr);
517
+
518
+ int pycall_without_gvl_p(void);
519
+ VALUE pycall_without_gvl(VALUE (* func)(VALUE), VALUE arg);
520
+
495
521
  /* ==== pycall ==== */
496
522
 
497
523
  typedef struct {
@@ -637,7 +663,7 @@ Py_ssize_t pycall_python_hexversion(void);
637
663
 
638
664
  void pycall_Py_DecRef(PyObject *);
639
665
 
640
- RUBY_EXTERN const rb_data_type_t pycall_pyptr_data_type;
666
+ extern const rb_data_type_t pycall_pyptr_data_type;
641
667
  size_t pycall_pyptr_memsize(void const *);
642
668
  void pycall_pyptr_free(void *);
643
669
 
@@ -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
@@ -5,6 +5,7 @@ module PyCall
5
5
  require 'pycall/pyobject_wrapper'
6
6
  require 'pycall/pytypeobject_wrapper'
7
7
  require 'pycall/pymodule_wrapper'
8
+ require 'pycall/iterable_wrapper'
8
9
  require 'pycall/init'
9
10
 
10
11
  module_function
@@ -58,10 +59,25 @@ module PyCall
58
59
  LibPython::Helpers.hasattr?(obj.__pyptr__, name)
59
60
  end
60
61
 
62
+ def same?(left, right)
63
+ case left
64
+ when PyObjectWrapper
65
+ case right
66
+ when PyObjectWrapper
67
+ return left.__pyptr__ == right.__pyptr__
68
+ end
69
+ end
70
+ false
71
+ end
72
+
61
73
  def import_module(name)
62
74
  LibPython::Helpers.import_module(name)
63
75
  end
64
76
 
77
+ def iterable(obj)
78
+ IterableWrapper.new(obj)
79
+ end
80
+
65
81
  def len(obj)
66
82
  case obj
67
83
  when PyObjectWrapper
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'
@@ -0,0 +1,32 @@
1
+ module PyCall
2
+ class IterableWrapper
3
+ include Enumerable
4
+
5
+ def initialize(obj)
6
+ @obj = check_iterable(obj)
7
+ end
8
+
9
+ private def check_iterable(obj)
10
+ unless PyCall.hasattr?(obj, :__iter__)
11
+ raise ArgumentError, "%p object is not iterable" % obj
12
+ end
13
+ obj
14
+ end
15
+
16
+ def each
17
+ return enum_for(__method__) unless block_given?
18
+ iter = @obj.__iter__()
19
+ while true
20
+ begin
21
+ yield iter.__next__()
22
+ rescue PyCall::PyError => err
23
+ if err.type == PyCall.builtins.StopIteration
24
+ break
25
+ else
26
+ raise err
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -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