Coverage for cuda / pathfinder / _dynamic_libs / search_platform.py: 81.73%
104 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-08 01:07 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-08 01:07 +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}") 1acbd
29 for sub_dir in find_sub_dirs_all_sitepackages(sub_dirs): 1acbd
30 attachments.append(f' listdir("{sub_dir}"):') 1d
31 for node in sorted(os.listdir(sub_dir)): 1d
32 attachments.append(f" {node}")
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, ...]] = [] 1acbkd
42 file_wild = so_basename + "*" 1acbkd
43 for rel_dir in rel_dirs: 1acbkd
44 sub_dir = tuple(rel_dir.split(os.path.sep)) 1acbkd
45 for abs_dir in find_sub_dirs_all_sitepackages(sub_dir): 1acbkd
46 so_name = os.path.join(abs_dir, so_basename) 1kd
47 if os.path.isfile(so_name): 1kd
48 return so_name 1k
49 for so_name in sorted(glob.glob(os.path.join(abs_dir, file_wild))): 1d
50 if os.path.isfile(so_name):
51 return so_name
52 sub_dirs_searched.append(sub_dir) 1acbd
53 for sub_dir in sub_dirs_searched: 1acbd
54 _no_such_file_in_sub_dirs(sub_dir, file_wild, error_messages, attachments) 1acbd
55 return None 1acbd
58def _find_dll_under_dir(dirpath: str, file_wild: str) -> str | None:
59 for path in sorted(glob.glob(os.path.join(dirpath, file_wild))): 1aefghiblmj
60 if not os.path.isfile(path): 1aefghiblmj
61 continue
62 if not is_suppressed_dll_file(os.path.basename(path)): 1aefghiblmj
63 return path 1aefghiblmj
64 return None
67def _find_dll_in_rel_dirs(
68 rel_dirs: tuple[str, ...],
69 lib_searched_for: str,
70 error_messages: list[str],
71 attachments: list[str],
72) -> str | None:
73 sub_dirs_searched: list[tuple[str, ...]] = [] 1acbj
74 for rel_dir in rel_dirs: 1acbj
75 sub_dir = tuple(rel_dir.split(os.path.sep)) 1acbj
76 for abs_dir in find_sub_dirs_all_sitepackages(sub_dir): 1acbj
77 dll_name = _find_dll_under_dir(abs_dir, lib_searched_for) 1j
78 if dll_name is not None: 1j
79 return dll_name 1j
80 sub_dirs_searched.append(sub_dir) 1acb
81 for sub_dir in sub_dirs_searched: 1acb
82 _no_such_file_in_sub_dirs(sub_dir, lib_searched_for, error_messages, attachments) 1acb
83 return None 1acb
86class SearchPlatform(Protocol):
87 def lib_searched_for(self, libname: str) -> str: ...
89 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: ...
91 def conda_anchor_point(self, conda_prefix: str) -> str: ...
93 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: ...
95 def find_in_site_packages(
96 self,
97 rel_dirs: tuple[str, ...],
98 lib_searched_for: str,
99 error_messages: list[str],
100 attachments: list[str],
101 ) -> str | None: ...
103 def find_in_lib_dir(
104 self,
105 lib_dir: str,
106 libname: str,
107 lib_searched_for: str,
108 error_messages: list[str],
109 attachments: list[str],
110 ) -> str | None: ...
113@dataclass(frozen=True, slots=True)
114class LinuxSearchPlatform:
115 def lib_searched_for(self, libname: str) -> str:
116 return f"lib{libname}.so" 1aefgshicnvobqprkdwxy
118 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
119 return cast(tuple[str, ...], desc.site_packages_linux) 1acbkdz
121 def conda_anchor_point(self, conda_prefix: str) -> str:
122 return conda_prefix 1nop
124 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
125 return cast(tuple[str, ...], desc.anchor_rel_dirs_linux) 1aeftghiunobABqpr
127 def find_in_site_packages(
128 self,
129 rel_dirs: tuple[str, ...],
130 lib_searched_for: str,
131 error_messages: list[str],
132 attachments: list[str],
133 ) -> str | None:
134 return _find_so_in_rel_dirs(rel_dirs, lib_searched_for, error_messages, attachments) 1acbkd
136 def find_in_lib_dir(
137 self,
138 lib_dir: str,
139 _libname: str,
140 lib_searched_for: str,
141 error_messages: list[str],
142 attachments: list[str],
143 ) -> str | None:
144 # Most libraries have both unversioned and versioned files/symlinks (exact match first)
145 so_name = os.path.join(lib_dir, lib_searched_for) 1aefghinobqpr
146 if os.path.isfile(so_name): 1aefghinobqpr
147 return so_name 1aefghibqpr
148 # Some libraries only exist as versioned files (e.g., libcupti.so.13 in conda),
149 # so the glob fallback is needed
150 file_wild = lib_searched_for + "*" 1no
151 # Only one match is expected, but to ensure deterministic behavior in unexpected
152 # situations, and to be internally consistent, we sort in reverse order with the
153 # intent to return the newest version first.
154 for so_name in sorted(glob.glob(os.path.join(lib_dir, file_wild)), reverse=True): 1no
155 if os.path.isfile(so_name): 1no
156 return so_name 1no
157 error_messages.append(f"No such file: {file_wild}")
158 attachments.append(f' listdir("{lib_dir}"):')
159 if not os.path.isdir(lib_dir):
160 attachments.append(" DIRECTORY DOES NOT EXIST")
161 else:
162 for node in sorted(os.listdir(lib_dir)):
163 attachments.append(f" {node}")
164 return None
167@dataclass(frozen=True, slots=True)
168class WindowsSearchPlatform:
169 def lib_searched_for(self, libname: str) -> str:
170 return f"{libname}*.dll" 1aefgshicblmjC
172 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
173 return cast(tuple[str, ...], desc.site_packages_windows) 1acbj
175 def conda_anchor_point(self, conda_prefix: str) -> str:
176 return os.path.join(conda_prefix, "Library") 1l
178 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]:
179 return cast(tuple[str, ...], desc.anchor_rel_dirs_windows) 1aeftghiubDlm
181 def find_in_site_packages(
182 self,
183 rel_dirs: tuple[str, ...],
184 lib_searched_for: str,
185 error_messages: list[str],
186 attachments: list[str],
187 ) -> str | None:
188 return _find_dll_in_rel_dirs(rel_dirs, lib_searched_for, error_messages, attachments) 1acbj
190 def find_in_lib_dir(
191 self,
192 lib_dir: str,
193 libname: str,
194 _lib_searched_for: str,
195 error_messages: list[str],
196 attachments: list[str],
197 ) -> str | None:
198 file_wild = libname + "*.dll" 1aefghiblm
199 dll_name = _find_dll_under_dir(lib_dir, file_wild) 1aefghiblm
200 if dll_name is not None: 1aefghiblm
201 return dll_name 1aefghiblm
202 error_messages.append(f"No such file: {file_wild}")
203 attachments.append(f' listdir("{lib_dir}"):')
204 if not os.path.isdir(lib_dir):
205 attachments.append(" DIRECTORY DOES NOT EXIST")
206 else:
207 for node in sorted(os.listdir(lib_dir)):
208 attachments.append(f" {node}")
209 return None
212PLATFORM: SearchPlatform = WindowsSearchPlatform() if IS_WINDOWS else LinuxSearchPlatform()