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,74 @@ 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+ return list (dict .fromkeys (k for k in lib_names if k ))
141141
142142
143- def _get_python_library_info (base_executable ):
143+ def _get_python_library_info (base_executable ) -> dict [ str , Any ] :
144144 """Returns a dictionary with the static and dynamic python libraries."""
145145 config_vars = sysconfig .get_config_vars ()
146146
147147 # VERSION is X.Y in Linux/macOS and XY in Windows. This is used to
148148 # 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 } " )
149+ version = config_vars .get ("VERSION" )
150+ if not version :
151+ if _IS_WINDOWS :
152+ version = f"{ sys .version_info .major } { sys .version_info .minor } "
153153 else :
154- config_vars ["VERSION" ] = (
155- f"{ sys .version_info .major } .{ sys .version_info .minor } " )
154+ version = f"{ sys .version_info .major } .{ sys .version_info .minor } "
155+
156+ defines = []
157+ if config_vars .get ("Py_GIL_DISABLED" , "0" ) == "1" :
158+ defines .append ("Py_GIL_DISABLED" )
159+
160+ # Avoid automatically linking the libraries on windows via pydefine.h
161+ # pragma comment(lib ...)
162+ if _IS_WINDOWS :
163+ defines .append ("Py_NO_LINK_LIB" )
164+
165+ # sys.abiflags may not exist, but it still may be set in the config.
166+ abi_flags = _get_abi_flags (config_vars .get )
156167
157- shlib_suffix = _get_shlib_suffix (config_vars .get )
158168 search_directories = _search_directories (config_vars .get , base_executable )
159- search_libnames = _search_library_names (config_vars .get , shlib_suffix )
169+ search_libnames = _search_library_names (config_vars .get , version ,
170+ abi_flags )
171+
172+ # Used to test whether the library is an abi3 library or a full api library.
173+ abi3_libraries = _default_library_names (sys .version_info .major , abi_flags )
160174
161- interface_libraries = {}
162- dynamic_libraries = {}
163- static_libraries = {}
175+ # Found libraries
176+ static_libraries : dict [str , None ] = {}
177+ dynamic_libraries : dict [str , None ] = {}
178+ interface_libraries : dict [str , None ] = {}
179+ abi_dynamic_libraries : dict [str , None ] = {}
180+ abi_interface_libraries : dict [str , None ] = {}
164181
165182 for root_dir in search_directories :
166183 for libname in search_libnames :
167- # Check whether the library exists.
168184 composed_path = os .path .join (root_dir , libname )
185+ is_abi3_file = os .path .basename (composed_path ) in abi3_libraries
186+
187+ # Check whether the library exists and add it to the appropriate list.
169188 if os .path .exists (composed_path ) or os .path .isdir (composed_path ):
170- if libname .endswith (".a" ):
189+ if is_abi3_file :
190+ if not libname .endswith (".a" ):
191+ abi_dynamic_libraries [composed_path ] = None
192+ elif libname .endswith (".a" ):
171193 static_libraries [composed_path ] = None
172194 else :
173195 dynamic_libraries [composed_path ] = None
174196
175197 interface_path = None
176198 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.
199+ # On windows a .lib file may be an "import library" or a static
200+ # library. The file could be inspected to determine which it is;
201+ # typically python is used as a shared library.
180202 #
181203 # On Windows, extensions should link with the pythonXY.lib interface
182204 # libraries.
@@ -190,39 +212,51 @@ def _get_python_library_info(base_executable):
190212
191213 # Check whether an interface library exists.
192214 if interface_path and os .path .exists (interface_path ):
193- interface_libraries [interface_path ] = None
215+ if is_abi3_file :
216+ abi_interface_libraries [interface_path ] = None
217+ else :
218+ interface_libraries [interface_path ] = None
194219
195- # Non-windows typically has abiflags.
196- if hasattr (sys , "abiflags" ):
197- abiflags = sys .abiflags
198- else :
199- abiflags = ""
220+ # Additional DLLs are needed on Windows to link properly.
221+ dlls = []
222+ if _IS_WINDOWS :
223+ dlls .extend (
224+ glob .glob (os .path .join (os .path .dirname (base_executable ), "*.dll" )))
225+ dlls = [
226+ x for x in dlls
227+ if x not in dynamic_libraries and x not in abi_dynamic_libraries
228+ ]
229+
230+ def _unique_basenames (inputs : dict [str , None ]) -> list [str ]:
231+ """Returns a list of paths, keeping only the first path for each basename."""
232+ result = []
233+ seen = set ()
234+ for k in inputs :
235+ b = os .path .basename (k )
236+ if b not in seen :
237+ seen .add (b )
238+ result .append (k )
239+ return result
200240
201241 # When no libraries are found it's likely that the python interpreter is not
202242 # configured to use shared or static libraries (minilinux). If this seems
203243 # suspicious try running `uv tool run find_libpython --list-all -v`
204244 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 ,
245+ "dynamic_libraries" : _unique_basenames (dynamic_libraries ),
246+ "static_libraries" : _unique_basenames (static_libraries ),
247+ "interface_libraries" : _unique_basenames (interface_libraries ),
248+ "abi_dynamic_libraries" : _unique_basenames (abi_dynamic_libraries ),
249+ "abi_interface_libraries" : _unique_basenames (abi_interface_libraries ),
250+ "abi_flags" : abi_flags ,
251+ "shlib_suffix" : ".dylib" if _IS_DARWIN else "" ,
252+ "additional_dlls" : dlls ,
253+ "defines" : defines ,
210254 }
211255
212256
213- def _get_base_executable ():
257+ def _get_base_executable () -> str :
214258 """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
259+ return getattr (sys , "_base_executable" , None ) or sys .executable
226260
227261
228262data = {
0 commit comments