Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import functools
import os
from pathlib import Path

from cuda.pathfinder._binaries import supported_nvidia_binaries
from cuda.pathfinder._utils.ctk_root_canary import CTK_ROOT_CANARY_ANCHOR_LIBNAMES
Expand All @@ -28,22 +29,23 @@ def _normalize_utility_name(utility_name: str) -> str:
return utility_name


def _is_executable_candidate(path: str) -> bool:
if not os.path.isfile(path):
def _is_executable_candidate(path: Path) -> bool:
if not path.is_file():
return False
if IS_WINDOWS:
return True
# pathlib has no access() equivalent.
return os.access(path, os.X_OK)


def _ctk_bin_subdirs(root: str) -> list[str]:
def _ctk_bin_subdirs(root: Path) -> list[Path]:
if IS_WINDOWS:
return [
os.path.join(root, "bin", "x64"),
os.path.join(root, "bin", "x86_64"),
os.path.join(root, "bin"),
root / "bin" / "x64",
root / "bin" / "x86_64",
root / "bin",
]
return [os.path.join(root, "bin")]
return [root / "bin"]


def _resolve_ctk_root_via_canary() -> str | None:
Expand All @@ -53,19 +55,22 @@ def _resolve_ctk_root_via_canary() -> str | None:
return ctk_root


def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[str]) -> str | None:
def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[Path]) -> Path | None:
"""Resolve ``normalized_name`` against ``dirs`` in order."""
seen: set[str] = set()
seen: set[Path] = set()
for directory in dirs:
if directory in seen:
continue
assert directory
# Path("") is Path("."), which would silently search the CWD (#2119).
assert directory != Path()
seen.add(directory)
candidate = os.path.join(directory, normalized_name)
candidate = directory / normalized_name
if _is_executable_candidate(candidate):
# Return an absolute path, as the docstring promises (a relative
# search dir would otherwise leak a relative result).
return os.path.abspath(candidate)
# search dir would otherwise leak a relative result). os.path.abspath
# has no pathlib equivalent: Path.absolute() does not normalize and
# Path.resolve() would also follow symlinks.
return Path(os.path.abspath(candidate))
return None


Expand Down Expand Up @@ -134,29 +139,28 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None:

# 1. Search in site-packages (NVIDIA wheels)
candidate_dirs = supported_nvidia_binaries.SITE_PACKAGES_BINDIRS.get(utility_name, ())
dirs = []
dirs: list[Path] = []

for sub_dir in candidate_dirs:
dirs.extend(find_sub_dirs_all_sitepackages(sub_dir.split(os.sep)))
dirs.extend(Path(abs_dir) for abs_dir in find_sub_dirs_all_sitepackages(sub_dir))

# 2. Search in Conda environment
if (conda_prefix := os.environ.get("CONDA_PREFIX")) is not None:
if IS_WINDOWS:
dirs.append(os.path.join(conda_prefix, "Library", "bin"))
else:
dirs.append(os.path.join(conda_prefix, "bin"))
conda_root = Path(conda_prefix)
dirs.append(conda_root / "Library" / "bin" if IS_WINDOWS else conda_root / "bin")

# 3. Search in CUDA Toolkit (CUDA_HOME/CUDA_PATH)
if (cuda_home := get_cuda_path_or_home()) is not None:
dirs.extend(_ctk_bin_subdirs(cuda_home))
dirs.extend(_ctk_bin_subdirs(Path(cuda_home)))

normalized_name = _normalize_utility_name(utility_name)
found = _resolve_in_trusted_dirs(normalized_name, dirs)
if found is not None:
return found
return str(found)

# 4. CTK-root canary fallback.
ctk_root = _resolve_ctk_root_via_canary()
if ctk_root is not None:
return _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(ctk_root))
found = _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(Path(ctk_root)))
return None if found is None else str(found)
return None
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
import os

# Site-packages bin directories where binaries might be found
# Based on NVIDIA wheel layouts (same for Linux and Windows)
_CUDA_NVCC_BIN = os.path.join("nvidia", "cuda_nvcc", "bin")
_CUDA13_BIN = os.path.join("nvidia", "cu13", "bin")
_NSIGHT_SYSTEMS_BIN = os.path.join("nvidia", "nsight_systems", "bin")
_NSIGHT_COMPUTE_BIN = os.path.join("nvidia", "nsight_compute", "bin")
# Based on NVIDIA wheel layouts (same for Linux and Windows).
# Path components, because that is what find_sub_dirs_all_sitepackages takes.
_CUDA_NVCC_BIN = ("nvidia", "cuda_nvcc", "bin")
_CUDA13_BIN = ("nvidia", "cu13", "bin")
_NSIGHT_SYSTEMS_BIN = ("nvidia", "nsight_systems", "bin")
_NSIGHT_COMPUTE_BIN = ("nvidia", "nsight_compute", "bin")

# Common CUDA binary utilities available on both Linux and Windows
SITE_PACKAGES_BINDIRS = {
Expand Down
9 changes: 7 additions & 2 deletions cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import functools
import os
import warnings
from pathlib import Path

_CUDA_PATH_ENV_VARS_ORDERED = ("CUDA_PATH", "CUDA_HOME")

Expand All @@ -36,15 +37,19 @@ def _paths_differ(a: str, b: str) -> bool:
2) If still different AND both exist, use os.path.samefile to resolve symlinks/junctions.
3) Otherwise (nonexistent paths or samefile unavailable), treat as different.
"""
# normcase/normpath have no pathlib equivalent: PurePath does not collapse
# "..", Path.resolve() would also follow symlinks, and comparing PurePath
# objects would only case-fold on Windows.
norm_a = os.path.normcase(os.path.normpath(a))
norm_b = os.path.normcase(os.path.normpath(b))
if norm_a == norm_b:
return False

path_a, path_b = Path(a), Path(b)
try:
if os.path.exists(a) and os.path.exists(b):
if path_a.exists() and path_b.exists():
# samefile raises on non-existent paths; only call when both exist.
return not os.path.samefile(a, b)
return not path_a.samefile(path_b)
except OSError:
# Fall through to "different" if samefile isn't applicable/available.
pass
Expand Down
35 changes: 25 additions & 10 deletions cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,51 @@
# SPDX-License-Identifier: Apache-2.0

import functools
import os
import site
import sys
from collections.abc import Sequence
from pathlib import Path


def _is_dir(path: Path) -> bool:
"""``path.is_dir()``, but False instead of raising on an inaccessible path.

This walks directories nobody here controls, so it has to tolerate whatever
it runs into. Path.is_dir() only swallows the errnos in pathlib's ignore
list, and raises for the rest (EACCES, ENAMETOOLONG); os.path.isdir, which
this replaces, returned False for all of them.
"""
try:
return path.is_dir()
except OSError:
return False


def find_sub_dirs_no_cache(parent_dirs: Sequence[str], sub_dirs: Sequence[str]) -> list[str]:
# Results stay str: they are consumed by _binaries, _dynamic_libs, _headers
# and _static_libs, so the type flip belongs in its own change.
results = []
for base in parent_dirs:
stack = [(base, 0)] # (current_path, index into sub_dirs)
stack = [(Path(base), 0)] # (current_path, index into sub_dirs)
while stack:
current_path, idx = stack.pop()
if idx == len(sub_dirs):
if os.path.isdir(current_path):
results.append(current_path)
if _is_dir(current_path):
results.append(str(current_path))
continue

sub = sub_dirs[idx]
if sub == "*":
try:
entries = sorted(os.listdir(current_path))
entries = sorted(current_path.iterdir(), key=lambda entry: entry.name)
except OSError:
continue
for entry in entries:
entry_path = os.path.join(current_path, entry)
if os.path.isdir(entry_path):
for entry_path in entries:
if _is_dir(entry_path):
stack.append((entry_path, idx + 1))
else:
next_path = os.path.join(current_path, sub)
if os.path.isdir(next_path):
next_path = current_path / sub
if _is_dir(next_path):
stack.append((next_path, idx + 1))
return results

Expand Down
Loading
Loading