Coverage for cuda / pathfinder / _dynamic_libs / search_platform.py: 90.38%
104 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-29 01:27 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-29 01:27 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2# SPDX-License-Identifier: Apache-2.0
4"""Platform abstraction for filesystem search steps.
6The goal is to keep :mod:`search_steps` platform-agnostic: it should not branch
7on OS flags like ``IS_WINDOWS``. Instead, it calls through the single
8``PLATFORM`` instance exported here.
9"""
11from __future__ import annotations
13import glob
14import os
15from collections.abc import Sequence
16from dataclasses import dataclass
17from typing import Protocol, cast
19from cuda.pathfinder._dynamic_libs.lib_descriptor import LibDescriptor
20from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import is_suppressed_dll_file
21from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages
22from cuda.pathfinder._utils.platform_aware import IS_WINDOWS
25def _no_such_file_in_sub_dirs(
26 sub_dirs: Sequence[str], file_wild: str, error_messages: list[str], attachments: list[str]
27) -> None:
28 error_messages.append(f"No such file: {file_wild}") 1abcd
29 for sub_dir in find_sub_dirs_all_sitepackages(sub_dirs): 1abcd
30 attachments.append(f' listdir("{sub_dir}"):') 1cd
31 for node in sorted(os.listdir(sub_dir)): 1cd
32 attachments.append(f" {node}") 1c
35def _find_so_in_rel_dirs(
36 rel_dirs: tuple[str, ...],
37 so_basename: str,
38 error_messages: list[str],
39 attachments: list[str],
40) -> str | None:
41 sub_dirs_searched: list[tuple[str, ...]] = [] 1abnklcd
42 file_wild = so_basename + "*" 1abnklcd
43 for rel_dir in rel_dirs: 1abnklcd
44 sub_dir = tuple(rel_dir.split(os.path.sep)) 1abnklcd
45 for abs_dir in find_sub_dirs_all_sitepackages(sub_dir): 1abnklcd
46 # Exact unversioned match first; fall back to versioned names because some
47 # distros only ship lib<name>.so.<major> (e.g. conda libcupti). Only one match
48 # is expected in practice. Sort in reverse so the newest-sorting name wins if
49 # multiple coexist, matching the newest-first bias elsewhere in pathfinder
50 # (see LinuxSearchPlatform.find_in_lib_dir and load_dl_linux._candidate_sonames).
51 # Issue #1732 tracks the deferred question of raising on true ambiguity.
52 so_name = os.path.join(abs_dir, so_basename) 1nklcd
53 if os.path.isfile(so_name): 1nklcd
54 return so_name 1n
55 for so_name in sorted(glob.glob(os.path.join(abs_dir, file_wild)), reverse=True): 1klcd
56 if os.path.isfile(so_name): 1kl
57 return so_name 1kl
58 sub_dirs_searched.append(sub_dir) 1abcd
59 for sub_dir in sub_dirs_searched: 1abcd
60 _no_such_file_in_sub_dirs(sub_dir, file_wild, error_messages, attachments) 1abcd
61 return None 1abcd
64def _find_dll_under_dir(dirpath: str, file_wild: str) -> str | None:
65 for path in sorted(glob.glob(os.path.join(dirpath, file_wild))): 1aefghiopj
66 if not os.path.isfile(path): 1aefghiopj
67 continue
68 if not is_suppressed_dll_file(os.path.basename(path)): 1aefghiopj
69 return path 1aefghiopj
70 return None
73def _find_dll_in_rel_dirs(
74 rel_dirs: tuple[str, ...],
75 lib_searched_for: str,
76 error_messages: list[str],
77 attachments: list[str],
78) -> str | None:
79 sub_dirs_searched: list[tuple[str, ...]] = [] 1abj
80 for rel_dir in rel_dirs: 1abj
81 sub_dir = tuple(rel_dir.split(os.path.sep)) 1abj
82 for abs_dir in find_sub_dirs_all_sitepackages(sub_dir): 1abj
83 dll_name = _find_dll_under_dir(abs_dir, lib_searched_for) 1j
84 if dll_name is not None: 1j
85 return dll_name 1j
86 sub_dirs_searched.append(sub_dir) 1ab
87 for sub_dir in sub_dirs_searched: 1ab
88 _no_such_file_in_sub_dirs(sub_dir, lib_searched_for, error_messages, attachments) 1ab
89 return None 1ab
92class SearchPlatform(Protocol):
93 def lib_searched_for(self, libname: str) -> str: ...
95 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: ...
97 def conda_anchor_point(self, conda_prefix: str) -> str: ...
99 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: ...
101 def find_in_site_packages(
102 self,
103 rel_dirs: tuple[str, ...],
104 lib_searched_for: str,
105 error_messages: list[str],
106 attachments: list[str],
107 ) -> str | None: ...
109 def find_in_lib_dir(
110 self,
111 lib_dir: str,
112 libname: str,
113 lib_searched_for: str,
114 error_messages: list[str],
115 attachments: list[str],
116 ) -> str | None: ...
119@dataclass(frozen=True, slots=True)
120class LinuxSearchPlatform:
121 def lib_searched_for(self, libname: str) -> str:
122 return f"lib{libname}.so" 1aefgxhibqArvustmwnklcdBCD
124 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
125 return cast(tuple[str, ...], desc.site_packages_linux) 1abnklcdE
127 def conda_anchor_point(self, conda_prefix: str) -> str:
128 return conda_prefix 1qrustm
130 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
131 return cast(tuple[str, ...], desc.anchor_rel_dirs_linux) 1aefyghizqrFGvustmw
133 def find_in_site_packages(
134 self,
135 rel_dirs: tuple[str, ...],
136 lib_searched_for: str,
137 error_messages: list[str],
138 attachments: list[str],
139 ) -> str | None:
140 return _find_so_in_rel_dirs(rel_dirs, lib_searched_for, error_messages, attachments) 1abnklcd
142 def find_in_lib_dir(
143 self,
144 lib_dir: str,
145 _libname: str,
146 lib_searched_for: str,
147 error_messages: list[str],
148 attachments: list[str],
149 ) -> str | None:
150 # Most libraries have both unversioned and versioned files/symlinks (exact match first)
151 so_name = os.path.join(lib_dir, lib_searched_for) 1aefghiqrvustmw
152 if os.path.isfile(so_name): 1aefghiqrvustmw
153 return so_name 1aefghivuw
154 # Some libraries only exist as versioned files (e.g., libcupti.so.13 in conda),
155 # so the glob fallback is needed
156 file_wild = lib_searched_for + "*" 1qrstm
157 # Only one match is expected, but to ensure deterministic behavior in unexpected
158 # situations, and to be internally consistent, we sort in reverse order with the
159 # intent to return the newest version first. Issue #1732 tracks the deferred
160 # question of raising on true ambiguity.
161 for so_name in sorted(glob.glob(os.path.join(lib_dir, file_wild)), reverse=True): 1qrstm
162 if os.path.isfile(so_name): 1qrst
163 return so_name 1qrst
164 error_messages.append(f"No such file: {file_wild}") 1m
165 attachments.append(f' listdir("{lib_dir}"):') 1m
166 if not os.path.isdir(lib_dir): 1m
167 attachments.append(" DIRECTORY DOES NOT EXIST")
168 else:
169 for node in sorted(os.listdir(lib_dir)): 1m
170 attachments.append(f" {node}") 1m
171 return None 1m
174@dataclass(frozen=True, slots=True)
175class WindowsSearchPlatform:
176 def lib_searched_for(self, libname: str) -> str:
177 return f"{libname}*.dll" 1aefgxhibopjH
179 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
180 return cast(tuple[str, ...], desc.site_packages_windows) 1abj
182 def conda_anchor_point(self, conda_prefix: str) -> str:
183 return os.path.join(conda_prefix, "Library") 1o
185 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
186 return cast(tuple[str, ...], desc.anchor_rel_dirs_windows) 1aefyghizIop
188 def find_in_site_packages(
189 self,
190 rel_dirs: tuple[str, ...],
191 lib_searched_for: str,
192 error_messages: list[str],
193 attachments: list[str],
194 ) -> str | None:
195 return _find_dll_in_rel_dirs(rel_dirs, lib_searched_for, error_messages, attachments) 1abj
197 def find_in_lib_dir(
198 self,
199 lib_dir: str,
200 libname: str,
201 _lib_searched_for: str,
202 error_messages: list[str],
203 attachments: list[str],
204 ) -> str | None:
205 file_wild = libname + "*.dll" 1aefghiop
206 dll_name = _find_dll_under_dir(lib_dir, file_wild) 1aefghiop
207 if dll_name is not None: 1aefghiop
208 return dll_name 1aefghiop
209 error_messages.append(f"No such file: {file_wild}")
210 attachments.append(f' listdir("{lib_dir}"):')
211 if not os.path.isdir(lib_dir):
212 attachments.append(" DIRECTORY DOES NOT EXIST")
213 else:
214 for node in sorted(os.listdir(lib_dir)):
215 attachments.append(f" {node}")
216 return None
219PLATFORM: SearchPlatform = WindowsSearchPlatform() if IS_WINDOWS else LinuxSearchPlatform()