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
« 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
4from __future__ import annotations
6import functools
7import glob
8import os
9from collections.abc import Callable
10from dataclasses import dataclass
11from typing import TYPE_CHECKING
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
25if TYPE_CHECKING:
26 from cuda.pathfinder._headers.header_descriptor import HeaderDescriptor
28# ---------------------------------------------------------------------------
29# Data types
30# ---------------------------------------------------------------------------
33@dataclass
34class LocatedHeaderDir:
35 abs_path: str | None
36 found_via: str
38 def __post_init__(self) -> None:
39 self.abs_path = _abs_norm(self.abs_path) 1knopqrstuh
42#: Type alias for a header find step callable.
43HeaderFindStep = Callable[["HeaderDescriptor"], LocatedHeaderDir | None]
45# ---------------------------------------------------------------------------
46# Helpers
47# ---------------------------------------------------------------------------
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
56def _joined_isfile(dirpath: str, basename: str) -> bool:
57 return os.path.isfile(os.path.join(dirpath, basename)) 1kanbocdefpqrgstuhlmj
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
74# ---------------------------------------------------------------------------
75# Find steps
76# ---------------------------------------------------------------------------
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
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
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
114def find_via_ctk_root_canary(desc: HeaderDescriptor) -> LocatedHeaderDir | None:
115 """Try CTK header lookup via CTK-root canary probing.
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
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
145# ---------------------------------------------------------------------------
146# Step sequence and cascade runner
147# ---------------------------------------------------------------------------
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)
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
168# ---------------------------------------------------------------------------
169# Public API
170# ---------------------------------------------------------------------------
173@functools.cache
174def locate_nvidia_header_directory(libname: str) -> LocatedHeaderDir | None:
175 """Locate the header directory for a supported NVIDIA library.
177 Args:
178 libname (str): The short name of the library whose headers are needed
179 (e.g., ``"nvrtc"``, ``"cusolver"``, ``"nvshmem"``).
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.
186 Raises:
187 RuntimeError: If ``libname`` is not in the supported set.
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
203def find_nvidia_header_directory(libname: str) -> str | None:
204 """Locate the header directory for a supported NVIDIA library.
206 Args:
207 libname (str): The short name of the library whose headers are needed
208 (e.g., ``"nvrtc"``, ``"cusolver"``, ``"nvshmem"``).
210 Returns:
211 str or None: Absolute path to the discovered header directory, or ``None``
212 if the headers cannot be found.
214 Raises:
215 RuntimeError: If ``libname`` is not in the supported set.
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