Skip to content

Commit aa6cece

Browse files
rparolincpcloud
authored andcommitted
feat(binaries): find binaries using pathfinder
1 parent 4408aef commit aa6cece

5 files changed

Lines changed: 227 additions & 1 deletion

File tree

cuda_pathfinder/cuda/pathfinder/__init__.py

Lines changed: 10 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
@@ -25,6 +29,12 @@
2529
#: (e.g., ``"cufile"`` may be Linux-only).
2630
SUPPORTED_HEADERS_CTK = _SUPPORTED_HEADERS_CTK
2731

32+
#: Tuple of supported CUDA binary utility names that can be located
33+
#: via ``find_nvidia_binary_utility()``. Platform-aware (e.g., some
34+
#: utilities may be available only on Linux or Windows).
35+
#: Example utilities: ``"nvdisasm"``, ``"cuobjdump"``, ``"nvcc"``.
36+
SUPPORTED_BINARY_UTILITIES = _SUPPORTED_BINARIES
37+
2838
# Backward compatibility: _find_nvidia_header_directory was added in release 1.2.2.
2939
# It will be removed in release 1.2.4.
3040
_find_nvidia_header_directory = find_nvidia_header_directory
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import functools
5+
import os
6+
import shutil
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+
class UnsupportedBinaryError(Exception):
15+
def __init__(self, utility: str) -> None:
16+
super().__init__(utility)
17+
self.utility = utility
18+
19+
def __str__(self) -> str:
20+
supported_utilities = ", ".join(supported_nvidia_binaries.SUPPORTED_BINARIES)
21+
return f"Binary '{self.utility}' is not supported. Supported utilities are: {supported_utilities}"
22+
23+
24+
def _normalize_utility_name(utility_name: str) -> str:
25+
"""Normalize utility name by adding .exe on Windows if needed."""
26+
if IS_WINDOWS and not utility_name.lower().endswith((".exe", ".bat", ".cmd")):
27+
return f"{utility_name}.exe"
28+
return utility_name
29+
30+
31+
@functools.cache
32+
def find_nvidia_binary_utility(utility_name: str) -> str | None:
33+
"""Locate a CUDA binary utility executable.
34+
35+
Args:
36+
utility_name (str): The name of the binary utility to find
37+
(e.g., ``"nvdisasm"``, ``"cuobjdump"``). On Windows, the ``.exe``
38+
extension will be automatically appended if not present. The function
39+
also recognizes ``.bat`` and ``.cmd`` files on Windows.
40+
41+
Returns:
42+
str or None: Absolute path to the discovered executable, or ``None``
43+
if the utility cannot be found. The returned path is normalized
44+
(absolute and with resolved separators).
45+
46+
Raises:
47+
UnsupportedBinaryError: If ``utility_name`` is not in the supported set
48+
(see ``SUPPORTED_BINARY_UTILITIES``).
49+
50+
Search order:
51+
1. **NVIDIA Python wheels**
52+
53+
- Scan installed distributions (``site-packages``) for binary layouts
54+
shipped in NVIDIA wheels (e.g., ``cuda-nvcc``).
55+
56+
2. **Conda environments**
57+
58+
- Check Conda-style installation prefixes via ``CONDA_PREFIX``
59+
environment variable, which use platform-specific bin directory
60+
layouts (``Library/bin`` on Windows, ``bin`` on Linux).
61+
62+
3. **CUDA Toolkit environment variables**
63+
64+
- Use ``CUDA_HOME`` or ``CUDA_PATH`` (in that order), searching
65+
``bin/x64``, ``bin/x86_64``, and ``bin`` subdirectories on Windows,
66+
or just ``bin`` on Linux.
67+
68+
Note:
69+
Results are cached using ``@functools.cache`` for performance. The cache
70+
persists for the lifetime of the process.
71+
72+
On Windows, executables are identified by their file extensions
73+
(``.exe``, ``.bat``, ``.cmd``). On Unix-like systems, executables
74+
are identified by the ``X_OK`` (execute) permission bit.
75+
76+
Example:
77+
>>> from cuda.pathfinder import find_nvidia_binary_utility
78+
>>> nvdisasm = find_nvidia_binary_utility("nvdisasm")
79+
>>> if nvdisasm:
80+
... print(f"Found nvdisasm at: {nvdisasm}")
81+
"""
82+
if utility_name not in supported_nvidia_binaries.SUPPORTED_BINARIES:
83+
raise UnsupportedBinaryError(utility_name)
84+
85+
# 1. Search in site-packages (NVIDIA wheels)
86+
candidate_dirs = supported_nvidia_binaries.SITE_PACKAGES_BINDIRS.get(utility_name, ())
87+
dirs = []
88+
89+
for sub_dir in candidate_dirs:
90+
if bin_dir := find_sub_dirs_all_sitepackages(sub_dir.split(os.sep)):
91+
dirs.extend(bin_dir)
92+
93+
# 2. Search in Conda environment
94+
if (conda_prefix := os.environ.get("CONDA_PREFIX")) is not None:
95+
if IS_WINDOWS:
96+
dirs.append(os.path.join(conda_prefix, "Library", "bin"))
97+
dirs.append(os.path.join(conda_prefix, "bin"))
98+
99+
# 3. Search in CUDA Toolkit (CUDA_HOME/CUDA_PATH)
100+
if (cuda_home := get_cuda_home_or_path()) is not None:
101+
if IS_WINDOWS:
102+
dirs.append(os.path.join(cuda_home, "bin", "x64"))
103+
dirs.append(os.path.join(cuda_home, "bin", "x86_64"))
104+
dirs.append(os.path.join(cuda_home, "bin"))
105+
106+
normalized_name = _normalize_utility_name(utility_name)
107+
return shutil.which(normalized_name, path=os.pathsep.join(dirs))
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
5+
# Common CUDA binary utilities available on both Linux and Windows
6+
SUPPORTED_BINARIES_ALL = (
7+
# Core compilation tools
8+
"nvcc",
9+
"nvdisasm",
10+
"cuobjdump",
11+
"nvprune",
12+
"fatbinary",
13+
"bin2c",
14+
"nvlink",
15+
# Runtime/debugging tools
16+
"cuda-gdb",
17+
"cuda-gdbserver",
18+
"compute-sanitizer",
19+
# Profiling tools
20+
"nvprof",
21+
"nsys",
22+
"nsight-sys",
23+
"ncu",
24+
"nsight-compute",
25+
)
26+
27+
SUPPORTED_BINARIES = SUPPORTED_BINARIES_ALL
28+
29+
# Site-packages bin directories where binaries might be found
30+
# Based on NVIDIA wheel layouts (same for Linux and Windows)
31+
SITE_PACKAGES_BINDIRS = {
32+
"nvcc": ("nvidia/cuda_nvcc/bin",),
33+
"nvdisasm": ("nvidia/cuda_nvcc/bin",),
34+
"cuobjdump": ("nvidia/cuda_nvcc/bin",),
35+
"nvprune": ("nvidia/cuda_nvcc/bin",),
36+
"fatbinary": ("nvidia/cuda_nvcc/bin",),
37+
"bin2c": ("nvidia/cuda_nvcc/bin",),
38+
"nvlink": ("nvidia/cuda_nvcc/bin",),
39+
"cuda-gdb": ("nvidia/cuda_nvcc/bin",),
40+
"cuda-gdbserver": ("nvidia/cuda_nvcc/bin",),
41+
"compute-sanitizer": ("nvidia/cuda_nvcc/bin",),
42+
"nvprof": ("nvidia/cuda_nvcc/bin",),
43+
"nsys": ("nvidia/nsight_systems/bin",),
44+
"nsight-sys": ("nvidia/nsight_systems/bin",),
45+
"ncu": ("nvidia/nsight_compute/bin",),
46+
"nsight-compute": ("nvidia/nsight_compute/bin",),
47+
}

cuda_pathfinder/docs/source/api.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
=================================
88

99
The ``cuda.pathfinder`` module provides utilities for loading NVIDIA dynamic libraries,
10-
and experimental APIs for locating NVIDIA C/C++ header directories.
10+
locating NVIDIA C/C++ header directories, and finding CUDA binary utilities.
1111

1212
.. autosummary::
1313
:toctree: generated/
@@ -20,3 +20,6 @@ and experimental APIs for locating NVIDIA C/C++ header directories.
2020
SUPPORTED_HEADERS_CTK
2121
SUPPORTED_HEADERS_NON_CTK
2222
find_nvidia_header_directory
23+
24+
SUPPORTED_BINARY_UTILITIES
25+
find_nvidia_binary_utility
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import os
5+
6+
import pytest
7+
8+
from cuda.pathfinder import find_nvidia_binary_utility
9+
from cuda.pathfinder._binaries.find_nvidia_binary_utility import UnsupportedBinaryError
10+
from cuda.pathfinder._binaries.supported_nvidia_binaries import (
11+
SITE_PACKAGES_BINDIRS,
12+
SUPPORTED_BINARIES,
13+
SUPPORTED_BINARIES_ALL,
14+
)
15+
16+
17+
def test_unknown_utility_name():
18+
with pytest.raises(UnsupportedBinaryError, match=r"'unknown-utility' is not supported"):
19+
find_nvidia_binary_utility("unknown-utility")
20+
21+
22+
@pytest.mark.parametrize("utility_name", SUPPORTED_BINARIES)
23+
def test_find_binary_utilities(info_summary_append, utility_name):
24+
bin_path = find_nvidia_binary_utility(utility_name)
25+
info_summary_append(f"{bin_path=!r}")
26+
27+
if bin_path:
28+
assert os.path.isfile(bin_path), f"Path exists but is not a file: {bin_path}"
29+
# Note: We verify the file exists but don't check executability here because
30+
# permissions may vary in test environments (e.g., mounted filesystems, CI
31+
# containers). The _is_executable() check is tested separately in unit tests.
32+
33+
34+
def test_supported_binaries_consistency():
35+
# Ensure SUPPORTED_BINARIES is a subset of SUPPORTED_BINARIES_ALL
36+
assert set(SUPPORTED_BINARIES).issubset(SUPPORTED_BINARIES_ALL)
37+
38+
39+
def test_site_packages_bindirs_consistency():
40+
"""Verify SITE_PACKAGES_BINDIRS keys are in SUPPORTED_BINARIES_ALL."""
41+
42+
assert set(SITE_PACKAGES_BINDIRS).issubset(SUPPORTED_BINARIES_ALL)
43+
44+
45+
def test_caching_per_utility():
46+
"""Verify that different utilities have independent cache entries."""
47+
nvdisasm1 = find_nvidia_binary_utility("nvdisasm")
48+
nvcc1 = find_nvidia_binary_utility("nvcc")
49+
nvdisasm2 = find_nvidia_binary_utility("nvdisasm")
50+
nvcc2 = find_nvidia_binary_utility("nvcc")
51+
52+
# Same utility should return cached result
53+
assert nvdisasm1 is nvdisasm2
54+
assert nvcc1 is nvcc2
55+
56+
# Different utilities should have different results (unless at least one of
57+
# them is None)
58+
if nvdisasm1 is not None and nvcc1 is not None:
59+
assert nvdisasm1 != nvcc1

0 commit comments

Comments
 (0)