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

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

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, ...]] = [] 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

56 

57 

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 

65 

66 

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

84 

85 

86class SearchPlatform(Protocol): 

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

88 

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

90 

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

92 

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

94 

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

102 

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

111 

112 

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

114class LinuxSearchPlatform: 

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

116 return f"lib{libname}.so" 1aefgshicnvobqprkdwxy

117 

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

119 return cast(tuple[str, ...], desc.site_packages_linux) 1acbkdz

120 

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

122 return conda_prefix 1nop

123 

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

125 return cast(tuple[str, ...], desc.anchor_rel_dirs_linux) 1aeftghiunobABqpr

126 

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

135 

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 

165 

166 

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

168class WindowsSearchPlatform: 

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

170 return f"{libname}*.dll" 1aefgshicblmjC

171 

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

173 return cast(tuple[str, ...], desc.site_packages_windows) 1acbj

174 

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

176 return os.path.join(conda_prefix, "Library") 1l

177 

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

179 return cast(tuple[str, ...], desc.anchor_rel_dirs_windows) 1aeftghiubDlm

180 

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

189 

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 

210 

211 

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