Coverage for cuda/pathfinder/_headers/find_nvidia_headers.py: 89.72%

107 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-03 02:41 +0000

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

2# SPDX-License-Identifier: Apache-2.0 

3 

4from __future__ import annotations 

5 

6import functools 

7import glob 

8import os 

9from collections.abc import Callable 

10from dataclasses import dataclass 

11from typing import TYPE_CHECKING 

12 

13from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import ( 

14 _resolve_system_loaded_abs_path_in_subprocess, 

15) 

16from cuda.pathfinder._dynamic_libs.search_steps import derive_ctk_root 

17from cuda.pathfinder._headers.header_descriptor import ( 

18 HEADER_DESCRIPTORS, 

19 platform_include_subdirs, 

20 resolve_conda_anchor, 

21 system_install_dir_patterns, 

22) 

23from cuda.pathfinder._utils.ctk_root_canary import CTK_ROOT_CANARY_ANCHOR_LIBNAMES 

24from cuda.pathfinder._utils.env_vars import get_cuda_path_or_home 

25from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages 

26from cuda.pathfinder._utils.path_sort import numeric_aware_path_sort_key 

27 

28if TYPE_CHECKING: 

29 from cuda.pathfinder._headers.header_descriptor import HeaderDescriptor 

30 

31# --------------------------------------------------------------------------- 

32# Data types 

33# --------------------------------------------------------------------------- 

34 

35 

36@dataclass 

37class LocatedHeaderDir: 

38 abs_path: str | None 

39 found_via: str 

40 

41 def __post_init__(self) -> None: 

42 self.abs_path = _abs_norm(self.abs_path) 1MNOPQRnzABCDEFGHkIJ

43 

44 

45#: Type alias for a header find step callable. 

46HeaderFindStep = Callable[["HeaderDescriptor"], LocatedHeaderDir | None] 

47 

48# --------------------------------------------------------------------------- 

49# Helpers 

50# --------------------------------------------------------------------------- 

51 

52 

53def _abs_norm(path: str | None) -> str | None: 

54 if path: 1MNOPQRnzABCDEFGHkIJ

55 return os.path.normpath(os.path.abspath(path)) 1MNOPQRnzABCDEFGHkIJ

56 return None 

57 

58 

59def _joined_isfile(dirpath: str, basename: str) -> bool: 

60 return os.path.isfile(os.path.join(dirpath, basename)) 1MNOPQRnazhbAcdefBCDgEFGHkoipqrstuvwxyjmIJ

61 

62 

63def _locate_in_anchor_layout(desc: HeaderDescriptor, anchor_point: str) -> str | None: 

64 """Search for a header under *anchor_point* using the descriptor's layout fields.""" 

65 h_basename = desc.header_basename 1nazhbAcdefBCDgEFGHkoipqrstuvwxyjmIJ

66 for rel_dir in desc.anchor_include_rel_dirs: 1nazhbAcdefBCDgEFGHkoipqrstuvwxyjmIJ

67 idir = os.path.join(anchor_point, rel_dir) 1nazhbAcdefBCDgEFGHkoipqrstuvwxyjmIJ

68 for subdir in platform_include_subdirs(desc): 1nazhbAcdefBCDgEFGHkoipqrstuvwxyjmIJ

69 cdir = os.path.join(idir, subdir) 1n

70 if _joined_isfile(cdir, h_basename): 1n

71 return cdir 1n

72 if _joined_isfile(idir, h_basename): 1azhbAcdefBCDgEFGHkoipqrstuvwxyjmIJ

73 return idir 1zABCDEFGHkIJ

74 return None 1ahbcdefgoipqrstuvwxyjm

75 

76 

77# --------------------------------------------------------------------------- 

78# Find steps 

79# --------------------------------------------------------------------------- 

80 

81 

82def find_in_site_packages(desc: HeaderDescriptor) -> LocatedHeaderDir | None: 

83 """Search pip wheel install locations.""" 

84 for sub_dir in desc.site_packages_dirs: 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

85 hdr_dir: str # help mypy 

86 for hdr_dir in find_sub_dirs_all_sitepackages(tuple(sub_dir.split("/"))): 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

87 if _joined_isfile(hdr_dir, desc.header_basename): 

88 return LocatedHeaderDir(abs_path=hdr_dir, found_via="site-packages") 

89 return None 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

90 

91 

92def find_in_conda(desc: HeaderDescriptor) -> LocatedHeaderDir | None: 

93 """Search ``$CONDA_PREFIX``.""" 

94 conda_prefix = os.environ.get("CONDA_PREFIX") 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

95 if not conda_prefix: 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

96 return None 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

97 anchor_point = resolve_conda_anchor(desc, conda_prefix) 

98 if anchor_point is None: 

99 return None 

100 found_header_path = _locate_in_anchor_layout(desc, anchor_point) 

101 if found_header_path: 

102 return LocatedHeaderDir(abs_path=found_header_path, found_via="conda") 

103 return None 

104 

105 

106def find_in_product_roots(desc: HeaderDescriptor) -> LocatedHeaderDir | None: 

107 """Search roots supplied through product-specific environment variables.""" 

108 for env_var in desc.product_root_env_vars: 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

109 root = os.environ.get(env_var) 1ijIJ

110 if not root: 1ijIJ

111 continue 1ij

112 result = _locate_in_anchor_layout(desc, root) 1IJ

113 if result is not None: 1IJ

114 return LocatedHeaderDir(abs_path=result, found_via=env_var) 1IJ

115 return None 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjm

116 

117 

118def find_in_cuda_path(desc: HeaderDescriptor) -> LocatedHeaderDir | None: 

119 """Search ``$CUDA_PATH`` / ``$CUDA_HOME``.""" 

120 cuda_home = get_cuda_path_or_home() 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjm

121 if cuda_home is None: 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjm

122 return None 1KLk

123 result = _locate_in_anchor_layout(desc, cuda_home) 1nazhbAcdefBCDgEFGHoipqrstuvwxyjm

124 if result is not None: 1nazhbAcdefBCDgEFGHoipqrstuvwxyjm

125 return LocatedHeaderDir(abs_path=result, found_via="CUDA_PATH") 1nzABCDEFGH

126 return None 1ahbcdefgoipqrstuvwxyjm

127 

128 

129def find_via_ctk_root_canary(desc: HeaderDescriptor) -> LocatedHeaderDir | None: 

130 """Try CTK header lookup via CTK-root canary probing. 

131 

132 Skips immediately if the descriptor does not opt in (``use_ctk_root_canary``). 

133 Otherwise, system-loads ``cudart`` in a fully isolated Python subprocess, derives 

134 CTK root from the resolved library path, and searches the expected include 

135 layout under that root. 

136 """ 

137 if not desc.use_ctk_root_canary: 1ahbcdefgKLkoipqrstuvwxyjm

138 return None 1oipqrstuvwxyjm

139 canary_abs_path = _resolve_system_loaded_abs_path_in_subprocess(CTK_ROOT_CANARY_ANCHOR_LIBNAMES[0]) 1ahbcdefgKLk

140 if canary_abs_path is None: 1ahbcdefgKk

141 return None 1abcdefgK

142 ctk_root = derive_ctk_root(canary_abs_path) 1ahbcdefgk

143 if ctk_root is None: 1ahbcdefgk

144 return None 

145 result = _locate_in_anchor_layout(desc, ctk_root) 1ahbcdefgk

146 if result is not None: 1ahbcdefgk

147 return LocatedHeaderDir(abs_path=result, found_via="system-ctk-root") 1k

148 return None 1ahbcdefg

149 

150 

151def find_in_system_install_dirs(desc: HeaderDescriptor) -> LocatedHeaderDir | None: 

152 """Search system install directories (glob patterns).""" 

153 for pattern in system_install_dir_patterns(desc): 1MNOPQRahbcdefgKoipqrstuvwxyjm

154 for hdr_dir in sorted(glob.glob(pattern), key=numeric_aware_path_sort_key, reverse=True): 1MNOPQRijm

155 if _joined_isfile(hdr_dir, desc.header_basename): 1MNOPQRij

156 return LocatedHeaderDir(abs_path=hdr_dir, found_via="supported_install_dir") 1MNOPQR

157 return None 1ahbcdefgKoipqrstuvwxyjm

158 

159 

160# --------------------------------------------------------------------------- 

161# Step sequence and cascade runner 

162# --------------------------------------------------------------------------- 

163 

164#: Unified find steps — each step self-gates based on descriptor fields. 

165FIND_STEPS: tuple[HeaderFindStep, ...] = ( 

166 find_in_site_packages, 

167 find_in_conda, 

168 find_in_product_roots, 

169 find_in_cuda_path, 

170 find_via_ctk_root_canary, 

171 find_in_system_install_dirs, 

172) 

173 

174 

175def run_find_steps(desc: HeaderDescriptor, steps: tuple[HeaderFindStep, ...]) -> LocatedHeaderDir | None: 

176 """Run find steps in order, returning the first hit.""" 

177 for step in steps: 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

178 result = step(desc) 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

179 if result is not None: 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

180 return result 1nzABCDEFGHkIJ

181 return None 1ahbcdefgKoipqrstuvwxyjm

182 

183 

184# --------------------------------------------------------------------------- 

185# Public API 

186# --------------------------------------------------------------------------- 

187 

188 

189@functools.cache 

190def locate_nvidia_header_directory(libname: str) -> LocatedHeaderDir | None: 

191 """Locate the header directory for a supported NVIDIA library. 

192 

193 Args: 

194 libname (str): The short name of the library whose headers are needed 

195 (e.g., ``"nvrtc"``, ``"cusolver"``, ``"nvshmem"``). 

196 

197 Returns: 

198 LocatedHeaderDir or None: A LocatedHeaderDir object containing the absolute path 

199 to the discovered header directory and information about where it was found, 

200 or ``None`` if the headers cannot be found. 

201 

202 Raises: 

203 RuntimeError: If ``libname`` is not in the supported set. 

204 

205 Search order: 

206 1. **NVIDIA Python wheels** — site-packages directories from the descriptor. 

207 2. **Conda environments** — platform-specific conda include layouts. 

208 3. **Product environment variables** — for example, ``CUDNN_PATH``. 

209 4. **CUDA Toolkit environment variables** — ``CUDA_PATH`` / ``CUDA_HOME``. 

210 5. **CTK root canary probe** — subprocess canary (descriptors with 

211 ``use_ctk_root_canary=True`` only). 

212 6. **System install directories** — glob patterns from the descriptor. 

213 """ 

214 desc = HEADER_DESCRIPTORS.get(libname) 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJS

215 if desc is None: 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJS

216 raise RuntimeError(f"UNKNOWN {libname=}") 1S

217 return run_find_steps(desc, FIND_STEPS) 1nazhbAcdefBCDgEFGKLHkoipqrstuvwxyjmIJ

218 

219 

220def find_nvidia_header_directory(libname: str) -> str | None: 

221 """Locate the header directory for a supported NVIDIA library. 

222 

223 Args: 

224 libname (str): The short name of the library whose headers are needed 

225 (e.g., ``"nvrtc"``, ``"cusolver"``, ``"nvshmem"``). 

226 

227 Returns: 

228 str or None: Absolute path to the discovered header directory, or ``None`` 

229 if the headers cannot be found. 

230 

231 Raises: 

232 RuntimeError: If ``libname`` is not in the supported set. 

233 

234 Search order: 

235 1. **NVIDIA Python wheels** — site-packages directories from the descriptor. 

236 2. **Conda environments** — platform-specific conda include layouts. 

237 3. **Product environment variables** — for example, ``CUDNN_PATH``. 

238 4. **CUDA Toolkit environment variables** — ``CUDA_PATH`` / ``CUDA_HOME``. 

239 5. **CTK root canary probe** — subprocess canary (descriptors with 

240 ``use_ctk_root_canary=True`` only). 

241 6. **System install directories** — glob patterns from the descriptor. 

242 """ 

243 found = locate_nvidia_header_directory(libname) 1nazhbAcdefBCDgEFGKLoipqrstuvwxyjmS

244 return found.abs_path if found else None 1nazhbAcdefBCDgEFGKoipqrstuvwxyjm