Skip to content

Commit ee5aff5

Browse files
rparolinclaude
andcommitted
fix(test): use Cython include_path wrapper for editable installs
Replace the PYTHONPATH shim in cuda_core/tests/cython/build_tests.sh with a small Python driver (build_tests.py) that calls Cython.Build.cythonize() with an explicit include_path resolved at runtime from cuda.bindings.__file__. Avoids platform-specific PYTHONPATH separator handling and surfaces missing-import failures as Python exceptions instead of silent fallbacks. Apply the same wrapper pattern to cuda_bindings/tests/cython/ for symmetry; both shell scripts gain `set -eo pipefail` and `${VAR:-}` defaults so the previously optional CPLUS_INCLUDE_PATH / CL env vars keep working under stricter error mode. Expand the pixi.toml comment block to document why each test-* task is wrapped in `bash -c '...'` and note Linux-only scope. Verified end-to-end: `pixi run test` passes 4,314 tests (974 pathfinder + 384 bindings + 2,956 core), 208 skipped, 2 xfailed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a2fd579 commit ee5aff5

5 files changed

Lines changed: 133 additions & 17 deletions

File tree

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
3+
"""Build cuda_bindings Cython test extensions in-place.
4+
5+
pixi-build's editable install exposes the `cuda` namespace package via a
6+
PEP 660 finder hook. Python's import machinery honors the hook, but
7+
Cython's filesystem .pxd resolver only walks real directories on sys.path,
8+
so `cimport cuda.bindings.*` fails to locate the .pxd files. We resolve
9+
the namespace package's source root from `cuda.bindings.__file__` and pass
10+
it via `include_path=` so cythonize finds the .pxd tree on every platform.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import sys
16+
from pathlib import Path
17+
18+
import cuda.bindings
19+
from Cython.Build import cythonize
20+
from setuptools import setup
21+
22+
23+
def _bindings_source_root() -> Path:
24+
# cuda.bindings.__file__ -> .../<root>/cuda/bindings/__init__.py
25+
root = Path(cuda.bindings.__file__).resolve().parents[2]
26+
if not (root / "cuda" / "bindings").is_dir():
27+
raise RuntimeError(
28+
f"cuda.bindings source tree not found at {root}; "
29+
"pixi-build editable install layout may have changed."
30+
)
31+
return root
32+
33+
34+
def main() -> None:
35+
script_dir = Path(__file__).resolve().parent
36+
pyx_files = sorted(str(p) for p in script_dir.glob("test_*.pyx"))
37+
if not pyx_files:
38+
raise SystemExit(f"no test_*.pyx files under {script_dir}")
39+
40+
ext_modules = cythonize(
41+
pyx_files,
42+
language_level=3,
43+
nthreads=1,
44+
include_path=[str(_bindings_source_root())],
45+
compiler_directives={"freethreading_compatible": True},
46+
)
47+
48+
sys.argv = [sys.argv[0], "build_ext", "--inplace"]
49+
setup(name="cuda_bindings_cython_tests", ext_modules=ext_modules)
50+
51+
52+
if __name__ == "__main__":
53+
main()
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
#!/bin/bash
2+
set -eo pipefail
23

34
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
45
# SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE
56

67
UNAME=$(uname)
78
if [ "$UNAME" == "Linux" ] ; then
89
SCRIPTPATH=$(dirname $(realpath "$0"))
9-
export CPLUS_INCLUDE_PATH=$CUDA_HOME/include:$CPLUS_INCLUDE_PATH
10+
export CPLUS_INCLUDE_PATH=$CUDA_HOME/include:${CPLUS_INCLUDE_PATH:-}
1011
elif [[ "$UNAME" == CYGWIN* || "$UNAME" == MINGW* || "$UNAME" == MSYS* ]] ; then
1112
SCRIPTPATH="$(dirname $(cygpath -w $(realpath "$0")))"
12-
export CL="/I\"${CUDA_HOME}\\include\" ${CL}"
13+
export CL="/I\"${CUDA_HOME}\\include\" ${CL:-}"
1314
else
1415
exit 1
1516
fi
1617

17-
# Use -j 1 to side-step any process-pool issues and ensure deterministic single-threaded builds
18-
cythonize -3 -j 1 -i -Xfreethreading_compatible=True ${SCRIPTPATH}/test_*.pyx
18+
# Use a Python driver so the cuda.bindings source root is resolved at
19+
# runtime and passed via Cython's include_path -- avoids platform-specific
20+
# PYTHONPATH separator handling and surfaces import errors as exceptions.
21+
# nthreads=1 inside the driver mirrors the previous `-j 1` to side-step
22+
# any process-pool issues and keep builds deterministic.
23+
python "${SCRIPTPATH}/build_tests.py"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Build cuda_core Cython test extensions in-place.
4+
5+
pixi-build's editable install exposes the `cuda` namespace package via a
6+
PEP 660 finder hook. Python's import machinery honors the hook, but
7+
Cython's filesystem .pxd resolver only walks real directories on sys.path,
8+
so `cimport cuda.bindings.*` fails to locate the .pxd files. We resolve
9+
the namespace package's source root from `cuda.bindings.__file__` and pass
10+
it via `include_path=` so cythonize finds the .pxd tree on every platform.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import sys
16+
from pathlib import Path
17+
18+
import cuda.bindings
19+
from Cython.Build import cythonize
20+
from setuptools import setup
21+
22+
23+
def _bindings_source_root() -> Path:
24+
# cuda.bindings.__file__ -> .../<root>/cuda/bindings/__init__.py
25+
root = Path(cuda.bindings.__file__).resolve().parents[2]
26+
if not (root / "cuda" / "bindings").is_dir():
27+
raise RuntimeError(
28+
f"cuda.bindings source tree not found at {root}; "
29+
"pixi-build editable install layout may have changed."
30+
)
31+
return root
32+
33+
34+
def main() -> None:
35+
script_dir = Path(__file__).resolve().parent
36+
pyx_files = sorted(str(p) for p in script_dir.glob("test_*.pyx"))
37+
if not pyx_files:
38+
raise SystemExit(f"no test_*.pyx files under {script_dir}")
39+
40+
ext_modules = cythonize(
41+
pyx_files,
42+
language_level=3,
43+
include_path=[str(_bindings_source_root())],
44+
compiler_directives={"freethreading_compatible": True},
45+
)
46+
47+
sys.argv = [sys.argv[0], "build_ext", "--inplace"]
48+
setup(name="cuda_core_cython_tests", ext_modules=ext_modules)
49+
50+
51+
if __name__ == "__main__":
52+
main()
Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
#!/bin/bash
2+
set -eo pipefail
23

34
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
45
# SPDX-License-Identifier: Apache-2.0
56

67
UNAME=$(uname)
78
if [ "$UNAME" == "Linux" ] ; then
89
SCRIPTPATH=$(dirname $(realpath "$0"))
9-
export CPLUS_INCLUDE_PATH=${SCRIPTPATH}/../../cuda/core/_include:$CUDA_HOME/include:$CPLUS_INCLUDE_PATH
10+
export CPLUS_INCLUDE_PATH=${SCRIPTPATH}/../../cuda/core/_include:$CUDA_HOME/include:${CPLUS_INCLUDE_PATH:-}
1011
elif [[ "$UNAME" == CYGWIN* || "$UNAME" == MINGW* || "$UNAME" == MSYS* ]] ; then
1112
SCRIPTPATH="$(dirname $(cygpath -w $(realpath "$0")))"
1213
CUDA_CORE_INCLUDE_PATH=$(echo "${SCRIPTPATH}\..\..\cuda\core\_include" | sed 's/\\/\\\\/g')
13-
export CL="/I\"${CUDA_CORE_INCLUDE_PATH}\" /I\"${CUDA_HOME}\\include\" ${CL}"
14+
export CL="/I\"${CUDA_CORE_INCLUDE_PATH}\" /I\"${CUDA_HOME}\\include\" ${CL:-}"
1415
else
1516
exit 1
1617
fi
1718

18-
# pixi-build's editable install exposes the cuda namespace package via a
19-
# finder hook that Cython's filesystem .pxd resolver does not consult.
20-
# Surface the package's parent directory on PYTHONPATH so `cimport
21-
# cuda.bindings.*` can locate the .pxd files.
22-
CUDA_PKG_PARENT=$(python -c "import cuda.bindings as m, os; print(os.path.dirname(os.path.dirname(os.path.dirname(m.__file__))))")
23-
export PYTHONPATH="${CUDA_PKG_PARENT}${PYTHONPATH:+:${PYTHONPATH}}"
24-
25-
cythonize -3 -i -Xfreethreading_compatible=True ${SCRIPTPATH}/test_*.pyx
19+
# Use a Python driver so the cuda.bindings source root is resolved at
20+
# runtime and passed via Cython's include_path -- avoids platform-specific
21+
# PYTHONPATH separator handling and surfaces import errors as exceptions.
22+
python "${SCRIPTPATH}/build_tests.py"

pixi.toml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,18 @@ docs = { features = [], solve-group = "docs" }
1818
PIXI_ENVIRONMENT_NAME = "${PIXI_ENVIRONMENT_NAME/default/cu13}"
1919

2020
# Test Tasks
21-
# Runs tests across all sub-packages: pathfinder → bindings → core (dependency order)
22-
# Each sub-package has its own pixi.toml; the active environment is forwarded
23-
# explicitly via -e "$PIXI_ENVIRONMENT_NAME" in each inner pixi run.
21+
# Runs tests across all sub-packages: pathfinder → bindings → core (dependency order).
22+
#
23+
# Each sub-package has its own pixi.toml. We wrap each task in `bash -c '...'`
24+
# (single-quoted in TOML, double-quoted inside) because pixi `cmd` arrays do
25+
# not expand shell variables -- without this, the inner `pixi run` would drop
26+
# into the sub-package's `default` environment, which lacks a cuda-version
27+
# pin, causing the conda solver to pick a mismatched CUDA toolkit. Forwarding
28+
# `-e "$PIXI_ENVIRONMENT_NAME"` keeps the active environment consistent end
29+
# to end.
30+
#
31+
# Linux-only for now; Windows/macOS root test-task parity is tracked
32+
# separately.
2433
#
2534
# Usage: pixi run test | pixi run -e cu12 test | pixi run -e cu13 test
2635
[target.linux.tasks.test-pathfinder]

0 commit comments

Comments
 (0)