From 5d7816e241d674cf7d05fcee77c3cf548d4a7130 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Tue, 4 Aug 2026 15:30:22 -0700 Subject: [PATCH] Use pathlib in cuda.bindings build hooks and tests Part of NVIDIA#2410. Replaces os.path/os.sep with pathlib.Path in cuda_bindings/build_hooks.py and in the test modules that build paths to the test data, the examples directory and the reproducer cwd. Directory listings use Path.glob instead of glob.glob over a stringified pattern, so _rename_architecture_specific_files() now returns Path objects and its consumers use .name / .suffix. Values handed to setuptools' Extension and to the cuFile string parameters stay str; only the path construction moves to pathlib. _prep_extensions() keeps glob.glob because its input is a glob pattern rather than a directory. --- cuda_bindings/build_hooks.py | 36 +++++++++++++++------------- cuda_bindings/tests/test_cuda.py | 4 ++-- cuda_bindings/tests/test_cufile.py | 13 +++++----- cuda_bindings/tests/test_examples.py | 10 ++++---- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/cuda_bindings/build_hooks.py b/cuda_bindings/build_hooks.py index a50133f9777..74f4622a10a 100644 --- a/cuda_bindings/build_hooks.py +++ b/cuda_bindings/build_hooks.py @@ -16,6 +16,7 @@ import sys import sysconfig import tempfile +from pathlib import Path from warnings import warn from setuptools import build_meta as _build_meta @@ -50,9 +51,9 @@ def _import_get_cuda_path_or_home(): cuda = None for p in sys.path: - sp_cuda = os.path.join(p, "cuda") - if os.path.isdir(os.path.join(sp_cuda, "pathfinder")): - cuda.__path__ = list(cuda.__path__) + [sp_cuda] + sp_cuda = Path(p, "cuda") + if (sp_cuda / "pathfinder").is_dir(): + cuda.__path__ = list(cuda.__path__) + [str(sp_cuda)] break else: raise ModuleNotFoundError( @@ -79,11 +80,11 @@ def _get_cuda_path() -> str: def _rename_architecture_specific_files(): - path = os.path.join("cuda", "bindings", "_internal") + path = Path("cuda", "bindings", "_internal") if sys.platform == "linux": - src_files = glob.glob(os.path.join(path, "*_linux.pyx")) + src_files = path.glob("*_linux.pyx") elif sys.platform == "win32": - src_files = glob.glob(os.path.join(path, "*_windows.pyx")) + src_files = path.glob("*_windows.pyx") else: raise RuntimeError(f"platform is unrecognized: {sys.platform}") dst_files = [] @@ -91,19 +92,22 @@ def _rename_architecture_specific_files(): with tempfile.NamedTemporaryFile(delete=False, dir=".") as f: shutil.copy2(src, f.name) f_name = f.name - dst = src.replace("_linux", "").replace("_windows", "") - os.replace(f_name, f"./{dst}") + dst = src.with_name(src.name.replace("_linux", "").replace("_windows", "")) + os.replace(f_name, dst) dst_files.append(dst) return dst_files def _prep_extensions(sources, libraries, include_dirs, library_dirs, extra_compile_args, extra_link_args): - pattern = sources[0] + # sources[0] is a glob pattern (or a concrete file), not a directory, so + # glob.glob() is still the right tool; str() covers the Path entries that + # _rename_architecture_specific_files() produces. + pattern = str(sources[0]) files = glob.glob(pattern) libraries = libraries if libraries else [] exts = [] for pyx in files: - mod_name = pyx.replace(".pyx", "").replace(os.sep, ".").replace("/", ".") + mod_name = ".".join(Path(pyx).with_suffix("").parts) exts.append( Extension( mod_name, @@ -149,16 +153,16 @@ def _build_cuda_bindings(debug=False): compile_for_coverage = bool(int(os.environ.get("CUDA_PYTHON_COVERAGE", "0"))) # Prepare compile/link arguments - include_path_list = [os.path.join(cuda_path, "include")] + include_path_list = [str(Path(cuda_path, "include"))] include_dirs = [ - os.path.dirname(sysconfig.get_path("include")), + str(Path(sysconfig.get_path("include")).parent), ] + include_path_list - library_dirs = [sysconfig.get_path("platlib"), os.path.join(os.sys.prefix, "lib")] + library_dirs = [sysconfig.get_path("platlib"), str(Path(os.sys.prefix, "lib"))] if sys.platform == "win32": cudalib_subdirs = [r"lib\arm64"] if sysconfig.get_platform() == "win-arm64" else [r"lib\x64"] else: cudalib_subdirs = ["lib64", "lib"] - library_dirs.extend(os.path.join(cuda_path, subdir) for subdir in cudalib_subdirs) + library_dirs.extend(str(Path(cuda_path, subdir)) for subdir in cudalib_subdirs) extra_compile_args = [] extra_link_args = [] @@ -201,7 +205,7 @@ def _cleanup_dst_files(): cuda_bindings_files = [f for f in cuda_bindings_files if "cufile" not in f] def get_static_libraries(f): - if os.path.basename(f) in ("runtime.pyx", "runtime_ptds.pyx"): + if f.name in ("runtime.pyx", "runtime_ptds.pyx"): if sys.platform == "linux": return ["cudart_static", "rt"] else: @@ -215,7 +219,7 @@ def get_static_libraries(f): *(([f], None) for f in cuda_bindings_files), # internal files used by generated bindings (["cuda/bindings/_internal/utils.pyx"], None), - *(([f], get_static_libraries(f)) for f in dst_files if f.endswith(".pyx")), + *(([f], get_static_libraries(f)) for f in dst_files if f.suffix == ".pyx"), ] for sources, libraries in sources_list: diff --git a/cuda_bindings/tests/test_cuda.py b/cuda_bindings/tests/test_cuda.py index e2751df9237..5fea9bf207d 100644 --- a/cuda_bindings/tests/test_cuda.py +++ b/cuda_bindings/tests/test_cuda.py @@ -2,11 +2,11 @@ # SPDX-License-Identifier: Apache-2.0 import ctypes -import os.path import shutil import subprocess import sys import textwrap +from pathlib import Path import numpy as np import pytest @@ -1294,7 +1294,7 @@ def test_array_setter_no_double_free_after_clearing_with_empty_list(): params.attrs = [cuda.CUlaunchAttribute() for _ in range(8)] """ ) - proc = subprocess.run([sys.executable, "-c", code], capture_output=True, cwd=os.path.dirname(__file__)) # noqa: S603 + proc = subprocess.run([sys.executable, "-c", code], capture_output=True, cwd=Path(__file__).parent) # noqa: S603 assert proc.returncode == 0, ( f"reproducer subprocess exited with code {proc.returncode}; stderr: {proc.stderr.decode(errors='replace')}" ) diff --git a/cuda_bindings/tests/test_cufile.py b/cuda_bindings/tests/test_cufile.py index 46bd8429a62..55ac3de1ffb 100644 --- a/cuda_bindings/tests/test_cufile.py +++ b/cuda_bindings/tests/test_cufile.py @@ -41,10 +41,9 @@ def _cufile_driver_session(): def cufile_env_json(monkeypatch): """Set CUFILE_ENV_PATH_JSON environment variable for async tests.""" # Get absolute path to cufile.json in the same directory as this test file - test_dir = os.path.dirname(os.path.abspath(__file__)) - config_path = os.path.join(test_dir, "cufile.json") - assert os.path.isfile(config_path) - monkeypatch.setenv("CUFILE_ENV_PATH_JSON", config_path) + config_path = pathlib.Path(__file__).resolve().parent / "cufile.json" + assert config_path.is_file() + monkeypatch.setenv("CUFILE_ENV_PATH_JSON", str(config_path)) logging.info(f"Using cuFile config: {config_path}") @@ -1452,7 +1451,7 @@ def test_param(param, val): @pytest.mark.usefixtures("ctx", "cufile_env_json") def test_set_get_parameter_string(tmp_path): """Test setting and getting string parameters with cuFile validation.""" - temp_dir = tempfile.gettempdir() + temp_dir = pathlib.Path(tempfile.gettempdir()) # must be set to avoid getter error when testing ENV_LOGFILE_PATH... os.environ["CUFILE_LOGFILE_PATH"] = "" @@ -1460,12 +1459,12 @@ def test_set_get_parameter_string(tmp_path): (cufile.StringConfigParameter.LOGGING_LEVEL, "INFO", "DEBUG"), # Test logging level ( cufile.StringConfigParameter.ENV_LOGFILE_PATH, - os.path.join(temp_dir, "cufile.log"), + str(temp_dir / "cufile.log"), str(tmp_path / "cufile.log"), ), # Test environment log file path ( cufile.StringConfigParameter.LOG_DIR, - os.path.join(temp_dir, "cufile_logs"), + str(temp_dir / "cufile_logs"), str(tmp_path), ), # Test log directory ) diff --git a/cuda_bindings/tests/test_examples.py b/cuda_bindings/tests/test_examples.py index 652515830f8..0c6a4d41ca2 100644 --- a/cuda_bindings/tests/test_examples.py +++ b/cuda_bindings/tests/test_examples.py @@ -1,19 +1,21 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -import glob import os import subprocess import sys +from pathlib import Path import pytest from cuda_python_test_helpers.pep723 import has_package_requirements_or_skip -examples_path = os.path.join(os.path.dirname(__file__), "..", "examples") -examples_files = glob.glob(os.path.join(examples_path, "**/*.py"), recursive=True) +examples_path = Path(__file__).parents[1] / "examples" +examples_files = list(examples_path.glob("**/*.py")) -@pytest.mark.parametrize("example", examples_files) +# ``ids=str`` keeps the test IDs as the example's path, the way they read when +# the parameters were plain strings. +@pytest.mark.parametrize("example", examples_files, ids=str) def test_example(example): has_package_requirements_or_skip(example)