11require 'pycall/error'
22require 'fiddle'
3+ require 'pathname'
34
45module PyCall
56 module LibPython
@@ -39,60 +40,94 @@ def find_python_config(python = nil)
3940 def find_libpython ( python = nil )
4041 debug_report ( "find_libpython(#{ python . inspect } )" )
4142 python , python_config = find_python_config ( python )
43+ suffix = python_config [ :SHLIB_SUFFIX ]
4244
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 } " )
4650 begin
47- return dlopen ( libpython )
51+ return dlopen ( normalized )
4852 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 } "
5254 end
55+ else
56+ debug_report ( "Not found." )
5357 end
54- warn "WARNING(#{ self } .#{ __method__ } ) Ignore the wrong libpython location specified in ENV['LIBPYTHON']."
5558 end
59+ end
5660
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 } "
8282 end
8383
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 )
92102 end
93103 end
94104
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 / , '' )
96131 end
97132
98133 def investigate_python_config ( python )
@@ -119,54 +154,40 @@ def python_investigator_py
119154 File . expand_path ( '../../python/investigator.py' , __FILE__ )
120155 end
121156
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 )
132159
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 ] )
141164 end
142165
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 ]
158168 end
159- libpaths << python_config [ :PYTHONFRAMEWORKPREFIX ]
169+
160170 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+
162174 libpaths . compact!
175+ libpaths . uniq!
163176
164177 debug_report "libpaths: #{ libpaths . inspect } "
165178 return libpaths
166179 end
167180
168181 private
169182
183+ def windows?
184+ Fiddle ::WINDOWS
185+ end
186+
187+ def apple?
188+ RUBY_PLATFORM . include? ( "darwin" )
189+ end
190+
170191 def dlopen ( libname )
171192 Fiddle . dlopen ( libname ) . tap do |handle |
172193 debug_report ( "dlopen(#{ libname . inspect } ) = #{ handle . inspect } " ) if handle
@@ -185,3 +206,22 @@ def debug?
185206 end
186207 end
187208end
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