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

97 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-29 01:38 +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.ctk_root_canary import CTK_ROOT_CANARY_ANCHOR_LIBNAMES 

23from cuda.pathfinder._utils.env_vars import get_cuda_path_or_home 

24from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages 

25 

26if TYPE_CHECKING: 

27 from cuda.pathfinder._headers.header_descriptor import HeaderDescriptor 

28 

29# --------------------------------------------------------------------------- 

30# Data types 

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

32 

33 

34@dataclass 

35class LocatedHeaderDir: 

36 abs_path: str | None 

37 found_via: str 

38 

39 def __post_init__(self) -> None: 

40 self.abs_path = _abs_norm(self.abs_path) 1lxyzABCDEFi

41 

42 

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

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

45 

46# --------------------------------------------------------------------------- 

47# Helpers 

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

49 

50 

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

52 if path: 1lxyzABCDEFi

53 return os.path.normpath(os.path.abspath(path)) 1lxyzABCDEFi

54 return None 

55 

56 

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

58 return os.path.isfile(os.path.join(dirpath, basename)) 1laxhbycdefzABgCDEFimnopqrstuvwk

59 

60 

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

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

63 h_basename = desc.header_basename 1laxhbycdefzABgCDEFimnopqrstuvwk

64 for rel_dir in desc.anchor_include_rel_dirs: 1laxhbycdefzABgCDEFimnopqrstuvwk

65 idir = os.path.join(anchor_point, rel_dir) 1laxhbycdefzABgCDEFimnopqrstuvwk

66 for subdir in platform_include_subdirs(desc): 1laxhbycdefzABgCDEFimnopqrstuvwk

67 cdir = os.path.join(idir, subdir) 1l

68 if _joined_isfile(cdir, h_basename): 1l

69 return cdir 1l

70 if _joined_isfile(idir, h_basename): 1axhbycdefzABgCDEFimnopqrstuvwk

71 return idir 1xyzABCDEFi

72 return None 1ahbcdefgmnopqrstuvwk

73 

74 

75# --------------------------------------------------------------------------- 

76# Find steps 

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

78 

79 

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

81 """Search pip wheel install locations.""" 

82 for sub_dir in desc.site_packages_dirs: 1laxhbycdefzABgCDEGHFimnopqrstuvwk

83 hdr_dir: str # help mypy 

84 for hdr_dir in find_sub_dirs_all_sitepackages(tuple(sub_dir.split("/"))): 1laxhbycdefzABgCDEGHFimnopqrstuvwk

85 if _joined_isfile(hdr_dir, desc.header_basename): 

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

87 return None 1laxhbycdefzABgCDEGHFimnopqrstuvwk

88 

89 

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

91 """Search ``$CONDA_PREFIX``.""" 

92 conda_prefix = os.environ.get("CONDA_PREFIX") 1laxhbycdefzABgCDEGHFimnopqrstuvwk

93 if not conda_prefix: 1laxhbycdefzABgCDEGHFimnopqrstuvwk

94 return None 1laxhbycdefzABgCDEGHFimnopqrstuvwk

95 anchor_point = resolve_conda_anchor(desc, conda_prefix) 

96 if anchor_point is None: 

97 return None 

98 found_header_path = _locate_in_anchor_layout(desc, anchor_point) 

99 if found_header_path: 

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

101 return None 

102 

103 

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

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

106 cuda_home = get_cuda_path_or_home() 1laxhbycdefzABgCDEGHFimnopqrstuvwk

107 if cuda_home is None: 1laxhbycdefzABgCDEGHFimnopqrstuvwk

108 return None 1GHi

109 result = _locate_in_anchor_layout(desc, cuda_home) 1laxhbycdefzABgCDEFmnopqrstuvwk

110 if result is not None: 1laxhbycdefzABgCDEFmnopqrstuvwk

111 return LocatedHeaderDir(abs_path=result, found_via="CUDA_PATH") 1lxyzABCDEF

112 return None 1ahbcdefgmnopqrstuvwk

113 

114 

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

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

117 

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

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

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

121 layout under that root. 

122 """ 

123 if not desc.use_ctk_root_canary: 1ahbcdefgGHimnopqrstuvwk

124 return None 1mnopqrstuvwk

125 canary_abs_path = _resolve_system_loaded_abs_path_in_subprocess(CTK_ROOT_CANARY_ANCHOR_LIBNAMES[0]) 1ahbcdefgGHi

126 if canary_abs_path is None: 1ahbcdefgGi

127 return None 1abcdefgG

128 ctk_root = derive_ctk_root(canary_abs_path) 1ahbcdefgi

129 if ctk_root is None: 1ahbcdefgi

130 return None 

131 result = _locate_in_anchor_layout(desc, ctk_root) 1ahbcdefgi

132 if result is not None: 1ahbcdefgi

133 return LocatedHeaderDir(abs_path=result, found_via="system-ctk-root") 1i

134 return None 1ahbcdefg

135 

136 

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

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

139 for pattern in desc.system_install_dirs: 1ahbcdefgGmnopqrstuvwk

140 for hdr_dir in sorted(glob.glob(pattern), reverse=True): 1k

141 if _joined_isfile(hdr_dir, desc.header_basename): 

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

143 return None 1ahbcdefgGmnopqrstuvwk

144 

145 

146# --------------------------------------------------------------------------- 

147# Step sequence and cascade runner 

148# --------------------------------------------------------------------------- 

149 

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

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

152 find_in_site_packages, 

153 find_in_conda, 

154 find_in_cuda_path, 

155 find_via_ctk_root_canary, 

156 find_in_system_install_dirs, 

157) 

158 

159 

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

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

162 for step in steps: 1laxhbycdefzABgCDEGHFimnopqrstuvwk

163 result = step(desc) 1laxhbycdefzABgCDEGHFimnopqrstuvwk

164 if result is not None: 1laxhbycdefzABgCDEGHFimnopqrstuvwk

165 return result 1lxyzABCDEFi

166 return None 1ahbcdefgGmnopqrstuvwk

167 

168 

169# --------------------------------------------------------------------------- 

170# Public API 

171# --------------------------------------------------------------------------- 

172 

173 

174@functools.cache 

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

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

177 

178 Args: 

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

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

181 

182 Returns: 

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

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

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

186 

187 Raises: 

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

189 

190 Search order: 

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

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

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

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

195 ``use_ctk_root_canary=True`` only). 

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

197 """ 

198 desc = HEADER_DESCRIPTORS.get(libname) 1laxhbycdefzABgCDEGHFimnopqrstuvwkI

199 if desc is None: 1laxhbycdefzABgCDEGHFimnopqrstuvwkI

200 raise RuntimeError(f"UNKNOWN {libname=}") 1I

201 return run_find_steps(desc, FIND_STEPS) 1laxhbycdefzABgCDEGHFimnopqrstuvwk

202 

203 

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

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

206 

207 Args: 

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

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

210 

211 Returns: 

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

213 if the headers cannot be found. 

214 

215 Raises: 

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

217 

218 Search order: 

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

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

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

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

223 ``use_ctk_root_canary=True`` only). 

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

225 """ 

226 found = locate_nvidia_header_directory(libname) 1laxhbycdefzABgCDEGHmnopqrstuvwkI

227 return found.abs_path if found else None 1laxhbycdefzABgCDEGmnopqrstuvwk