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

1# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 

2# SPDX-License-Identifier: Apache-2.0 

3 

4"""Platform abstraction for filesystem search steps. 

5 

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""" 

10 

11from __future__ import annotations 

12 

13import glob 

14import os 

15from collections.abc import Sequence 

16from dataclasses import dataclass 

17from typing import Protocol, cast 

18 

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 

23 

24 

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

33 

34 

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

62 

63 

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 

71 

72 

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

90 

91 

92class SearchPlatform(Protocol): 

93 def lib_searched_for(self, libname: str) -> str: ... 

94 

95 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: ... 

96 

97 def conda_anchor_point(self, conda_prefix: str) -> str: ... 

98 

99 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: ... 

100 

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: ... 

108 

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: ... 

117 

118 

119@dataclass(frozen=True, slots=True) 

120class LinuxSearchPlatform: 

121 def lib_searched_for(self, libname: str) -> str: 

122 return f"lib{libname}.so" 1aefgxhibqArvustmwnklcdBCD

123 

124 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: 

125 return cast(tuple[str, ...], desc.site_packages_linux) 1abnklcdE

126 

127 def conda_anchor_point(self, conda_prefix: str) -> str: 

128 return conda_prefix 1qrustm

129 

130 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: 

131 return cast(tuple[str, ...], desc.anchor_rel_dirs_linux) 1aefyghizqrFGvustmw

132 

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

141 

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

172 

173 

174@dataclass(frozen=True, slots=True) 

175class WindowsSearchPlatform: 

176 def lib_searched_for(self, libname: str) -> str: 

177 return f"{libname}*.dll" 1aefgxhibopjH

178 

179 def site_packages_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: 

180 return cast(tuple[str, ...], desc.site_packages_windows) 1abj

181 

182 def conda_anchor_point(self, conda_prefix: str) -> str: 

183 return os.path.join(conda_prefix, "Library") 1o

184 

185 def anchor_rel_dirs(self, desc: LibDescriptor) -> tuple[str, ...]: 

186 return cast(tuple[str, ...], desc.anchor_rel_dirs_windows) 1aefyghizIop

187 

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

196 

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 

217 

218 

219PLATFORM: SearchPlatform = WindowsSearchPlatform() if IS_WINDOWS else LinuxSearchPlatform()