From 50c51d4a55a1f1fd203dc40e1b7bcfabb6e0a8a9 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Sat, 8 Aug 2026 17:59:25 -0700 Subject: [PATCH] Use pathlib in cuda.pathfinder._headers (part 8 of #2410) Converts the header search modules to pathlib.Path: - `_locate_in_anchor_layout` and `resolve_conda_anchor` now build and return `Path` objects; the anchor arguments accept `str` or any `os.PathLike[str]`. - `os.path.isfile` / `os.path.join` probes become `(dir / name).is_file()`, which lets the `_joined_isfile` helper go away. - `LocatedHeaderDir.abs_path` stays a `str` (it is public API), so the find steps convert at the boundary. Two deliberate exceptions: - `_abs_norm` keeps `os.path.abspath`. pathlib has no purely lexical normalization: `Path.resolve()` follows symlinks, which would change the reported header directory for the common `/usr/local/cuda -> /usr/local/cuda-` layout. `os.path.normpath` around `os.path.abspath` was redundant (posixpath/ntpath `abspath` already normalize), so that call is dropped. - `glob.glob` stays for the catalog's glob patterns; only the `os.path` parts of those expressions move to `Path`. Per the discussion on #2410, single-file `is_file()` probes are left with pathlib's error behavior rather than wrapped to mimic `os.path.isfile`'s "any stat error means False". No behavior change intended; the existing cuda_pathfinder test suite covers these paths (`tests/test_find_nvidia_headers.py`). --- .../_headers/find_nvidia_headers.py | 35 ++++++++++--------- .../pathfinder/_headers/header_descriptor.py | 15 ++++---- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py b/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py index f5f56817141..4fb203ffdb1 100644 --- a/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py +++ b/cuda_pathfinder/cuda/pathfinder/_headers/find_nvidia_headers.py @@ -8,6 +8,7 @@ import os from collections.abc import Callable from dataclasses import dataclass +from pathlib import Path from typing import TYPE_CHECKING from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import ( @@ -50,24 +51,24 @@ def __post_init__(self) -> None: def _abs_norm(path: str | None) -> str | None: if path: - return os.path.normpath(os.path.abspath(path)) + # os.path.abspath() is already normalizing (it is normpath(join(cwd, path))) + # and, unlike Path.resolve(), it does not follow symlinks. pathlib has no + # purely lexical equivalent, so os.path stays in this one spot. + return os.path.abspath(path) return None -def _joined_isfile(dirpath: str, basename: str) -> bool: - return os.path.isfile(os.path.join(dirpath, basename)) - - -def _locate_in_anchor_layout(desc: HeaderDescriptor, anchor_point: str) -> str | None: +def _locate_in_anchor_layout(desc: HeaderDescriptor, anchor_point: str | os.PathLike[str]) -> Path | None: """Search for a header under *anchor_point* using the descriptor's layout fields.""" h_basename = desc.header_basename + cdir: Path # help mypy for rel_dir in desc.anchor_include_rel_dirs: - idir = os.path.join(anchor_point, rel_dir) + idir = Path(anchor_point, rel_dir) for subdir in platform_include_subdirs(desc): - cdir = os.path.join(idir, subdir) - if _joined_isfile(cdir, h_basename): + cdir = idir / subdir + if (cdir / h_basename).is_file(): return cdir - if _joined_isfile(idir, h_basename): + if (idir / h_basename).is_file(): return idir return None @@ -82,7 +83,7 @@ def find_in_site_packages(desc: HeaderDescriptor) -> LocatedHeaderDir | None: for sub_dir in desc.site_packages_dirs: hdr_dir: str # help mypy for hdr_dir in find_sub_dirs_all_sitepackages(tuple(sub_dir.split("/"))): - if _joined_isfile(hdr_dir, desc.header_basename): + if (Path(hdr_dir) / desc.header_basename).is_file(): return LocatedHeaderDir(abs_path=hdr_dir, found_via="site-packages") return None @@ -95,9 +96,9 @@ def find_in_conda(desc: HeaderDescriptor) -> LocatedHeaderDir | None: anchor_point = resolve_conda_anchor(desc, conda_prefix) if anchor_point is None: return None - found_header_path = _locate_in_anchor_layout(desc, anchor_point) - if found_header_path: - return LocatedHeaderDir(abs_path=found_header_path, found_via="conda") + found_header_dir = _locate_in_anchor_layout(desc, anchor_point) + if found_header_dir is not None: + return LocatedHeaderDir(abs_path=str(found_header_dir), found_via="conda") return None @@ -108,7 +109,7 @@ def find_in_cuda_path(desc: HeaderDescriptor) -> LocatedHeaderDir | None: return None result = _locate_in_anchor_layout(desc, cuda_home) if result is not None: - return LocatedHeaderDir(abs_path=result, found_via="CUDA_PATH") + return LocatedHeaderDir(abs_path=str(result), found_via="CUDA_PATH") return None @@ -130,7 +131,7 @@ def find_via_ctk_root_canary(desc: HeaderDescriptor) -> LocatedHeaderDir | None: return None result = _locate_in_anchor_layout(desc, ctk_root) if result is not None: - return LocatedHeaderDir(abs_path=result, found_via="system-ctk-root") + return LocatedHeaderDir(abs_path=str(result), found_via="system-ctk-root") return None @@ -138,7 +139,7 @@ def find_in_system_install_dirs(desc: HeaderDescriptor) -> LocatedHeaderDir | No """Search system install directories (glob patterns).""" for pattern in desc.system_install_dirs: for hdr_dir in sorted(glob.glob(pattern), reverse=True): - if _joined_isfile(hdr_dir, desc.header_basename): + if (Path(hdr_dir) / desc.header_basename).is_file(): return LocatedHeaderDir(abs_path=hdr_dir, found_via="supported_install_dir") return None diff --git a/cuda_pathfinder/cuda/pathfinder/_headers/header_descriptor.py b/cuda_pathfinder/cuda/pathfinder/_headers/header_descriptor.py index 609dcab7184..276e0c5ade9 100644 --- a/cuda_pathfinder/cuda/pathfinder/_headers/header_descriptor.py +++ b/cuda_pathfinder/cuda/pathfinder/_headers/header_descriptor.py @@ -12,6 +12,7 @@ import glob import os +from pathlib import Path from typing import TypeAlias, cast from cuda.pathfinder._headers.header_descriptor_catalog import ( @@ -37,18 +38,18 @@ def platform_include_subdirs(desc: HeaderDescriptor) -> tuple[str, ...]: return cast(tuple[str, ...], desc.include_subdirs) -def resolve_conda_anchor(desc: HeaderDescriptor, conda_prefix: str) -> str | None: +def resolve_conda_anchor(desc: HeaderDescriptor, conda_prefix: str | os.PathLike[str]) -> Path | None: """Resolve the conda anchor point for header search on the current platform. Returns the directory that ``_locate_in_anchor_layout`` should use as *anchor_point*, or ``None`` if the conda layout is not usable. """ if IS_WINDOWS: - anchor = os.path.join(conda_prefix, "Library") - return anchor if os.path.isdir(anchor) else None + anchor = Path(conda_prefix, "Library") + return anchor if anchor.is_dir() else None if desc.conda_targets_layout: - targets_include_path = glob.glob(os.path.join(conda_prefix, "targets", "*", "include")) - if not targets_include_path or len(targets_include_path) != 1: + targets_include_paths = glob.glob(str(Path(conda_prefix, "targets", "*", "include"))) + if len(targets_include_paths) != 1: return None - return os.path.dirname(targets_include_path[0]) - return conda_prefix + return Path(targets_include_paths[0]).parent + return Path(conda_prefix)