1
1
require 'pycall/error'
2
2
require 'fiddle'
3
+ require 'pathname'
3
4
4
5
module PyCall
5
6
module LibPython
@@ -39,60 +40,94 @@ def find_python_config(python = nil)
39
40
def find_libpython ( python = nil )
40
41
debug_report ( "find_libpython(#{ python . inspect } )" )
41
42
python , python_config = find_python_config ( python )
43
+ suffix = python_config [ :SHLIB_SUFFIX ]
42
44
43
- # Try LIBPYTHON environment variable first.
44
- if ( libpython = ENV [ 'LIBPYTHON' ] )
45
- if File . file? ( libpython )
45
+ candidate_paths ( python_config ) do |path |
46
+ debug_report ( "Candidate: #{ path } " )
47
+ normalized = normalize_path ( path , suffix )
48
+ if normalized
49
+ debug_report ( "Trying to dlopen: #{ normalized } " )
46
50
begin
47
- return dlopen ( libpython )
51
+ return dlopen ( normalized )
48
52
rescue Fiddle ::DLError
49
- debug_report "#{ $!. class } : #{ $!. message } "
50
- else
51
- debug_report "Success to dlopen #{ libpython . inspect } from ENV['LIBPYTHON']"
53
+ debug_report "dlopen(#{ normalized . inspect } ) => #{ $!. class } : #{ $!. message } "
52
54
end
55
+ else
56
+ debug_report ( "Not found." )
53
57
end
54
- warn "WARNING(#{ self } .#{ __method__ } ) Ignore the wrong libpython location specified in ENV['LIBPYTHON']."
55
58
end
59
+ end
56
60
57
- # Find libpython (we hope):
58
- set_PYTHONHOME ( python_config )
59
- libs = make_libs ( python_config )
60
- libpaths = make_libpaths ( python_config )
61
- multiarch = python_config [ :MULTIARCH ] || python_config [ :multiarch ]
62
- libs . each do |lib |
63
- libpaths . each do |libpath |
64
- libpath_libs = [ File . join ( libpath , lib ) ]
65
- libpath_libs << File . join ( libpath , multiarch , lib ) if multiarch
66
- libpath_libs . each do |libpath_lib |
67
- [ libpath_lib , "#{ libpath_lib } .#{ LIBSUFFIX } " ] . each do |fullname |
68
- unless File . file? fullname
69
- debug_report "Unable to find #{ fullname } "
70
- next
71
- end
72
- begin
73
- return dlopen ( libpath_lib )
74
- rescue Fiddle ::DLError
75
- debug_report "#{ $!. class } : #{ $!. message } "
76
- else
77
- debug_report "Success to dlopen #{ libpaht_lib } "
78
- end
79
- end
80
- end
81
- end
61
+ def candidate_names ( python_config )
62
+ names = [ ]
63
+ names << python_config [ :LDLIBRARY ] if python_config [ :LDLIBRARY ]
64
+ suffix = python_config [ :SHLIB_SUFFIX ]
65
+ if python_config [ :LIBRARY ]
66
+ ext = File . extname ( python_config [ :LIBRARY ] )
67
+ names << python_config [ :LIBRARY ] . delete_suffix ( ext ) + suffix
68
+ end
69
+ dlprefix = if windows? then "" else "lib" end
70
+ sysdata = {
71
+ v_major : python_config [ :version_major ] ,
72
+ VERSION : python_config [ :VERSION ] ,
73
+ ABIFLAGS : python_config [ :ABIFLAGS ] ,
74
+ }
75
+ [
76
+ "python%{VERSION}%{ABIFLAGS}" % sysdata ,
77
+ "python%{VERSION}" % sysdata ,
78
+ "python%{v_major}" % sysdata ,
79
+ "python"
80
+ ] . each do |stem |
81
+ names << "#{ dlprefix } #{ stem } #{ suffix } "
82
82
end
83
83
84
- # Find libpython in the system path
85
- libs . each do |lib |
86
- begin
87
- return dlopen ( lib )
88
- rescue Fiddle ::DLError
89
- debug_report "#{ $!. class } : #{ $!. message } "
90
- else
91
- debug_report "Success to dlopen #{ lib } "
84
+ names . compact!
85
+ names . uniq!
86
+
87
+ debug_report ( "candidate_names: #{ names } " )
88
+ return names
89
+ end
90
+
91
+ def candidate_paths ( python_config )
92
+ # The candidate library that linked by executable
93
+ yield python_config [ :linked_libpython ]
94
+
95
+ lib_dirs = make_libpaths ( python_config )
96
+ lib_basenames = candidate_names ( python_config )
97
+
98
+ # candidates by absolute paths
99
+ lib_dirs . each do |dir |
100
+ lib_basenames . each do |name |
101
+ yield File . join ( dir , name )
92
102
end
93
103
end
94
104
95
- raise ::PyCall ::PythonNotFound
105
+ # library names for searching in system library paths
106
+ lib_basenames . each do |name |
107
+ yield name
108
+ end
109
+ end
110
+
111
+ def normalize_path ( path , suffix , apple_p = apple? )
112
+ return nil if path . nil?
113
+ case
114
+ when path . nil? ,
115
+ Pathname . new ( path ) . relative?
116
+ nil
117
+ when File . exist? ( path )
118
+ File . realpath ( path )
119
+ when File . exist? ( path + suffix )
120
+ File . realpath ( path + suffix )
121
+ when apple_p
122
+ normalize_path ( remove_suffix_apple ( path ) , ".so" , false )
123
+ else
124
+ nil
125
+ end
126
+ end
127
+
128
+ # Strip off .so or .dylib
129
+ def remove_suffix_apple ( path )
130
+ path . sub ( /\. (?:dylib|so)\z / , '' )
96
131
end
97
132
98
133
def investigate_python_config ( python )
@@ -119,54 +154,40 @@ def python_investigator_py
119
154
File . expand_path ( '../../python/investigator.py' , __FILE__ )
120
155
end
121
156
122
- def set_PYTHONHOME ( python_config )
123
- if !ENV . has_key? ( 'PYTHONHOME' ) && python_config [ :conda ]
124
- case RUBY_PLATFORM
125
- when /mingw32/ , /cygwin/ , /mswin/
126
- ENV [ 'PYTHONHOME' ] = python_config [ :exec_prefix ]
127
- else
128
- ENV [ 'PYTHONHOME' ] = python_config . values_at ( :prefix , :exec_prefix ) . join ( ':' )
129
- end
130
- end
131
- end
157
+ def make_libpaths ( python_config )
158
+ libpaths = python_config . values_at ( :LIBPL , :srcdir , :LIBDIR )
132
159
133
- def make_libs ( python_config )
134
- libs = [ ]
135
- %i( INSTSONAME LDLIBRARY ) . each do |key |
136
- lib = python_config [ key ]
137
- libs << lib << File . basename ( lib ) if lib
138
- end
139
- if ( lib = python_config [ :LIBRARY ] )
140
- libs << File . basename ( lib , File . extname ( lib ) )
160
+ if windows?
161
+ libpaths << File . dirname ( python_config [ :executable ] )
162
+ else
163
+ libpaths << File . expand_path ( '../../lib' , python_config [ :executable ] )
141
164
end
142
165
143
- v = python_config [ :VERSION ]
144
- libs << "#{ LIBPREFIX } python#{ v } " << "#{ LIBPREFIX } python"
145
- libs . uniq!
146
-
147
- debug_report "libs: #{ libs . inspect } "
148
- return libs
149
- end
150
-
151
- def make_libpaths ( python_config )
152
- executable = python_config [ :executable ]
153
- libpaths = [ python_config [ :LIBDIR ] ]
154
- if Fiddle ::WINDOWS
155
- libpaths << File . dirname ( executable )
156
- else
157
- libpaths << File . expand_path ( '../../lib' , executable )
166
+ if apple?
167
+ libpaths << python_config [ :PYTHONFRAMEWORKPREFIX ]
158
168
end
159
- libpaths << python_config [ :PYTHONFRAMEWORKPREFIX ]
169
+
160
170
exec_prefix = python_config [ :exec_prefix ]
161
- libpaths << exec_prefix << File . join ( exec_prefix , 'lib' )
171
+ libpaths << exec_prefix
172
+ libpaths << File . join ( exec_prefix , 'lib' )
173
+
162
174
libpaths . compact!
175
+ libpaths . uniq!
163
176
164
177
debug_report "libpaths: #{ libpaths . inspect } "
165
178
return libpaths
166
179
end
167
180
168
181
private
169
182
183
+ def windows?
184
+ Fiddle ::WINDOWS
185
+ end
186
+
187
+ def apple?
188
+ RUBY_PLATFORM . include? ( "darwin" )
189
+ end
190
+
170
191
def dlopen ( libname )
171
192
Fiddle . dlopen ( libname ) . tap do |handle |
172
193
debug_report ( "dlopen(#{ libname . inspect } ) = #{ handle . inspect } " ) if handle
@@ -185,3 +206,22 @@ def debug?
185
206
end
186
207
end
187
208
end
209
+
210
+ if __FILE__ == $0
211
+ require "pp"
212
+ python , python_config = PyCall ::LibPython ::Finder . find_python_config
213
+
214
+ puts "python_config:"
215
+ pp python_config
216
+
217
+ puts "\n candidate_names:"
218
+ p PyCall ::LibPython ::Finder . candidate_names ( python_config )
219
+
220
+ puts "\n lib_dirs:"
221
+ p PyCall ::LibPython ::Finder . make_libpaths ( python_config )
222
+
223
+ puts "\n candidate_paths:"
224
+ PyCall ::LibPython ::Finder . candidate_paths ( python_config ) do |path |
225
+ puts "- #{ path } "
226
+ end
227
+ end
0 commit comments