1313# limitations under the License.
1414"""Returns information about the local Python runtime as JSON."""
1515
16+ import glob
1617import json
1718import os
1819import sys
1920import sysconfig
21+ from typing import Any
2022
2123_IS_WINDOWS = sys .platform == "win32"
2224_IS_DARWIN = sys .platform == "darwin"
2325
2426
25- def _search_directories (get_config , base_executable ):
27+ def _get_abi_flags (get_config ) -> str :
28+ """Returns the ABI flags for the Python runtime."""
29+ # sys.abiflags may not exist, but it still may be set in the config.
30+ abi_flags = getattr (sys , "abiflags" , None )
31+ if abi_flags is None :
32+ abi_flags = get_config ("ABIFLAGS" ) or get_config ("abiflags" ) or ""
33+ return abi_flags
34+
35+
36+ def _search_directories (get_config , base_executable ) -> list [str ]:
2637 """Returns a list of library directories to search for shared libraries."""
2738 # There's several types of libraries with different names and a plethora
2839 # of settings, and many different config variables to check:
@@ -73,23 +84,31 @@ def _search_directories(get_config, base_executable):
7384 lib_dirs .append (os .path .join (os .path .dirname (exec_dir ), "lib" ))
7485
7586 # Dedup and remove empty values, keeping the order.
76- lib_dirs = [v for v in lib_dirs if v ]
77- return {k : None for k in lib_dirs }.keys ()
87+ return list (dict .fromkeys (d for d in lib_dirs if d ))
7888
7989
80- def _get_shlib_suffix (get_config ) -> str :
81- """Returns the suffix for shared libraries."""
82- if _IS_DARWIN :
83- return ".dylib"
90+ def _default_library_names (version , abi_flags ) -> tuple [str , ...]:
91+ """Returns a list of default library files to search for shared libraries."""
8492 if _IS_WINDOWS :
85- return ".dll"
86- suffix = get_config ("SHLIB_SUFFIX" )
87- if not suffix :
88- suffix = ".so"
89- return suffix
93+ return (
94+ f"python{ version } { abi_flags } .dll" ,
95+ f"python{ version } .dll" ,
96+ )
97+ elif _IS_DARWIN :
98+ return (
99+ f"libpython{ version } { abi_flags } .dylib" ,
100+ f"libpython{ version } .dylib" ,
101+ )
102+ else :
103+ return (
104+ f"libpython{ version } { abi_flags } .so" ,
105+ f"libpython{ version } .so" ,
106+ f"libpython{ version } { abi_flags } .so.1.0" ,
107+ f"libpython{ version } .so.1.0" ,
108+ )
90109
91110
92- def _search_library_names (get_config , shlib_suffix ) :
111+ def _search_library_names (get_config , version , abi_flags ) -> list [ str ] :
93112 """Returns a list of library files to search for shared libraries."""
94113 # Quoting configure.ac in the cpython code base:
95114 # "INSTSONAME is the name of the shared library that will be use to install
@@ -112,71 +131,75 @@ def _search_library_names(get_config, shlib_suffix):
112131 )
113132 ]
114133
115- # Set the prefix and suffix to construct the library name used for linking.
116- # The suffix and version are set here to the default values for the OS,
117- # since they are used below to construct "default" library names.
118- if _IS_DARWIN :
119- prefix = "lib"
120- elif _IS_WINDOWS :
121- prefix = ""
122- else :
123- prefix = "lib"
124-
125- version = get_config ("VERSION" )
126-
127- # Ensure that the pythonXY.dll files are included in the search.
128- lib_names .append (f"{ prefix } python{ version } { shlib_suffix } " )
134+ # Include the default libraries for the system.
135+ lib_names .extend (_default_library_names (version , abi_flags ))
129136
130- # If there are ABIFLAGS, also add them to the python version lib search.
131- abiflags = get_config ("ABIFLAGS" ) or get_config ("abiflags" ) or ""
132- if abiflags :
133- lib_names .append (f"{ prefix } python{ version } { abiflags } { shlib_suffix } " )
137+ # Also include the abi3 libraries for the system.
138+ lib_names .extend (_default_library_names (sys .version_info .major , abi_flags ))
134139
135- # Add the abi-version includes to the search list.
136- lib_names .append (f"{ prefix } python{ sys .version_info .major } { shlib_suffix } " )
137-
138- # Dedup and remove empty values, keeping the order.
139- lib_names = [v for v in lib_names if v ]
140- return {k : None for k in lib_names }.keys ()
140+ # Uniqify, preserving order.
141+ return list (dict .fromkeys (k for k in lib_names if k ))
141142
142143
143- def _get_python_library_info (base_executable ):
144+ def _get_python_library_info (base_executable ) -> dict [ str , Any ] :
144145 """Returns a dictionary with the static and dynamic python libraries."""
145146 config_vars = sysconfig .get_config_vars ()
146147
147148 # VERSION is X.Y in Linux/macOS and XY in Windows. This is used to
148149 # construct library paths such as python3.12, so ensure it exists.
149- if not config_vars .get ("VERSION" ):
150- if sys . platform == "win32" :
151- config_vars [ "VERSION" ] = (
152- f"{ sys .version_info .major } { sys .version_info .minor } " )
150+ version = config_vars .get ("VERSION" )
151+ if not version :
152+ if _IS_WINDOWS :
153+ version = f"{ sys .version_info .major } { sys .version_info .minor } "
153154 else :
154- config_vars ["VERSION" ] = (
155- f"{ sys .version_info .major } .{ sys .version_info .minor } " )
155+ version = f"{ sys .version_info .major } .{ sys .version_info .minor } "
156+
157+ defines = []
158+ if config_vars .get ("Py_GIL_DISABLED" , "0" ) == "1" :
159+ defines .append ("Py_GIL_DISABLED" )
160+
161+ # Avoid automatically linking the libraries on windows via pydefine.h
162+ # pragma comment(lib ...)
163+ if _IS_WINDOWS :
164+ defines .append ("Py_NO_LINK_LIB" )
165+
166+ # sys.abiflags may not exist, but it still may be set in the config.
167+ abi_flags = _get_abi_flags (config_vars .get )
156168
157- shlib_suffix = _get_shlib_suffix (config_vars .get )
158169 search_directories = _search_directories (config_vars .get , base_executable )
159- search_libnames = _search_library_names (config_vars .get , shlib_suffix )
170+ search_libnames = _search_library_names (config_vars .get , version ,
171+ abi_flags )
172+
173+ # Used to test whether the library is an abi3 library or a full api library.
174+ abi3_libraries = _default_library_names (sys .version_info .major , abi_flags )
160175
161- interface_libraries = {}
162- dynamic_libraries = {}
163- static_libraries = {}
176+ # Found libraries
177+ static_libraries : dict [str , None ] = {}
178+ dynamic_libraries : dict [str , None ] = {}
179+ interface_libraries : dict [str , None ] = {}
180+ abi_dynamic_libraries : dict [str , None ] = {}
181+ abi_interface_libraries : dict [str , None ] = {}
164182
165183 for root_dir in search_directories :
166184 for libname in search_libnames :
167- # Check whether the library exists.
168185 composed_path = os .path .join (root_dir , libname )
186+ is_abi3_file = os .path .basename (composed_path ) in abi3_libraries
187+
188+ # Check whether the library exists and add it to the appropriate list.
169189 if os .path .exists (composed_path ) or os .path .isdir (composed_path ):
170- if libname .endswith (".a" ):
190+ if is_abi3_file :
191+ if not libname .endswith (".a" ):
192+ abi_dynamic_libraries [composed_path ] = None
193+ elif libname .endswith (".a" ):
171194 static_libraries [composed_path ] = None
172195 else :
173196 dynamic_libraries [composed_path ] = None
174197
175198 interface_path = None
176199 if libname .endswith (".dll" ):
177- # On windows a .lib file may be an "import library" or a static library.
178- # The file could be inspected to determine which it is; typically python
179- # is used as a shared library.
200+ # On windows a .lib file may be an "import library" or a static
201+ # library. The file could be inspected to determine which it is;
202+ # typically python is used as a shared library.
180203 #
181204 # On Windows, extensions should link with the pythonXY.lib interface
182205 # libraries.
@@ -190,39 +213,51 @@ def _get_python_library_info(base_executable):
190213
191214 # Check whether an interface library exists.
192215 if interface_path and os .path .exists (interface_path ):
193- interface_libraries [interface_path ] = None
216+ if is_abi3_file :
217+ abi_interface_libraries [interface_path ] = None
218+ else :
219+ interface_libraries [interface_path ] = None
194220
195- # Non-windows typically has abiflags.
196- if hasattr (sys , "abiflags" ):
197- abiflags = sys .abiflags
198- else :
199- abiflags = ""
221+ # Additional DLLs are needed on Windows to link properly.
222+ dlls = []
223+ if _IS_WINDOWS :
224+ dlls .extend (
225+ glob .glob (os .path .join (os .path .dirname (base_executable ), "*.dll" )))
226+ dlls = [
227+ x for x in dlls
228+ if x not in dynamic_libraries and x not in abi_dynamic_libraries
229+ ]
230+
231+ def _unique_basenames (inputs : dict [str , None ]) -> list [str ]:
232+ """Returns a list of paths, keeping only the first path for each basename."""
233+ result = []
234+ seen = set ()
235+ for k in inputs :
236+ b = os .path .basename (k )
237+ if b not in seen :
238+ seen .add (b )
239+ result .append (k )
240+ return result
200241
201242 # When no libraries are found it's likely that the python interpreter is not
202243 # configured to use shared or static libraries (minilinux). If this seems
203244 # suspicious try running `uv tool run find_libpython --list-all -v`
204245 return {
205- "dynamic_libraries" : list (dynamic_libraries .keys ()),
206- "static_libraries" : list (static_libraries .keys ()),
207- "interface_libraries" : list (interface_libraries .keys ()),
208- "shlib_suffix" : "" if _IS_WINDOWS else shlib_suffix ,
209- "abi_flags" : abiflags ,
246+ "dynamic_libraries" : _unique_basenames (dynamic_libraries ),
247+ "static_libraries" : _unique_basenames (static_libraries ),
248+ "interface_libraries" : _unique_basenames (interface_libraries ),
249+ "abi_dynamic_libraries" : _unique_basenames (abi_dynamic_libraries ),
250+ "abi_interface_libraries" : _unique_basenames (abi_interface_libraries ),
251+ "abi_flags" : abi_flags ,
252+ "shlib_suffix" : ".dylib" if _IS_DARWIN else "" ,
253+ "additional_dlls" : dlls ,
254+ "defines" : defines ,
210255 }
211256
212257
213- def _get_base_executable ():
258+ def _get_base_executable () -> str :
214259 """Returns the base executable path."""
215- try :
216- if sys ._base_executable : # pylint: disable=protected-access
217- return sys ._base_executable # pylint: disable=protected-access
218- except AttributeError :
219- # Bug reports indicate sys._base_executable doesn't exist in some cases,
220- # but it's not clear why.
221- # See https://github.com/bazel-contrib/rules_python/issues/3172
222- pass
223- # The normal sys.executable is the next-best guess if sys._base_executable
224- # is missing.
225- return sys .executable
260+ return getattr (sys , "_base_executable" , None ) or sys .executable
226261
227262
228263data = {
0 commit comments