1010
1111from __future__ import annotations
1212
13- import glob
1413from collections .abc import Sequence
1514from dataclasses import dataclass
1615from pathlib import Path , PurePath
2322from cuda .pathfinder ._utils .windows_arch import windows_pe_matches_arch , windows_python_arch
2423
2524
25+ def sorted_glob (directory : Path , pattern : str , * , reverse : bool = False ) -> list [Path ]:
26+ """Return ``directory.glob(pattern)`` matches in a deterministic order.
27+
28+ The ordering is deliberately taken from the string form rather than from
29+ ``Path`` comparison: ``PurePath`` ordering is case-insensitive on Windows,
30+ so plain ``sorted()`` over ``Path`` objects would reorder mixed-case
31+ filenames relative to the byte-wise ordering used up to now. Issue #1732
32+ tracks the newest-first policy that rides on this ordering, so it is kept
33+ unchanged on both platforms.
34+ """
35+ return sorted (directory .glob (pattern ), key = str , reverse = reverse )
36+
37+
38+ def _sorted_dir_entry_names (directory : Path ) -> list [str ]:
39+ return sorted (entry .name for entry in directory .iterdir ())
40+
41+
2642def _no_such_file_in_sub_dirs (
2743 sub_dirs : Sequence [str ], file_wild : str , error_messages : list [str ], attachments : list [str ]
2844) -> None :
2945 error_messages .append (f"No such file: { file_wild } " )
3046 for sub_dir in find_sub_dirs_all_sitepackages (sub_dirs ):
3147 attachments .append (f' listdir("{ sub_dir } "):' )
32- for node in sorted ( node_path . name for node_path in Path (sub_dir ). iterdir ( )):
48+ for node in _sorted_dir_entry_names ( Path (sub_dir )):
3349 attachments .append (f" { node } " )
3450
3551
@@ -38,7 +54,7 @@ def _find_so_in_rel_dirs(
3854 so_basename : str ,
3955 error_messages : list [str ],
4056 attachments : list [str ],
41- ) -> str | None :
57+ ) -> Path | None :
4258 sub_dirs_searched : list [tuple [str , ...]] = []
4359 file_wild = so_basename + "*"
4460 for rel_dir in rel_dirs :
@@ -50,29 +66,29 @@ def _find_so_in_rel_dirs(
5066 # multiple coexist, matching the newest-first bias elsewhere in pathfinder
5167 # (see LinuxSearchPlatform.find_in_lib_dir and load_dl_linux._candidate_sonames).
5268 # Issue #1732 tracks the deferred question of raising on true ambiguity.
53- so_path = Path (abs_dir , so_basename )
69+ abs_dir_path = Path (abs_dir )
70+ so_path = abs_dir_path / so_basename
5471 if so_path .is_file ():
55- return str (so_path )
56- for match in sorted (glob .glob (str (Path (abs_dir , file_wild ))), reverse = True ):
57- so_path = Path (match )
72+ return so_path
73+ for so_path in sorted_glob (abs_dir_path , file_wild , reverse = True ):
5874 if so_path .is_file ():
59- return str ( so_path )
75+ return so_path
6076 sub_dirs_searched .append (sub_dir )
6177 for sub_dir in sub_dirs_searched :
6278 _no_such_file_in_sub_dirs (sub_dir , file_wild , error_messages , attachments )
6379 return None
6480
6581
66- def _find_dll_under_dir (dirpath : str , file_wild : str , target_arch : str | None = None ) -> str | None :
67- for match in sorted (glob .glob (str (Path (dirpath , file_wild )))):
68- path = Path (match )
82+ def _find_dll_under_dir (dirpath : Path , file_wild : str , target_arch : str | None = None ) -> Path | None :
83+ for path in sorted_glob (dirpath , file_wild ):
6984 if not path .is_file ():
7085 continue
7186 if is_suppressed_dll_file (path .name ):
7287 continue
88+ # windows_pe_matches_arch() lives in _utils and is still str-typed.
7389 if target_arch is not None and not windows_pe_matches_arch (str (path ), target_arch ):
7490 continue
75- return str ( path )
91+ return path
7692 return None
7793
7894
@@ -81,14 +97,14 @@ def _find_dll_in_rel_dirs(
8197 lib_searched_for : str ,
8298 error_messages : list [str ],
8399 attachments : list [str ],
84- ) -> str | None :
100+ ) -> Path | None :
85101 sub_dirs_searched : list [tuple [str , ...]] = []
86102 for rel_dir in rel_dirs :
87103 sub_dir = PurePath (rel_dir ).parts
88104 for abs_dir in find_sub_dirs_all_sitepackages (sub_dir ):
89- dll_name = _find_dll_under_dir (abs_dir , lib_searched_for )
90- if dll_name is not None :
91- return dll_name
105+ dll_path = _find_dll_under_dir (Path ( abs_dir ) , lib_searched_for )
106+ if dll_path is not None :
107+ return dll_path
92108 sub_dirs_searched .append (sub_dir )
93109 for sub_dir in sub_dirs_searched :
94110 _no_such_file_in_sub_dirs (sub_dir , lib_searched_for , error_messages , attachments )
@@ -100,7 +116,7 @@ def lib_searched_for(self, libname: str) -> str: ...
100116
101117 def site_packages_rel_dirs (self , desc : LibDescriptor ) -> tuple [str , ...]: ...
102118
103- def conda_anchor_point (self , conda_prefix : str ) -> str : ...
119+ def conda_anchor_point (self , conda_prefix : str ) -> Path : ...
104120
105121 def anchor_rel_dirs (self , desc : LibDescriptor ) -> tuple [str , ...]: ...
106122
@@ -110,16 +126,16 @@ def find_in_site_packages(
110126 lib_searched_for : str ,
111127 error_messages : list [str ],
112128 attachments : list [str ],
113- ) -> str | None : ...
129+ ) -> Path | None : ...
114130
115131 def find_in_lib_dir (
116132 self ,
117- lib_dir : str ,
133+ lib_dir : Path ,
118134 desc : LibDescriptor ,
119135 lib_searched_for : str ,
120136 error_messages : list [str ],
121137 attachments : list [str ],
122- ) -> str | None : ...
138+ ) -> Path | None : ...
123139
124140
125141@dataclass (frozen = True , slots = True )
@@ -130,8 +146,8 @@ def lib_searched_for(self, libname: str) -> str:
130146 def site_packages_rel_dirs (self , desc : LibDescriptor ) -> tuple [str , ...]:
131147 return cast (tuple [str , ...], desc .site_packages_linux )
132148
133- def conda_anchor_point (self , conda_prefix : str ) -> str :
134- return conda_prefix
149+ def conda_anchor_point (self , conda_prefix : str ) -> Path :
150+ return Path ( conda_prefix )
135151
136152 def anchor_rel_dirs (self , desc : LibDescriptor ) -> tuple [str , ...]:
137153 return cast (tuple [str , ...], desc .anchor_rel_dirs_linux )
@@ -142,39 +158,37 @@ def find_in_site_packages(
142158 lib_searched_for : str ,
143159 error_messages : list [str ],
144160 attachments : list [str ],
145- ) -> str | None :
161+ ) -> Path | None :
146162 return _find_so_in_rel_dirs (rel_dirs , lib_searched_for , error_messages , attachments )
147163
148164 def find_in_lib_dir (
149165 self ,
150- lib_dir : str ,
166+ lib_dir : Path ,
151167 _desc : LibDescriptor ,
152168 lib_searched_for : str ,
153169 error_messages : list [str ],
154170 attachments : list [str ],
155- ) -> str | None :
156- lib_dir_path = Path (lib_dir )
171+ ) -> Path | None :
157172 # Most libraries have both unversioned and versioned files/symlinks (exact match first)
158- so_path = lib_dir_path / lib_searched_for
173+ so_path = lib_dir / lib_searched_for
159174 if so_path .is_file ():
160- return str ( so_path )
175+ return so_path
161176 # Some libraries only exist as versioned files (e.g., libcupti.so.13 in conda),
162177 # so the glob fallback is needed
163178 file_wild = lib_searched_for + "*"
164179 # Only one match is expected, but to ensure deterministic behavior in unexpected
165180 # situations, and to be internally consistent, we sort in reverse order with the
166181 # intent to return the newest version first. Issue #1732 tracks the deferred
167182 # question of raising on true ambiguity.
168- for match in sorted (glob .glob (str (lib_dir_path / file_wild )), reverse = True ):
169- so_path = Path (match )
183+ for so_path in sorted_glob (lib_dir , file_wild , reverse = True ):
170184 if so_path .is_file ():
171- return str ( so_path )
185+ return so_path
172186 error_messages .append (f"No such file: { file_wild } " )
173187 attachments .append (f' listdir("{ lib_dir } "):' )
174- if not lib_dir_path .is_dir ():
188+ if not lib_dir .is_dir ():
175189 attachments .append (" DIRECTORY DOES NOT EXIST" )
176190 else :
177- for node in sorted ( node_path . name for node_path in lib_dir_path . iterdir () ):
191+ for node in _sorted_dir_entry_names ( lib_dir ):
178192 attachments .append (f" { node } " )
179193 return None
180194
@@ -189,8 +203,8 @@ def lib_searched_for(self, libname: str) -> str:
189203 def site_packages_rel_dirs (self , desc : LibDescriptor ) -> tuple [str , ...]:
190204 return cast (tuple [str , ...], desc .site_packages_windows .for_arch (self .target_arch ))
191205
192- def conda_anchor_point (self , conda_prefix : str ) -> str :
193- return str ( Path (conda_prefix , "Library" ) )
206+ def conda_anchor_point (self , conda_prefix : str ) -> Path :
207+ return Path (conda_prefix , "Library" )
194208
195209 def anchor_rel_dirs (self , desc : LibDescriptor ) -> tuple [str , ...]:
196210 return cast (tuple [str , ...], desc .anchor_rel_dirs_windows .for_arch (self .target_arch ))
@@ -201,32 +215,31 @@ def find_in_site_packages(
201215 lib_searched_for : str ,
202216 error_messages : list [str ],
203217 attachments : list [str ],
204- ) -> str | None :
218+ ) -> Path | None :
205219 return _find_dll_in_rel_dirs (rel_dirs , lib_searched_for , error_messages , attachments )
206220
207221 def find_in_lib_dir (
208222 self ,
209- lib_dir : str ,
223+ lib_dir : Path ,
210224 desc : LibDescriptor ,
211225 _lib_searched_for : str ,
212226 error_messages : list [str ],
213227 attachments : list [str ],
214- ) -> str | None :
228+ ) -> Path | None :
215229 file_wild = desc .name + "*.dll"
216230 target_arch = self .target_arch if desc .requires_windows_binary_arch_check else None
217- dll_name = _find_dll_under_dir (lib_dir , file_wild , target_arch )
218- if dll_name is not None :
219- return dll_name
231+ dll_path = _find_dll_under_dir (lib_dir , file_wild , target_arch )
232+ if dll_path is not None :
233+ return dll_path
220234 if target_arch is None :
221235 error_messages .append (f"No such file: { file_wild } " )
222236 else :
223237 error_messages .append (f"No { target_arch } -compatible PE file: { file_wild } " )
224- lib_dir_path = Path (lib_dir )
225238 attachments .append (f' listdir("{ lib_dir } "):' )
226- if not lib_dir_path .is_dir ():
239+ if not lib_dir .is_dir ():
227240 attachments .append (" DIRECTORY DOES NOT EXIST" )
228241 else :
229- for node in sorted ( node_path . name for node_path in lib_dir_path . iterdir () ):
242+ for node in _sorted_dir_entry_names ( lib_dir ):
230243 attachments .append (f" { node } " )
231244 return None
232245
0 commit comments