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

96 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-25 01:07 +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) 

22from cuda.pathfinder._utils.env_vars import get_cuda_path_or_home 

23from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages 

24 

25if TYPE_CHECKING: 

26 from cuda.pathfinder._headers.header_descriptor import HeaderDescriptor 

27 

28# --------------------------------------------------------------------------- 

29# Data types 

30# --------------------------------------------------------------------------- 

31 

32 

33@dataclass 

34class LocatedHeaderDir: 

35 abs_path: str | None 

36 found_via: str 

37 

38 def __post_init__(self) -> None: 

39 self.abs_path = _abs_norm(self.abs_path) 1knopqrstuh

40 

41 

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

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

44 

45# --------------------------------------------------------------------------- 

46# Helpers 

47# --------------------------------------------------------------------------- 

48 

49 

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

51 if path: 1knopqrstuh

52 return os.path.normpath(os.path.abspath(path)) 1knopqrstuh

53 return None 

54 

55 

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

57 return os.path.isfile(os.path.join(dirpath, basename)) 1kanbocdefpqrgstuhlmj

58 

59 

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

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

62 h_basename = desc.header_basename 1kanbocdefpqrgstuhlmj

63 for rel_dir in desc.anchor_include_rel_dirs: 1kanbocdefpqrgstuhlmj

64 idir = os.path.join(anchor_point, rel_dir) 1kanbocdefpqrgstuhlmj

65 for subdir in platform_include_subdirs(desc): 1kanbocdefpqrgstuhlmj

66 cdir = os.path.join(idir, subdir) 1k

67 if _joined_isfile(cdir, h_basename): 1k

68 return cdir 1k

69 if _joined_isfile(idir, h_basename): 1anbocdefpqrgstuhlmj

70 return idir 1nopqrstuh

71 return None 1abcdefglmj

72 

73 

74# --------------------------------------------------------------------------- 

75# Find steps 

76# --------------------------------------------------------------------------- 

77 

78 

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

80 """Search pip wheel install locations.""" 

81 for sub_dir in desc.site_packages_dirs: 1kanbocdefpqrgstvwuhlmj

82 hdr_dir: str # help mypy 

83 for hdr_dir in find_sub_dirs_all_sitepackages(tuple(sub_dir.split("/"))): 1kanbocdefpqrgstvwuhlmj

84 if _joined_isfile(hdr_dir, desc.header_basename): 

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

86 return None 1kanbocdefpqrgstvwuhlmj

87 

88 

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

90 """Search ``$CONDA_PREFIX``.""" 

91 conda_prefix = os.environ.get("CONDA_PREFIX") 1kanbocdefpqrgstvwuhlmj

92 if not conda_prefix: 1kanbocdefpqrgstvwuhlmj

93 return None 1kanbocdefpqrgstvwuhlmj

94 anchor_point = resolve_conda_anchor(desc, conda_prefix) 

95 if anchor_point is None: 

96 return None 

97 found_header_path = _locate_in_anchor_layout(desc, anchor_point) 

98 if found_header_path: 

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

100 return None 

101 

102 

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

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

105 cuda_home = get_cuda_path_or_home() 1kanbocdefpqrgstvwuhlmj

106 if cuda_home is None: 1kanbocdefpqrgstvwuhlmj

107 return None 1vwh

108 result = _locate_in_anchor_layout(desc, cuda_home) 1kanbocdefpqrgstulmj

109 if result is not None: 1kanbocdefpqrgstulmj

110 return LocatedHeaderDir(abs_path=result, found_via="CUDA_PATH") 1knopqrstu

111 return None 1abcdefglmj

112 

113 

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

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

116 

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

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

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

120 layout under that root. 

121 """ 

122 if not desc.use_ctk_root_canary: 1abcdefgvwhlmj

123 return None 1lmj

124 canary_abs_path = _resolve_system_loaded_abs_path_in_subprocess("cudart") 1abcdefgvwh

125 if canary_abs_path is None: 1abcdefgvh

126 return None 1abcdefgv

127 ctk_root = derive_ctk_root(canary_abs_path) 1abcdefgh

128 if ctk_root is None: 1abcdefgh

129 return None 

130 result = _locate_in_anchor_layout(desc, ctk_root) 1abcdefgh

131 if result is not None: 1abcdefgh

132 return LocatedHeaderDir(abs_path=result, found_via="system-ctk-root") 1h

133 return None 1abcdefg

134 

135 

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

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

138 for pattern in desc.system_install_dirs: 1abcdefgvlmj

139 for hdr_dir in sorted(glob.glob(pattern), reverse=True): 1j

140 if _joined_isfile(hdr_dir, desc.header_basename): 

141 return LocatedHeaderDir(abs_path=hdr_dir, found_via="supported_install_dir") 

142 return None 1abcdefgvlmj

143 

144 

145# --------------------------------------------------------------------------- 

146# Step sequence and cascade runner 

147# --------------------------------------------------------------------------- 

148 

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

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

151 find_in_site_packages, 

152 find_in_conda, 

153 find_in_cuda_path, 

154 find_via_ctk_root_canary, 

155 find_in_system_install_dirs, 

156) 

157 

158 

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

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

161 for step in steps: 1kanbocdefpqrgstvwuhlmj

162 result = step(desc) 1kanbocdefpqrgstvwuhlmj

163 if result is not None: 1kanbocdefpqrgstvwuhlmj

164 return result 1knopqrstuh

165 return None 1abcdefgvlmj

166 

167 

168# --------------------------------------------------------------------------- 

169# Public API 

170# --------------------------------------------------------------------------- 

171 

172 

173@functools.cache 

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

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

176 

177 Args: 

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

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

180 

181 Returns: 

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

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

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

185 

186 Raises: 

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

188 

189 Search order: 

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

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

192 3. **CUDA Toolkit environment variables** — ``CUDA_PATH`` / ``CUDA_HOME``. 

193 4. **CTK root canary probe** — subprocess canary (descriptors with 

194 ``use_ctk_root_canary=True`` only). 

195 5. **System install directories** — glob patterns from the descriptor. 

196 """ 

197 desc = HEADER_DESCRIPTORS.get(libname) 1kanbocdefpqrgstvwuhlmjx

198 if desc is None: 1kanbocdefpqrgstvwuhlmjx

199 raise RuntimeError(f"UNKNOWN {libname=}") 1x

200 return run_find_steps(desc, FIND_STEPS) 1kanbocdefpqrgstvwuhlmj

201 

202 

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

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

205 

206 Args: 

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

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

209 

210 Returns: 

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

212 if the headers cannot be found. 

213 

214 Raises: 

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

216 

217 Search order: 

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

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

220 3. **CUDA Toolkit environment variables** — ``CUDA_PATH`` / ``CUDA_HOME``. 

221 4. **CTK root canary probe** — subprocess canary (descriptors with 

222 ``use_ctk_root_canary=True`` only). 

223 5. **System install directories** — glob patterns from the descriptor. 

224 """ 

225 found = locate_nvidia_header_directory(libname) 1kanbocdefpqrgstvwlmjx

226 return found.abs_path if found else None 1kanbocdefpqrgstvlmj