Skip to content

Commit b4fedec

Browse files
committed
cuda.core: import cuda.bindings for the build check through the #1824 namespace repair (#2783)
The build-configuration check imported cuda.bindings directly. In an isolated PEP 517 build with an in-tree backend (the wheel-from-sdist CI job), the project's own cuda/ directory is the whole `cuda` namespace, so the cuda-bindings pip installed into the build environment is not importable (#1824) and the check failed with "requires cuda-bindings to build". Import it the way _import_get_cuda_path_or_home() imports cuda.pathfinder: add the site-packages cuda/ directory to the namespace path when the plain import fails. The pxd-path block uses the same helper. Package metadata is not a substitute: pip's in-process hook runner forwards find_distributions without the requested name, so it returns this project's own metadata for `cuda-bindings`. test_managed_ops.py asserted the old wording of the NUMA-host message ("cuda-bindings 13.0+"), which now names the CUDA 13 build; the four regexes follow.
1 parent af88337 commit b4fedec

3 files changed

Lines changed: 50 additions & 15 deletions

File tree

‎cuda_core/build_hooks.py‎

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,37 @@ def _import_get_cuda_path_or_home():
6666
return cuda.pathfinder.get_cuda_path_or_home
6767

6868

69+
def _import_cuda_bindings():
70+
"""Import cuda.bindings, working around PEP 517 namespace shadowing.
71+
72+
Same problem and same repair as _import_get_cuda_path_or_home() (see
73+
https://github.com/NVIDIA/cuda-python/issues/1824): in an isolated build the
74+
project's own ``cuda/`` directory is the whole ``cuda`` namespace, so the
75+
cuda-bindings pip installed into the build environment is not importable
76+
until its ``cuda/`` directory is added to the namespace path. Raises
77+
ModuleNotFoundError when no cuda-bindings is installed at all.
78+
(importlib.metadata is no alternative: pip's in-process hook runner forwards
79+
``find_distributions`` without the requested name, so it reports this
80+
project's own metadata for any name.)
81+
"""
82+
try:
83+
import cuda.bindings
84+
except ModuleNotFoundError as exc:
85+
if exc.name not in ("cuda", "cuda.bindings"):
86+
raise
87+
import cuda
88+
89+
for p in sys.path:
90+
sp_cuda = Path(p) / "cuda"
91+
if (sp_cuda / "bindings").is_dir():
92+
cuda.__path__ = list(cuda.__path__) + [str(sp_cuda)]
93+
break
94+
else:
95+
raise
96+
import cuda.bindings
97+
return cuda.bindings
98+
99+
69100
@functools.cache
70101
def _get_cuda_path() -> str:
71102
get_cuda_path_or_home = _import_get_cuda_path_or_home()
@@ -178,13 +209,12 @@ def _check_build_configuration(cuda_path: str, cuda_major: str) -> None:
178209
requirement = floor.pip_requirement(major)
179210

180211
try:
181-
bindings_module = importlib.import_module("cuda.bindings")
182-
except ImportError as exc:
212+
bindings_version = _import_cuda_bindings().__version__
213+
except ModuleNotFoundError as exc:
183214
raise RuntimeError(
184215
f"cuda.core requires cuda-bindings to build (install '{requirement}'). "
185216
"Isolated builds install it automatically; other builds must provide it."
186217
) from exc
187-
bindings_version = bindings_module.__version__
188218
bindings = floor.release_triple(bindings_version)
189219
if bindings is None:
190220
raise RuntimeError(
@@ -360,10 +390,10 @@ def _build_cuda_core(debug=False):
360390
# We need to add the directory containing the 'cuda' package so Cython can resolve
361391
# "from cuda.bindings cimport cydriver"
362392
try:
363-
import cuda.bindings
393+
cuda_bindings = _import_cuda_bindings()
364394

365-
bindings_path = Path(cuda.bindings.__file__).parent # .../cuda/bindings/
366-
print(f"Using cuda-bindings {cuda.bindings.__version__} from {bindings_path}", file=sys.stderr)
395+
bindings_path = Path(cuda_bindings.__file__).parent # .../cuda/bindings/
396+
print(f"Using cuda-bindings {cuda_bindings.__version__} from {bindings_path}", file=sys.stderr)
367397
cuda_package_dir = bindings_path.parent.parent # .../cuda_bindings/ (contains cuda/)
368398
if str(cuda_package_dir) not in sys.path:
369399
sys.path.insert(0, str(cuda_package_dir))

‎cuda_core/tests/memory/test_managed_ops.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ def test_accessed_by_set_assignment_validates_kind_before_mutation(
465465

466466
with pytest.raises(
467467
(ValueError, TypeError),
468-
match=r"does not support location_type='host_numa'|cuda-bindings 13\.0\+",
468+
match=r"does not support location_type='host_numa'|CUDA 13 build of cuda\.core",
469469
):
470470
buf.accessed_by = {device, Host(numa_id=0)}
471471

@@ -517,7 +517,7 @@ def test_operation_validation(self, managed_buffer):
517517
# rejected at the boundary first (TypeError).
518518
with pytest.raises(
519519
(ValueError, TypeError),
520-
match=r"does not support location_type='host_numa'|cuda-bindings 13\.0\+",
520+
match=r"does not support location_type='host_numa'|CUDA 13 build of cuda\.core",
521521
):
522522
buf.accessed_by.add(Host(numa_id=_INVALID_HOST_DEVICE_ORDINAL))
523523

@@ -536,13 +536,13 @@ def test_advise_location_validation(self, location_ops_device, external_managed_
536536
# accessed_by rejects host_numa (CUDA 13: kind check; CUDA 12: boundary)
537537
with pytest.raises(
538538
(ValueError, TypeError),
539-
match=r"does not support location_type='host_numa'|cuda-bindings 13\.0\+",
539+
match=r"does not support location_type='host_numa'|CUDA 13 build of cuda\.core",
540540
):
541541
buf.accessed_by.add(Host(numa_id=0))
542542

543543
# accessed_by rejects host_numa_current (same reasoning)
544544
with pytest.raises(
545545
(ValueError, TypeError),
546-
match=r"does not support location_type='host_numa_current'|cuda-bindings 13\.0\+",
546+
match=r"does not support location_type='host_numa_current'|CUDA 13 build of cuda\.core",
547547
):
548548
buf.accessed_by.add(Host.numa_current())

‎cuda_core/tests/test_build_hooks.py‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -457,12 +457,17 @@ def test_serial_builds_and_compilers_without_the_hook_keep_the_stock_path(self,
457457

458458

459459
def _fake_bindings(monkeypatch, version):
460-
"""Install a stand-in cuda.bindings whose __version__ is `version`."""
460+
"""Make the build see an installed cuda-bindings of `version` (None: not installed)."""
461461
import types
462462

463-
module = types.ModuleType("cuda.bindings")
464-
module.__version__ = version
465-
monkeypatch.setitem(sys.modules, "cuda.bindings", module)
463+
def import_cuda_bindings():
464+
if version is None:
465+
raise ModuleNotFoundError("No module named 'cuda.bindings'", name="cuda.bindings")
466+
module = types.ModuleType("cuda.bindings")
467+
module.__version__ = version
468+
return module
469+
470+
monkeypatch.setattr(build_hooks, "_import_cuda_bindings", import_cuda_bindings)
466471

467472

468473
def _write_cuda_h(tmp_path, cuda_version):
@@ -539,7 +544,7 @@ def test_header_is_read_even_when_the_major_override_is_set(self, tmp_path, monk
539544

540545
@pytest.mark.agent_authored(model="claude-fable-5-1")
541546
def test_missing_bindings_is_a_build_error(self, tmp_path, monkeypatch):
542-
monkeypatch.setitem(sys.modules, "cuda.bindings", None) # makes `import cuda.bindings` fail
547+
_fake_bindings(monkeypatch, None) # no cuda-bindings in the build environment
543548
cuda_path = _write_cuda_h(tmp_path, 13040)
544549
with pytest.raises(RuntimeError, match="requires cuda-bindings to build"):
545550
build_hooks._check_build_configuration(cuda_path, "13")

0 commit comments

Comments
 (0)