From 04eb067e644170a0c79ef0fcaf5cf2c5a00f29f3 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 31 Jul 2026 09:06:31 -0700 Subject: [PATCH 1/3] CI: build cuda.core against local cuda-bindings wheel --- .github/workflows/build-wheel.yml | 8 ++-- .github/workflows/coverage.yml | 10 +++-- .github/workflows/test-sdist-linux.yml | 3 +- .github/workflows/test-sdist-windows.yml | 3 +- cuda_core/build_hooks.py | 28 ++++++++++++ cuda_core/tests/test_build_hooks.py | 55 ++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-wheel.yml b/.github/workflows/build-wheel.yml index d8a34baaf13..ace32c17734 100644 --- a/.github/workflows/build-wheel.yml +++ b/.github/workflows/build-wheel.yml @@ -250,7 +250,7 @@ jobs: CUDA_PATH=/host/${{ env.CUDA_PATH }} CUDA_PYTHON_PARALLEL_LEVEL=${{ env.CUDA_PYTHON_PARALLEL_LEVEL }} CUDA_CORE_BUILD_MAJOR=${{ env.BUILD_CUDA_MAJOR }} - PIP_FIND_LINKS=/host/${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }} + CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR=/host/${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }} CC="/host/${{ env.SCCACHE_PATH }} cc" CXX="/host/${{ env.SCCACHE_PATH }} c++" SCCACHE_GHA_ENABLED=true @@ -265,7 +265,7 @@ jobs: CUDA_PATH="$(cygpath -w ${{ env.CUDA_PATH }})" CUDA_PYTHON_PARALLEL_LEVEL=${{ env.CUDA_PYTHON_PARALLEL_LEVEL }} CUDA_CORE_BUILD_MAJOR=${{ env.BUILD_CUDA_MAJOR }} - PIP_FIND_LINKS="$(cygpath -w ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }})" + CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR="$(cygpath -w ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }})" # check cache stats before leaving cibuildwheel CIBW_BEFORE_TEST_LINUX: > "/host${{ env.SCCACHE_PATH }}" --show-adv-stats && @@ -524,7 +524,7 @@ jobs: CUDA_PATH=/host/${{ env.CUDA_PATH }} CUDA_PYTHON_PARALLEL_LEVEL=${{ env.CUDA_PYTHON_PARALLEL_LEVEL }} CUDA_CORE_BUILD_MAJOR=${{ env.BUILD_PREV_CUDA_MAJOR }} - PIP_FIND_LINKS=/host/${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }} + CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR=/host/${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }} CC="/host/${{ env.SCCACHE_PATH }} cc" CXX="/host/${{ env.SCCACHE_PATH }} c++" SCCACHE_GHA_ENABLED=true @@ -539,7 +539,7 @@ jobs: CUDA_PATH="$(cygpath -w ${{ env.CUDA_PATH }})" CUDA_PYTHON_PARALLEL_LEVEL=${{ env.CUDA_PYTHON_PARALLEL_LEVEL }} CUDA_CORE_BUILD_MAJOR=${{ env.BUILD_PREV_CUDA_MAJOR }} - PIP_FIND_LINKS="$(cygpath -w ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }})" + CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR="$(cygpath -w ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }})" # check cache stats before leaving cibuildwheel CIBW_BEFORE_TEST_LINUX: > "/host${{ env.SCCACHE_PATH }}" --show-adv-stats && diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f1b8eb9f3a2..07002e4fd65 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -123,13 +123,14 @@ jobs: cd cuda_pathfinder ../.venv/bin/pip install -v . --group test - - name: Build cuda-bindings + - name: Build and install cuda-bindings wheel run: | - cd cuda_bindings - ../.venv/bin/pip install -v . --group test + .venv/bin/pip wheel -v --no-deps ./cuda_bindings -w ./wheels/ + .venv/bin/pip install -v ./wheels/cuda_bindings*.whl --group ./cuda_bindings/pyproject.toml:test - name: Build cuda-core run: | + export CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR="$(pwd)/wheels" cd cuda_core ../.venv/bin/pip install -v . --group test @@ -237,8 +238,11 @@ jobs: - name: Build cuda.core wheel run: | + # Keep the same-checkout prerelease pathfinder eligible. The bindings + # wheel is selected independently through the direct requirement below. export PIP_FIND_LINKS="$(pwd)/wheels" export PIP_PRE=1 + export CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR="$(cygpath -w "$(pwd)/wheels")" cd cuda_core ../.venv/Scripts/pip wheel -v --no-deps . -w ../wheels/ diff --git a/.github/workflows/test-sdist-linux.yml b/.github/workflows/test-sdist-linux.yml index 9d077912f3c..5359d3f20e9 100644 --- a/.github/workflows/test-sdist-linux.yml +++ b/.github/workflows/test-sdist-linux.yml @@ -99,9 +99,10 @@ jobs: run: | export CUDA_PYTHON_PARALLEL_LEVEL=$(nproc) export CUDA_CORE_BUILD_MAJOR="$(echo '${{ inputs.cuda-version }}' | cut -d. -f1)" + export CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR="$(pwd)/cuda_bindings/dist" export CC="sccache cc" export CXX="sccache c++" - export PIP_FIND_LINKS="$(pwd)/cuda_bindings/dist $(pwd)/cuda_pathfinder/dist" + export PIP_FIND_LINKS="$(pwd)/cuda_pathfinder/dist" python -m build --sdist cuda_core/ pip wheel --no-deps --wheel-dir cuda_core/dist cuda_core/dist/*.tar.gz diff --git a/.github/workflows/test-sdist-windows.yml b/.github/workflows/test-sdist-windows.yml index 043bacc1cad..fca54af722c 100644 --- a/.github/workflows/test-sdist-windows.yml +++ b/.github/workflows/test-sdist-windows.yml @@ -91,6 +91,7 @@ jobs: run: | export CUDA_PYTHON_PARALLEL_LEVEL=$(nproc) export CUDA_CORE_BUILD_MAJOR="$(echo '${{ inputs.cuda-version }}' | cut -d. -f1)" - export PIP_FIND_LINKS="$(cygpath -w "$(pwd)/cuda_bindings/dist") $(cygpath -w "$(pwd)/cuda_pathfinder/dist")" + export CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR="$(cygpath -w "$(pwd)/cuda_bindings/dist")" + export PIP_FIND_LINKS="$(cygpath -w "$(pwd)/cuda_pathfinder/dist")" python -m build --sdist cuda_core/ pip wheel --no-deps --wheel-dir cuda_core/dist cuda_core/dist/*.tar.gz diff --git a/cuda_core/build_hooks.py b/cuda_core/build_hooks.py index 4aec4981c53..292028b41ce 100644 --- a/cuda_core/build_hooks.py +++ b/cuda_core/build_hooks.py @@ -119,6 +119,8 @@ def _determine_cuda_major_version() -> str: # used later by setup() _extensions = None +_CUDA_BINDINGS_WHEEL_DIR_ENV_VAR = "CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR" + def _build_cuda_core(debug=False): # Customizing the build hooks is needed because we must defer cythonization until cuda-bindings, @@ -136,6 +138,7 @@ def _build_cuda_core(debug=False): import cuda.bindings bindings_path = Path(cuda.bindings.__file__).parent # .../cuda/bindings/ + print(f"Using cuda-bindings {cuda.bindings.__version__} from {bindings_path}", file=sys.stderr) cuda_package_dir = bindings_path.parent.parent # .../cuda_bindings/ (contains cuda/) if str(cuda_package_dir) not in sys.path: sys.path.insert(0, str(cuda_package_dir)) @@ -329,7 +332,32 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): def _get_cuda_bindings_require(): + """Return the cuda-bindings requirement for an isolated cuda.core build.""" cuda_major = _determine_cuda_major_version() + + # A direct reference prevents pip from ranking the CI artifact against index candidates. + wheel_dir_value = os.environ.get(_CUDA_BINDINGS_WHEEL_DIR_ENV_VAR) + if wheel_dir_value is not None: + if not wheel_dir_value: + raise RuntimeError(f"{_CUDA_BINDINGS_WHEEL_DIR_ENV_VAR} must not be empty") + + wheel_dir = Path(wheel_dir_value) + wheel_pattern = f"cuda_bindings-{cuda_major}.*-*.whl" + wheels = sorted(path for path in wheel_dir.glob(wheel_pattern) if path.is_file()) if wheel_dir.is_dir() else [] + if len(wheels) != 1: + available = ( + sorted(path.name for path in wheel_dir.glob("cuda_bindings-*.whl")) if wheel_dir.is_dir() else [] + ) + raise RuntimeError( + f"Expected exactly one CUDA {cuda_major} cuda-bindings wheel in {wheel_dir} " + f"via {_CUDA_BINDINGS_WHEEL_DIR_ENV_VAR}, found {len(wheels)}. " + f"Available cuda-bindings wheels: {available}" + ) + + wheel = wheels[0].resolve() + print(f"Using local cuda-bindings build dependency: {wheel}", file=sys.stderr) + return [f"cuda-bindings @ {wheel.as_uri()}"] + return [f"cuda-bindings=={cuda_major}.*"] diff --git a/cuda_core/tests/test_build_hooks.py b/cuda_core/tests/test_build_hooks.py index 121ed1be053..546bc304651 100644 --- a/cuda_core/tests/test_build_hooks.py +++ b/cuda_core/tests/test_build_hooks.py @@ -135,3 +135,58 @@ def test_missing_cuda_path_raises_error(self): pytest.raises(RuntimeError, match="CUDA_PATH or CUDA_HOME"), ): build_hooks._determine_cuda_major_version() + + +@pytest.mark.agent_authored(model="gpt-5.6") +class TestGetCudaBindingsRequire: + """Tests for cuda-bindings build dependency selection.""" + + def test_default_requirement_uses_cuda_major(self, monkeypatch): + monkeypatch.setenv("CUDA_CORE_BUILD_MAJOR", "13") + monkeypatch.delenv("CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR", raising=False) + build_hooks._determine_cuda_major_version.cache_clear() + + assert build_hooks._get_cuda_bindings_require() == ["cuda-bindings==13.*"] + + def test_local_wheel_requirement_uses_matching_major(self, monkeypatch, tmp_path): + wheel_dir = tmp_path / "wheel directory" + wheel_dir.mkdir() + matching_wheel = wheel_dir / "cuda_bindings-13.3.2.dev1+gabc123.d20260731-py3-none-any.whl" + matching_wheel.touch() + (wheel_dir / "cuda_bindings-12.9.2-py3-none-any.whl").touch() + + monkeypatch.setenv("CUDA_CORE_BUILD_MAJOR", "13") + monkeypatch.setenv("CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR", str(wheel_dir)) + build_hooks._determine_cuda_major_version.cache_clear() + + assert build_hooks._get_cuda_bindings_require() == [f"cuda-bindings @ {matching_wheel.resolve().as_uri()}"] + + def test_local_wheel_directory_must_not_be_empty(self, monkeypatch): + monkeypatch.setenv("CUDA_CORE_BUILD_MAJOR", "13") + monkeypatch.setenv("CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR", "") + build_hooks._determine_cuda_major_version.cache_clear() + + with pytest.raises(RuntimeError, match="CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR must not be empty"): + build_hooks._get_cuda_bindings_require() + + @pytest.mark.parametrize( + "wheel_names", + [ + (), + ( + "cuda_bindings-13.3.2.dev1-py3-none-any.whl", + "cuda_bindings-13.3.2.dev2-py3-none-any.whl", + ), + ], + ids=["missing", "ambiguous"], + ) + def test_local_wheel_requirement_requires_exactly_one_match(self, monkeypatch, tmp_path, wheel_names): + for wheel_name in wheel_names: + (tmp_path / wheel_name).touch() + + monkeypatch.setenv("CUDA_CORE_BUILD_MAJOR", "13") + monkeypatch.setenv("CUDA_CORE_BUILD_BINDINGS_WHEEL_DIR", str(tmp_path)) + build_hooks._determine_cuda_major_version.cache_clear() + + with pytest.raises(RuntimeError, match="Expected exactly one CUDA 13 cuda-bindings wheel"): + build_hooks._get_cuda_bindings_require() From e08eedf3f1d0c20f47def61c24eade0ab0fb994d Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 31 Jul 2026 09:46:25 -0700 Subject: [PATCH 2/3] cuda.core: fix host memcpy source pointer cast --- cuda_core/cuda/core/graph/_graph_node.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cuda_core/cuda/core/graph/_graph_node.pyx b/cuda_core/cuda/core/graph/_graph_node.pyx index 38bc6f955e6..37411c857b5 100644 --- a/cuda_core/cuda/core/graph/_graph_node.pyx +++ b/cuda_core/cuda/core/graph/_graph_node.pyx @@ -1003,7 +1003,7 @@ cdef void _init_memcpy_params( params.srcMemoryType = src_type[0] params.dstMemoryType = dst_type[0] if src_type[0] == cydriver.CU_MEMORYTYPE_HOST: - params.srcHost = src + params.srcHost = src else: params.srcDevice = src if dst_type[0] == cydriver.CU_MEMORYTYPE_HOST: From 71502adb7c0f304e61d80e34387275c69ff90365 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 31 Jul 2026 10:10:34 -0700 Subject: [PATCH 3/3] CI: avoid SIGPIPE when selecting core release tag --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 442476ba05e..f36b880beee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -255,11 +255,11 @@ jobs: GH_TOKEN: ${{ github.token }} run: | # --paginate fetches all pages; jq outputs one name per line per page; - # head -1 takes the first (newest) match since GitHub returns tags - # newest-first. Fails hard if no cuda-core-v* tag is found. + # sed prints the first (newest) match while consuming all pages, so + # gh can complete without SIGPIPE. Fails if no cuda-core-v* tag is found. tag="$(gh api "repos/$GITHUB_REPOSITORY/tags" --paginate \ --jq '.[] | select(.name | startswith("cuda-core-v")) | .name' \ - | head -1)" + | sed -n '1p')" if [[ -z "${tag}" ]]; then echo "::error::No cuda-core-v* tag found in the repository." >&2 exit 1