Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions cuda_core/build_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import importlib.util
import os
import re
import shlex
import sys
import tempfile
import zipfile
from distutils import sysconfig
from pathlib import Path

import Cython as _Cython
Expand Down Expand Up @@ -340,7 +342,8 @@ def _build_define_macros(cuda_major: str, bindings_cuda_version: int | None = No

def _build_config_key(cuda_major, toolchain, debug, coverage):
"""Return a stable string key for the build configuration."""
return f"cu{cuda_major}-{toolchain}-{'debug' if debug else 'opt'}{'-cov' if coverage else ''}"
# Rebuild artifacts made before the pinned CCCL took precedence over environment flags.
return f"cu{cuda_major}-{toolchain}-{'debug' if debug else 'opt'}{'-cov' if coverage else ''}-cccl-first"


def _check_build_config(toolchain, debug, coverage):
Expand Down Expand Up @@ -445,6 +448,20 @@ def _get_cccl_include_dirs() -> list[str]:
return [str(libcudacxx_include)]


def _configure_cccl_include_flags(include_dirs):
if sys.platform == "win32":
return

# setuptools puts environment flags before Extension.include_dirs;
# conda's cuda-nvcc activation adds a competing CCCL -I there.
cccl_flags = shlex.join([f"-I{path}" for path in include_dirs])
default_flags = sysconfig.get_config_var("CFLAGS") or ""
for name in ("CFLAGS", "CXXFLAGS", "CPPFLAGS"):
flags = os.environ.get(name, "" if name == "CPPFLAGS" else default_flags)
if flags != cccl_flags and not flags.startswith(cccl_flags + " "):
os.environ[name] = f"{cccl_flags} {flags}"
Comment on lines +457 to +462

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

git diff --no-ext-diff --unified=80 3f58a7bee17cef175a614a591b97733c2c0f569d 3aefb73e4ef474692b984a9d8857438ed0b2fc08 -- cuda_core/build_hooks.py cuda_core/tests/test_build_hooks.py
printf '\n--- helper and callers ---\n'
rg -n -C 12 '_configure_cccl_include_flags|_build_cuda_core|CFLAGS|CXXFLAGS|CPPFLAGS' cuda_core/build_hooks.py cuda_core/tests/test_build_hooks.py
printf '\n--- repository guidance ---\n'
find .. -name AGENTS.md -o -name CONTRIBUTING.md -o -name '*guideline*' -o -name '*style*' | head -80

Repository: NVIDIA/cuda-python

Length of output: 41763


🏁 Script executed:

set -e
printf '%s\n' '--- applicable guidance ---'
for f in AGENTS.md cuda_core/AGENTS.md cuda_core/tests/AGENTS.md CONTRIBUTING.md; do
  if [ -f "$f" ]; then
    printf '\n### %s\n' "$f"
    nl -ba "$f"
  fi
done

printf '%s\n' '--- head helper and callers ---'
rg -n -F -C 20 -- '_configure_cccl_include_flags' cuda_core/build_hooks.py cuda_core/tests/test_build_hooks.py

printf '%s\n' '--- all test references to helper/caller and environment flags ---'
rg -n -F -C 8 -e '_configure_cccl_include_flags' -e '_build_cuda_core(' -e 'CFLAGS' -e 'CXXFLAGS' -e 'CPPFLAGS' cuda_core/tests cuda_core | sed -n '1,1400p'

printf '%s\n' '--- test declarations and relevant blocks ---'
python3 - <<'PY'
from pathlib import Path
p = Path('cuda_core/tests/test_build_hooks.py')
for i, line in enumerate(p.read_text().splitlines(), 1):
    if any(x in line for x in ('def test_', 'class Test', '_capture_cythonize_kwargs', '_build_cuda_core()', '_configure_cccl_include_flags')):
        print(f'{i}:{line}')
PY

printf '%s\n' '--- changed test diff ---'
git diff --no-ext-diff --unified=30 3f58a7bee17cef175a614a591b97733c2c0f569d 3aefb73e4ef474692b984a9d8857438ed0b2fc08 -- cuda_core/tests/test_build_hooks.py

printf '%s\n' '--- exact head source ranges around helper and every _build_cuda_core test call ---'
nl -ba cuda_core/build_hooks.py | sed -n '440,510p'
nl -ba cuda_core/tests/test_build_hooks.py | sed -n '380,430p;840,910p;980,1060p;1060,1190p'

Repository: NVIDIA/cuda-python

Length of output: 41996


🏁 Script executed:

set -e
printf '%s\n' '--- scoped guidance ---'
for f in cuda_core/AGENTS.md cuda_core/tests/AGENTS.md; do
  printf '\n### %s\n' "$f"
  nl -ba "$f"
done

printf '%s\n' '--- helper call and test assertions ---'
rg -n -F -C 6 -e '_configure_cccl_include_flags' -e 'os.environ' -e 'CFLAGS' -e 'CXXFLAGS' -e 'CPPFLAGS' cuda_core/tests/test_build_hooks.py
printf '%s\n' '--- indirect caller test blocks ---'
nl -ba cuda_core/tests/test_build_hooks.py | sed -n '390,425p;870,905p;995,1045p'
printf '%s\n' '--- caller source ---'
nl -ba cuda_core/build_hooks.py | sed -n '451,520p'

Repository: NVIDIA/cuda-python

Length of output: 41756


suggestion: Add direct tests for CCCL flag ordering and repeated invocation.

_build_cuda_core() indirectly calls _configure_cccl_include_flags(), but existing tests do not assert the resulting environment flags. Add a focused test that supplies a competing -I flag, checks that the pinned CCCL path comes first, and calls the helper twice to check idempotence.



def _build_cuda_core(debug=False):
# Customizing the build hooks is needed because we must defer cythonization until cuda-bindings,
# now a required build-time dependency that's dynamically installed via the other hook below,
Expand Down Expand Up @@ -491,7 +508,9 @@ def module_names():
yield mod

# The pinned CCCL must precede the toolkit's bundled headers.
all_include_dirs = [*_get_cccl_include_dirs(), os.path.join(cuda_path, "include")]
cccl_include_dirs = _get_cccl_include_dirs()
all_include_dirs = [*cccl_include_dirs, os.path.join(cuda_path, "include")]
_configure_cccl_include_flags(cccl_include_dirs)

# Resolve the C/C++ toolchain (CUDA_PYTHON_TOOLCHAIN). The default (gnu on
# Linux, msvc on Windows) reproduces the previous build behavior and does
Expand Down
28 changes: 19 additions & 9 deletions cuda_core/tests/test_build_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,17 @@ def _load_module(name, path, *, register=False):

@pytest.fixture(autouse=True)
def _isolate_toolchain_env():
names = ("CUDA_PYTHON_TOOLCHAIN", "CC", "CXX", "LDSHARED", "LDCXXSHARED", "CUDA_PYTHON_CYTHON_CACHE_DIR")
names = (
"CUDA_PYTHON_TOOLCHAIN",
"CC",
"CXX",
"CFLAGS",
"CXXFLAGS",
"CPPFLAGS",
"LDSHARED",
"LDCXXSHARED",
"CUDA_PYTHON_CYTHON_CACHE_DIR",
)
original = {name: os.environ[name] for name in names if name in os.environ}
for name in names:
os.environ.pop(name, None)
Expand Down Expand Up @@ -236,38 +246,38 @@ def test_missing_stamp_forces_rebuild(self, stamp):
# No stamp means the last build's config is unknown, so rebuild.
cuda_major, key = build_hooks._check_build_config("gnu", False, False)
assert cuda_major == "13"
assert key == "cu13-gnu-opt"
assert key == "cu13-gnu-opt-cccl-first"
assert build_hooks.force_build_ext is True

@pytest.mark.agent_authored(model="glm-5.2")
def test_same_config_does_not_force(self, stamp):
_write_stamp(stamp, "cu13-gnu-opt")
_write_stamp(stamp, "cu13-gnu-opt-cccl-first")
cuda_major, key = build_hooks._check_build_config("gnu", False, False)
assert cuda_major == "13"
assert build_hooks.force_build_ext is False

@pytest.mark.agent_authored(model="glm-5.2")
def test_changed_cuda_major_forces_rebuild(self, stamp):
_write_stamp(stamp, "cu12-gnu-opt")
_write_stamp(stamp, "cu12-gnu-opt-cccl-first")
cuda_major, _ = build_hooks._check_build_config("gnu", False, False)
assert cuda_major == "13"
assert build_hooks.force_build_ext is True

@pytest.mark.agent_authored(model="glm-5.2")
def test_changed_toolchain_forces_rebuild(self, stamp):
_write_stamp(stamp, "cu13-gnu-opt")
_write_stamp(stamp, "cu13-gnu-opt-cccl-first")
build_hooks._check_build_config("llvm", False, False)
assert build_hooks.force_build_ext is True

@pytest.mark.agent_authored(model="glm-5.2")
def test_changed_debug_forces_rebuild(self, stamp):
_write_stamp(stamp, "cu13-gnu-opt")
_write_stamp(stamp, "cu13-gnu-opt-cccl-first")
build_hooks._check_build_config("gnu", True, False)
assert build_hooks.force_build_ext is True

@pytest.mark.agent_authored(model="glm-5.2")
def test_changed_coverage_forces_rebuild(self, stamp):
_write_stamp(stamp, "cu13-gnu-opt")
_write_stamp(stamp, "cu13-gnu-opt-cccl-first")
build_hooks._check_build_config("gnu", False, True)
assert build_hooks.force_build_ext is True

Expand Down Expand Up @@ -428,8 +438,8 @@ def test_majors_use_different_dirs(self, monkeypatch):

assert dir_12 != dir_13
toolchain = "msvc" if sys.platform == "win32" else "gnu"
assert dir_12.name == f"cu12-{toolchain}-opt"
assert dir_13.name == f"cu13-{toolchain}-opt"
assert dir_12.name == f"cu12-{toolchain}-opt-cccl-first"
assert dir_13.name == f"cu13-{toolchain}-opt-cccl-first"

@pytest.mark.agent_authored(model="glm-5.2")
def test_dir_is_anchored_not_relative_to_cwd(self, monkeypatch):
Expand Down
Loading