Skip to content

Commit a74ec8a

Browse files
committed
initial commit
1 parent 27e9066 commit a74ec8a

7 files changed

Lines changed: 337 additions & 31 deletions

File tree

cuda_bindings/pixi.lock

Lines changed: 18 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cuda_core/pixi.lock

Lines changed: 18 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cuda_pathfinder/cuda/pathfinder/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
"""cuda.pathfinder public APIs"""
55

6+
from cuda.pathfinder._binaries.find_nvidia_binary_utility import (
7+
find_nvidia_binary_utility as find_nvidia_binary_utility,
8+
)
9+
from cuda.pathfinder._binaries.supported_nvidia_binaries import SUPPORTED_BINARIES as _SUPPORTED_BINARIES
610
from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError as DynamicLibNotFoundError
711
from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL as LoadedDL
812
from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import load_nvidia_dynamic_lib as load_nvidia_dynamic_lib
@@ -11,6 +15,7 @@
1115
)
1216
from cuda.pathfinder._headers.find_nvidia_headers import find_nvidia_header_directory as find_nvidia_header_directory
1317
from cuda.pathfinder._headers.supported_nvidia_headers import SUPPORTED_HEADERS_CTK as _SUPPORTED_HEADERS_CTK
18+
from cuda.pathfinder._headers.supported_nvidia_headers import SUPPORTED_HEADERS_NON_CTK as _SUPPORTED_HEADERS_NON_CTK
1419

1520
from cuda.pathfinder._version import __version__ # isort: skip # noqa: F401
1621

@@ -21,6 +26,16 @@
2126
#: (e.g., ``"cufile"`` may be Linux-only).
2227
SUPPORTED_HEADERS_CTK = _SUPPORTED_HEADERS_CTK
2328

29+
#: Mapping from non-CTK library names to their canonical header basenames.
30+
#: Example: ``"cutensor" → "cutensor.h"``. Platform-aware.
31+
SUPPORTED_HEADERS_NON_CTK = _SUPPORTED_HEADERS_NON_CTK
32+
33+
#: Tuple of supported CUDA binary utility names that can be located
34+
#: via ``find_nvidia_binary_utility()``. Platform-aware (e.g., some
35+
#: utilities may be available only on Linux or Windows).
36+
#: Example utilities: ``"nvdisasm"``, ``"cuobjdump"``, ``"nvcc"``.
37+
SUPPORTED_BINARY_UTILITIES = _SUPPORTED_BINARIES
38+
2439
# Backward compatibility: _find_nvidia_header_directory was added in release 1.2.2.
2540
# It will be removed in release 1.2.4.
2641
_find_nvidia_header_directory = find_nvidia_header_directory
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import functools
5+
import glob
6+
import os
7+
8+
from cuda.pathfinder._binaries import supported_nvidia_binaries
9+
from cuda.pathfinder._utils.env_vars import get_cuda_home_or_path
10+
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_all_sitepackages
11+
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS
12+
13+
14+
def _abs_norm(path: str | None) -> str | None:
15+
if path:
16+
return os.path.normpath(os.path.abspath(path))
17+
return None
18+
19+
20+
def _is_executable(filepath: str) -> bool:
21+
"""Check if a file exists and is executable."""
22+
if not os.path.isfile(filepath):
23+
return False
24+
if IS_WINDOWS:
25+
# On Windows, any file can be executed; check extension
26+
return filepath.lower().endswith((".exe", ".bat", ".cmd"))
27+
else:
28+
# On Unix, check execute permission
29+
return os.access(filepath, os.X_OK)
30+
31+
32+
def _normalize_utility_name(utility_name: str) -> str:
33+
"""Normalize utility name by adding .exe on Windows if needed."""
34+
if IS_WINDOWS and not utility_name.lower().endswith((".exe", ".bat", ".cmd")):
35+
return utility_name + ".exe"
36+
return utility_name
37+
38+
39+
def _find_under_site_packages(sub_dir: str, utility_name: str) -> str | None:
40+
"""Search for binary in site-packages subdirectories."""
41+
bin_path: str
42+
normalized_name = _normalize_utility_name(utility_name)
43+
for bin_dir in find_sub_dirs_all_sitepackages(tuple(sub_dir.split("/"))):
44+
bin_path = os.path.join(bin_dir, normalized_name)
45+
if _is_executable(bin_path):
46+
return bin_path
47+
return None
48+
49+
50+
def _find_based_on_cuda_toolkit_layout(utility_name: str, anchor_point: str) -> str | None:
51+
"""Search in CUDA Toolkit style bin directories."""
52+
normalized_name = _normalize_utility_name(utility_name)
53+
54+
if IS_WINDOWS:
55+
# Windows: try bin/, bin/x64/, bin/x86_64/
56+
rel_paths = ["bin/x64", "bin/x86_64", "bin"]
57+
else:
58+
# Linux: just bin/
59+
rel_paths = ["bin"]
60+
61+
for rel_path in rel_paths:
62+
for bin_dir in sorted(glob.glob(os.path.join(anchor_point, rel_path))):
63+
if not os.path.isdir(bin_dir):
64+
continue
65+
bin_path = os.path.join(bin_dir, normalized_name)
66+
if _is_executable(bin_path):
67+
return bin_path
68+
69+
return None
70+
71+
72+
def _find_based_on_conda_layout(utility_name: str) -> str | None:
73+
"""Search in Conda environment bin directories."""
74+
conda_prefix = os.environ.get("CONDA_PREFIX")
75+
if not conda_prefix:
76+
return None
77+
78+
if IS_WINDOWS:
79+
anchor_points = [
80+
os.path.join(conda_prefix, "Library"),
81+
conda_prefix,
82+
]
83+
else:
84+
anchor_points = [conda_prefix]
85+
86+
for anchor_point in anchor_points:
87+
if not os.path.isdir(anchor_point):
88+
continue
89+
if result := _find_based_on_cuda_toolkit_layout(utility_name, anchor_point):
90+
return result
91+
92+
return None
93+
94+
95+
def _find_using_cuda_home(utility_name: str) -> str | None:
96+
"""Search using CUDA_HOME or CUDA_PATH environment variables."""
97+
cuda_home = get_cuda_home_or_path()
98+
if cuda_home is None:
99+
return None
100+
return _find_based_on_cuda_toolkit_layout(utility_name, cuda_home)
101+
102+
103+
def _find_binary_utility(utility_name: str) -> str | None:
104+
"""Core search logic for finding a binary utility."""
105+
# 1. Search in site-packages (NVIDIA wheels)
106+
candidate_dirs = supported_nvidia_binaries.SITE_PACKAGES_BINDIRS.get(utility_name, ())
107+
for cdir in candidate_dirs:
108+
if bin_path := _find_under_site_packages(cdir, utility_name):
109+
return _abs_norm(bin_path)
110+
111+
# 2. Search in Conda environment
112+
if bin_path := _find_based_on_conda_layout(utility_name):
113+
return _abs_norm(bin_path)
114+
115+
# 3. Search in CUDA Toolkit (CUDA_HOME/CUDA_PATH)
116+
if bin_path := _find_using_cuda_home(utility_name):
117+
return _abs_norm(bin_path)
118+
119+
return None
120+
121+
122+
@functools.cache
123+
def find_nvidia_binary_utility(utility_name: str) -> str | None:
124+
"""Locate a CUDA binary utility executable.
125+
126+
Args:
127+
utility_name (str): The name of the binary utility to find
128+
(e.g., ``"nvdisasm"``, ``"cuobjdump"``).
129+
130+
Returns:
131+
str or None: Absolute path to the discovered executable, or ``None``
132+
if the utility cannot be found.
133+
134+
Raises:
135+
RuntimeError: If ``utility_name`` is not in the supported set.
136+
137+
Search order:
138+
1. **NVIDIA Python wheels**
139+
140+
- Scan installed distributions (``site-packages``) for binary layouts
141+
shipped in NVIDIA wheels (e.g., ``cuda-nvcc``).
142+
143+
2. **Conda environments**
144+
145+
- Check Conda-style installation prefixes, which use platform-specific
146+
bin directory layouts.
147+
148+
3. **CUDA Toolkit environment variables**
149+
150+
- Use ``CUDA_HOME`` or ``CUDA_PATH`` (in that order).
151+
152+
Example:
153+
>>> from cuda.pathfinder import find_nvidia_binary_utility
154+
>>> nvdisasm = find_nvidia_binary_utility("nvdisasm")
155+
>>> if nvdisasm:
156+
... print(f"Found nvdisasm at: {nvdisasm}")
157+
"""
158+
if utility_name not in supported_nvidia_binaries.SUPPORTED_BINARIES:
159+
raise RuntimeError(f"UNKNOWN {utility_name=}")
160+
161+
return _find_binary_utility(utility_name)

0 commit comments

Comments
 (0)