From 3aefb73e4ef474692b984a9d8857438ed0b2fc08 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Wed, 14 Oct 2026 09:07:38 +0200 Subject: [PATCH] BLD: Insert CCCL also via FLAGS environment to work around conda nvcc Conda's `cuda-nvcc` inserts it's own CCCL in a way that setuptools ends up placing them before our includes. However, using the system CCCL means we find a version that isn't (currently) compatible. That means that in such a setup, the old "TMA doesn't link" bug resurfaces. This is a somewhat weird work-around, but it was the best idea, I could think of... --- cuda_core/build_hooks.py | 23 +++++++++++++++++++++-- cuda_core/tests/test_build_hooks.py | 28 +++++++++++++++++++--------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/cuda_core/build_hooks.py b/cuda_core/build_hooks.py index 95deb1b44a4..c80f8f55162 100644 --- a/cuda_core/build_hooks.py +++ b/cuda_core/build_hooks.py @@ -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 @@ -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): @@ -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}" + + 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, @@ -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 diff --git a/cuda_core/tests/test_build_hooks.py b/cuda_core/tests/test_build_hooks.py index fd016f9df71..d3195059910 100644 --- a/cuda_core/tests/test_build_hooks.py +++ b/cuda_core/tests/test_build_hooks.py @@ -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) @@ -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 @@ -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):