diff --git a/cuda_bindings/tests/cython/build_tests.py b/cuda_bindings/tests/cython/build_tests.py new file mode 100644 index 00000000000..53e66070634 --- /dev/null +++ b/cuda_bindings/tests/cython/build_tests.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE +"""Build cuda_bindings Cython test extensions in-place. + +pixi-build's editable install exposes the `cuda` namespace package via a +PEP 660 finder hook. Python's import machinery honors the hook, but +Cython's filesystem .pxd resolver only walks real directories on sys.path, +so `cimport cuda.bindings.*` fails to locate the .pxd files. We resolve +the namespace package's source root from `cuda.bindings.__file__` and pass +it via `include_path=` so cythonize finds the .pxd tree on every platform. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +from Cython.Build import cythonize +from setuptools import setup + +import cuda.bindings + + +def _bindings_source_root() -> Path: + # cuda.bindings.__file__ -> ...//cuda/bindings/__init__.py + root = Path(cuda.bindings.__file__).resolve().parents[2] + if not (root / "cuda" / "bindings").is_dir(): + raise RuntimeError( + f"cuda.bindings source tree not found at {root}; pixi-build editable install layout may have changed." + ) + return root + + +def main() -> None: + script_dir = Path(__file__).resolve().parent + pyx_files = sorted(str(p) for p in script_dir.glob("test_*.pyx")) + if not pyx_files: + raise SystemExit(f"no test_*.pyx files under {script_dir}") + + ext_modules = cythonize( + pyx_files, + language_level=3, + nthreads=1, + include_path=[str(_bindings_source_root())], + compiler_directives={"freethreading_compatible": True}, + ) + + sys.argv = [sys.argv[0], "build_ext", "--inplace"] + setup(name="cuda_bindings_cython_tests", ext_modules=ext_modules) + + +if __name__ == "__main__": + main() diff --git a/cuda_bindings/tests/cython/build_tests.sh b/cuda_bindings/tests/cython/build_tests.sh index c2ddc9ea79d..12cf46f7ffa 100755 --- a/cuda_bindings/tests/cython/build_tests.sh +++ b/cuda_bindings/tests/cython/build_tests.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -eo pipefail # SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE @@ -6,13 +7,17 @@ UNAME=$(uname) if [ "$UNAME" == "Linux" ] ; then SCRIPTPATH=$(dirname $(realpath "$0")) - export CPLUS_INCLUDE_PATH=$CUDA_HOME/include:$CPLUS_INCLUDE_PATH + export CPLUS_INCLUDE_PATH=$CUDA_HOME/include:${CPLUS_INCLUDE_PATH:-} elif [[ "$UNAME" == CYGWIN* || "$UNAME" == MINGW* || "$UNAME" == MSYS* ]] ; then SCRIPTPATH="$(dirname $(cygpath -w $(realpath "$0")))" - export CL="/I\"${CUDA_HOME}\\include\" ${CL}" + export CL="/I\"${CUDA_HOME}\\include\" ${CL:-}" else exit 1 fi -# Use -j 1 to side-step any process-pool issues and ensure deterministic single-threaded builds -cythonize -3 -j 1 -i -Xfreethreading_compatible=True ${SCRIPTPATH}/test_*.pyx +# Use a Python driver so the cuda.bindings source root is resolved at +# runtime and passed via Cython's include_path -- avoids platform-specific +# PYTHONPATH separator handling and surfaces import errors as exceptions. +# nthreads=1 inside the driver mirrors the previous `-j 1` to side-step +# any process-pool issues and keep builds deterministic. +python "${SCRIPTPATH}/build_tests.py" diff --git a/cuda_core/tests/cython/build_tests.py b/cuda_core/tests/cython/build_tests.py new file mode 100644 index 00000000000..af08ab25ea4 --- /dev/null +++ b/cuda_core/tests/cython/build_tests.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Build cuda_core Cython test extensions in-place. + +pixi-build's editable install exposes the `cuda` namespace package via a +PEP 660 finder hook. Python's import machinery honors the hook, but +Cython's filesystem .pxd resolver only walks real directories on sys.path, +so `cimport cuda.bindings.*` fails to locate the .pxd files. We resolve +the namespace package's source root from `cuda.bindings.__file__` and pass +it via `include_path=` so cythonize finds the .pxd tree on every platform. +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +from Cython.Build import cythonize +from setuptools import setup + +import cuda.bindings + + +def _bindings_source_root() -> Path: + # cuda.bindings.__file__ -> ...//cuda/bindings/__init__.py + root = Path(cuda.bindings.__file__).resolve().parents[2] + if not (root / "cuda" / "bindings").is_dir(): + raise RuntimeError( + f"cuda.bindings source tree not found at {root}; pixi-build editable install layout may have changed." + ) + return root + + +def main() -> None: + script_dir = Path(__file__).resolve().parent + pyx_files = sorted(str(p) for p in script_dir.glob("test_*.pyx")) + if not pyx_files: + raise SystemExit(f"no test_*.pyx files under {script_dir}") + + ext_modules = cythonize( + pyx_files, + language_level=3, + include_path=[str(_bindings_source_root())], + compiler_directives={"freethreading_compatible": True}, + ) + + sys.argv = [sys.argv[0], "build_ext", "--inplace"] + setup(name="cuda_core_cython_tests", ext_modules=ext_modules) + + +if __name__ == "__main__": + main() diff --git a/cuda_core/tests/cython/build_tests.sh b/cuda_core/tests/cython/build_tests.sh index 3e20136133a..7ee65c50d68 100755 --- a/cuda_core/tests/cython/build_tests.sh +++ b/cuda_core/tests/cython/build_tests.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -eo pipefail # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 @@ -6,13 +7,16 @@ UNAME=$(uname) if [ "$UNAME" == "Linux" ] ; then SCRIPTPATH=$(dirname $(realpath "$0")) - export CPLUS_INCLUDE_PATH=${SCRIPTPATH}/../../cuda/core/_include:$CUDA_HOME/include:$CPLUS_INCLUDE_PATH + export CPLUS_INCLUDE_PATH=${SCRIPTPATH}/../../cuda/core/_include:$CUDA_HOME/include:${CPLUS_INCLUDE_PATH:-} elif [[ "$UNAME" == CYGWIN* || "$UNAME" == MINGW* || "$UNAME" == MSYS* ]] ; then SCRIPTPATH="$(dirname $(cygpath -w $(realpath "$0")))" CUDA_CORE_INCLUDE_PATH=$(echo "${SCRIPTPATH}\..\..\cuda\core\_include" | sed 's/\\/\\\\/g') - export CL="/I\"${CUDA_CORE_INCLUDE_PATH}\" /I\"${CUDA_HOME}\\include\" ${CL}" + export CL="/I\"${CUDA_CORE_INCLUDE_PATH}\" /I\"${CUDA_HOME}\\include\" ${CL:-}" else exit 1 fi -cythonize -3 -i -Xfreethreading_compatible=True ${SCRIPTPATH}/test_*.pyx +# Use a Python driver so the cuda.bindings source root is resolved at +# runtime and passed via Cython's include_path -- avoids platform-specific +# PYTHONPATH separator handling and surfaces import errors as exceptions. +python "${SCRIPTPATH}/build_tests.py" diff --git a/pixi.toml b/pixi.toml index 71d6b5d91bc..f73d299e012 100644 --- a/pixi.toml +++ b/pixi.toml @@ -18,30 +18,40 @@ docs = { features = [], solve-group = "docs" } PIXI_ENVIRONMENT_NAME = "${PIXI_ENVIRONMENT_NAME/default/cu13}" # Test Tasks -# Runs tests across all sub-packages: pathfinder → bindings → core (dependency order) -# Each sub-package has its own pixi.toml; the -e environment propagates via PIXI_ENVIRONMENT_NAME +# Runs tests across all sub-packages: pathfinder → bindings → core (dependency order). +# +# Each sub-package has its own pixi.toml. We wrap each task in `bash -c '...'` +# (single-quoted in TOML, double-quoted inside) because pixi `cmd` arrays do +# not expand shell variables -- without this, the inner `pixi run` would drop +# into the sub-package's `default` environment, which lacks a cuda-version +# pin, causing the conda solver to pick a mismatched CUDA toolkit. Forwarding +# `-e "$PIXI_ENVIRONMENT_NAME"` keeps the active environment consistent end +# to end. +# +# Linux-only for now; Windows/macOS root test-task parity is tracked +# separately. # # Usage: pixi run test | pixi run -e cu12 test | pixi run -e cu13 test [target.linux.tasks.test-pathfinder] cmd = [ - "pixi", - "run", - "--manifest-path", - "$PIXI_PROJECT_ROOT/cuda_pathfinder", - "test", + "bash", + "-c", + 'pixi run --manifest-path "$PIXI_PROJECT_ROOT/cuda_pathfinder" -e "$PIXI_ENVIRONMENT_NAME" test', ] [target.linux.tasks.test-bindings] cmd = [ - "pixi", - "run", - "--manifest-path", - "$PIXI_PROJECT_ROOT/cuda_bindings", - "test", + "bash", + "-c", + 'pixi run --manifest-path "$PIXI_PROJECT_ROOT/cuda_bindings" -e "$PIXI_ENVIRONMENT_NAME" test', ] [target.linux.tasks.test-core] -cmd = ["pixi", "run", "--manifest-path", "$PIXI_PROJECT_ROOT/cuda_core", "test"] +cmd = [ + "bash", + "-c", + 'pixi run --manifest-path "$PIXI_PROJECT_ROOT/cuda_core" -e "$PIXI_ENVIRONMENT_NAME" test', +] [target.linux.tasks.test] depends-on = [