From 3ab81b4f4f8cf350d67c564dd2a094fb6a40e66a Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 30 Oct 2026 21:48:59 -0700 Subject: [PATCH 1/3] ci: normalize toolkit configuration and isolate CUDA 13 versions Signed-off-by: Ralf W. Grosse-Kunstleve --- .github/workflows/ci-nightly.yml | 2 +- .github/workflows/ci.yml | 11 ++- .pre-commit-config.yaml | 4 +- ci/__init__.py | 2 + ci/tools/__init__.py | 2 + ci/tools/bindings_config.py | 60 ++++++++++++++ ci/tools/check_pixi_cuda_version.py | 22 ++--- ci/tools/tests/test_bindings_config.py | 106 +++++++++++++++++++++++++ cuda_bindings_13/pyproject.toml | 2 +- cuda_python/setup.py | 2 +- 10 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 ci/__init__.py create mode 100644 ci/tools/__init__.py create mode 100644 ci/tools/bindings_config.py create mode 100644 ci/tools/tests/test_bindings_config.py diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index 0abfdd626cb..94baaf40f34 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -65,7 +65,7 @@ jobs: - name: Run CI tools unit tests run: | - python -m pip install pytest pyyaml==6.0.3 + python -m pip install pytest pyyaml==6.0.3 setuptools-scm # Standalone CI tool tests; skip repo-root conftest.py (imports cuda.pathfinder). python -m pytest -v --noconftest ci/tools/tests diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 920629706c7..c89ec831e46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -258,8 +258,15 @@ jobs: echo "Reusable artifacts: run \`${run_id}\` at \`${merge_base}\` on \`${BASE_REF}\`." } >> "$GITHUB_STEP_SUMMARY" - - name: Test CI workplan planner - run: python3 -m unittest ci/tools/tests/test_compute_ci_plan.py + - name: Set up Python for CI tool tests + uses: ./.github/actions/setup-hardened-python + with: + python-version: "3.12" + + - name: Test CI tools + run: | + python -m pip install pytest==9.1.0 pyyaml==6.0.3 setuptools-scm + python -m pytest -v --noconftest ci/tools/tests - name: Compute CI workplan id: workplan diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bd896833b45..0bc872b1ec3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -80,10 +80,10 @@ repos: - id: check-pixi-cuda-version name: Check pixi cuda-version pins track ci/versions.yml - entry: python ./ci/tools/check_pixi_cuda_version.py + entry: python -m ci.tools.check_pixi_cuda_version language: python additional_dependencies: [pyyaml==6.0.3] - files: '^(ci/versions\.yml|cuda_bindings_13/pixi\.toml|cuda_core/pixi\.toml)$' + files: '^(ci/versions\.yml|ci/tools/(bindings_config|check_pixi_cuda_version)\.py|cuda_bindings_13/pixi\.toml|cuda_core/pixi\.toml)$' pass_filenames: false - id: check-mempool-hygiene diff --git a/ci/__init__.py b/ci/__init__.py new file mode 100644 index 00000000000..52a7a9daf02 --- /dev/null +++ b/ci/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/ci/tools/__init__.py b/ci/tools/__init__.py new file mode 100644 index 00000000000..52a7a9daf02 --- /dev/null +++ b/ci/tools/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/ci/tools/bindings_config.py b/ci/tools/bindings_config.py new file mode 100644 index 00000000000..50741f8e57e --- /dev/null +++ b/ci/tools/bindings_config.py @@ -0,0 +1,60 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Read bindings package roots and release roles from the existing toolkit pins.""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path + +import yaml + +DEFAULT_CONFIG = Path(__file__).resolve().parents[2] / "ci" / "versions.yml" + + +@dataclass(frozen=True) +class BindingsPackage: + package_root: str + toolkit: tuple[int, int, int] + release_status: str + + @property + def toolkit_version(self) -> str: + return ".".join(map(str, self.toolkit)) + + @property + def ctk_target(self) -> str: + return f"{self.toolkit[0]}.{self.toolkit[1]}" + + @property + def cuda_major(self) -> str: + return str(self.toolkit[0]) + + @property + def cuda_variant(self) -> str: + return f"cu{self.cuda_major}" + + +@dataclass(frozen=True) +class BindingsConfig: + package_roots: tuple[BindingsPackage, ...] + + def get_package(self, package_root: str) -> BindingsPackage: + for package in self.package_roots: + if package.package_root == package_root: + return package + raise ValueError(f"unknown CUDA bindings package root: {package_root!r}") + + def package_for_release_status(self, release_status: str) -> BindingsPackage: + return next(package for package in self.package_roots if package.release_status == release_status) + + +def load_config(path: Path = DEFAULT_CONFIG) -> BindingsConfig: + """Normalize legacy build/prev_build fields without changing their consumers.""" + raw = yaml.safe_load(path.read_text(encoding="utf-8")) + packages = [] + for field, release_status in (("build", "current"), ("prev_build", "maintenance")): + major, minor, patch = map(int, raw["cuda"][field]["version"].split(".")) + packages.append(BindingsPackage(f"cuda_bindings_{major}", (major, minor, patch), release_status)) + return BindingsConfig(tuple(packages)) diff --git a/ci/tools/check_pixi_cuda_version.py b/ci/tools/check_pixi_cuda_version.py index acd244a7316..dd57b15144f 100644 --- a/ci/tools/check_pixi_cuda_version.py +++ b/ci/tools/check_pixi_cuda_version.py @@ -9,31 +9,31 @@ import tomllib from pathlib import Path -import yaml +from .bindings_config import load_config ROOT = Path(__file__).resolve().parents[2] VERSIONS_FILE_PATH = ROOT / "ci" / "versions.yml" -PIXI_FILES = [ROOT / d / "pixi.toml" for d in ("cuda_bindings_13", "cuda_core")] def main() -> int: - """Verify cuda_bindings_13/cuda_core pixi pins match ci/versions.yml.""" + """Verify current bindings and cuda_core pixi pins match ci/versions.yml.""" if not VERSIONS_FILE_PATH.is_file(): print(f"error: {VERSIONS_FILE_PATH} not found", file=sys.stderr) return 2 try: - build_version = yaml.safe_load(VERSIONS_FILE_PATH.read_text(encoding="utf-8"))["cuda"]["build"]["version"] - except (KeyError, TypeError): - print(f"error: cuda.build.version not found in {VERSIONS_FILE_PATH}", file=sys.stderr) + current = load_config(VERSIONS_FILE_PATH).package_for_release_status("current") + except (KeyError, TypeError, ValueError) as exc: + print(f"error: invalid CUDA toolkit pins in {VERSIONS_FILE_PATH}: {exc}", file=sys.stderr) return 2 - major, minor, *_ = build_version.split(".") - expected = f"{major}.{minor}.*" - cuda_feature = f"cu{major}" + build_version = current.toolkit_version + expected = f"{current.ctk_target}.*" + cuda_feature = current.cuda_variant errors: list[str] = [] checked: list[str] = [] - for path in PIXI_FILES: + for package_root in (current.package_root, "cuda_core"): + path = ROOT / package_root / "pixi.toml" if not path.is_file(): print(f"error: {path} not found", file=sys.stderr) return 2 @@ -69,7 +69,7 @@ def main() -> int: if errors: print( - f"error: cuda_bindings_13/cuda_core pixi cuda-version pins out of sync with " + f"error: {current.package_root}/cuda_core pixi cuda-version pins out of sync with " f"ci/versions.yml cuda.build.version={build_version!r} " f"(expected pin {expected!r}):", file=sys.stderr, diff --git a/ci/tools/tests/test_bindings_config.py b/ci/tools/tests/test_bindings_config.py new file mode 100644 index 00000000000..ae39af2753a --- /dev/null +++ b/ci/tools/tests/test_bindings_config.py @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import runpy +import shutil +import subprocess +import tomllib +from pathlib import Path + +import pytest +import setuptools +from packaging.version import Version +from setuptools_scm import get_version + +from ci.tools import check_pixi_cuda_version +from ci.tools.bindings_config import BindingsPackage, load_config + +ROOT = Path(__file__).resolve().parents[3] + + +@pytest.fixture +def legacy_config(tmp_path): + path = tmp_path / "versions.yml" + path.write_text('cuda:\n build:\n version: "13.4.2"\n prev_build:\n version: "12.9.1"\n') + return path + + +@pytest.mark.agent_authored(model="gpt-6") +def test_normalizes_legacy_toolkit_pins(legacy_config): + config = load_config(legacy_config) + current = config.package_for_release_status("current") + maintenance = config.get_package("cuda_bindings_12") + assert current == BindingsPackage("cuda_bindings_13", (13, 4, 2), "current") + assert maintenance == config.package_for_release_status("maintenance") + assert maintenance.toolkit_version == "12.9.1" + assert maintenance.ctk_target == "12.9" + assert maintenance.cuda_major == "12" + assert maintenance.cuda_variant == "cu12" + + +@pytest.fixture +def pixi_checkout(tmp_path, monkeypatch, legacy_config): + for package_root in ("cuda_bindings_13", "cuda_core"): + path = tmp_path / package_root / "pixi.toml" + path.parent.mkdir() + path.write_text( + '[workspace.build-variants]\ncuda-version = ["13.4.*"]\n' + '[feature.cu13.dependencies]\ncuda-version = "13.4.*"\n' + ) + monkeypatch.setattr(check_pixi_cuda_version, "ROOT", tmp_path) + monkeypatch.setattr(check_pixi_cuda_version, "VERSIONS_FILE_PATH", legacy_config) + return tmp_path + + +@pytest.mark.agent_authored(model="gpt-6") +def test_pixi_check_keeps_current_bindings_and_core_contract(pixi_checkout, capsys): + # The hook requires only the current bindings workspace. + assert check_pixi_cuda_version.main() == 0 + output = capsys.readouterr().out + assert "cuda_bindings_13/pixi.toml" in output + assert "cuda_core/pixi.toml" in output + + +@pytest.mark.agent_authored(model="gpt-6") +def test_pixi_check_reports_core_pin_drift(pixi_checkout, capsys): + path = pixi_checkout / "cuda_core" / "pixi.toml" + path.write_text(path.read_text().replace('cuda-version = "13.4.*"', 'cuda-version = "13.3.*"')) + assert check_pixi_cuda_version.main() == 1 + assert "cuda_core/pixi.toml: feature.cu13.dependencies.cuda-version='13.3.*'" in capsys.readouterr().err + + +@pytest.mark.agent_authored(model="gpt-6") +def test_cuda12_milestone_does_not_change_current_package_versions(tmp_path, monkeypatch): + for package_root, filename in (("cuda_bindings_13", "pyproject.toml"), ("cuda_python", "setup.py")): + destination = tmp_path / package_root / filename + destination.parent.mkdir() + shutil.copyfile(ROOT / package_root / filename, destination) + + def git(*args): + return subprocess.run( # noqa: S603 + ["git", "-c", "user.name=CI test", "-c", "user.email=ci-test@example.invalid", *args], # noqa: S607 + cwd=tmp_path, + check=True, + capture_output=True, + text=True, + ) + + git("init", "--quiet") + git("add", ".") + git("commit", "--quiet", "-m", "Current CUDA 13 release") + git("tag", "v13.4.3") + (tmp_path / "maintenance-change").touch() + git("add", ".") + git("commit", "--quiet", "-m", "CUDA 12 maintenance milestone") + git("tag", "v12.9.11a0") + + with (tmp_path / "cuda_bindings_13" / "pyproject.toml").open("rb") as stream: + scm = tomllib.load(stream)["tool"]["setuptools_scm"] + bindings_version = get_version(root=tmp_path, git_describe_command=scm["git_describe_command"]) + metadata = {} + monkeypatch.setattr(setuptools, "setup", lambda **kwargs: metadata.update(kwargs)) + runpy.run_path(str(tmp_path / "cuda_python" / "setup.py")) + assert Version(bindings_version).release[:2] == (13, 4) + assert metadata["version"] == bindings_version diff --git a/cuda_bindings_13/pyproject.toml b/cuda_bindings_13/pyproject.toml index 471fa0ee45c..078c9d4c036 100644 --- a/cuda_bindings_13/pyproject.toml +++ b/cuda_bindings_13/pyproject.toml @@ -112,4 +112,4 @@ markers = [ [tool.setuptools_scm] root = ".." version_file = "cuda/bindings/_version.py" -git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v*[0-9]*"] +git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.4.*"] diff --git a/cuda_python/setup.py b/cuda_python/setup.py index 2c308da1c51..a2b9702828e 100644 --- a/cuda_python/setup.py +++ b/cuda_python/setup.py @@ -11,7 +11,7 @@ version = get_version( root="..", relative_to=__file__, - git_describe_command=["git", "describe", "--dirty", "--tags", "--long", "--match", "v*[0-9]*"], + git_describe_command=["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.4.*"], ) From d04b64d42ef2021158cb6052a166b44d1e01d86a Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sun, 1 Nov 2026 19:28:57 -0800 Subject: [PATCH 2/3] test: derive current SCM fixture version from registry [skip ci] Signed-off-by: Ralf W. Grosse-Kunstleve --- ci/tools/tests/test_bindings_config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/tools/tests/test_bindings_config.py b/ci/tools/tests/test_bindings_config.py index ae39af2753a..c659bab9938 100644 --- a/ci/tools/tests/test_bindings_config.py +++ b/ci/tools/tests/test_bindings_config.py @@ -73,6 +73,7 @@ def test_pixi_check_reports_core_pin_drift(pixi_checkout, capsys): @pytest.mark.agent_authored(model="gpt-6") def test_cuda12_milestone_does_not_change_current_package_versions(tmp_path, monkeypatch): + current = load_config(ROOT / "ci" / "versions.yml").package_for_release_status("current") for package_root, filename in (("cuda_bindings_13", "pyproject.toml"), ("cuda_python", "setup.py")): destination = tmp_path / package_root / filename destination.parent.mkdir() @@ -90,7 +91,7 @@ def git(*args): git("init", "--quiet") git("add", ".") git("commit", "--quiet", "-m", "Current CUDA 13 release") - git("tag", "v13.4.3") + git("tag", f"v{current.toolkit_version}") (tmp_path / "maintenance-change").touch() git("add", ".") git("commit", "--quiet", "-m", "CUDA 12 maintenance milestone") @@ -102,5 +103,5 @@ def git(*args): metadata = {} monkeypatch.setattr(setuptools, "setup", lambda **kwargs: metadata.update(kwargs)) runpy.run_path(str(tmp_path / "cuda_python" / "setup.py")) - assert Version(bindings_version).release[:2] == (13, 4) + assert Version(bindings_version).release[:2] == current.toolkit[:2] assert metadata["version"] == bindings_version From 77981309e15bed0531b4ca595acf89d4095c90af Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 3 Nov 2026 21:47:23 -0800 Subject: [PATCH 3/3] Address migration review: simplify configuration and tooling Signed-off-by: Ralf W. Grosse-Kunstleve --- .github/workflows/ci-nightly.yml | 7 +++---- .github/workflows/ci.yml | 6 +++--- ci/tools/check_pixi_cuda_version.py | 10 +++------- ci/tools/tests/test_bindings_config.py | 19 +++++++++++++++++-- cuda_bindings_13/pyproject.toml | 2 +- cuda_python/setup.py | 2 +- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index 94baaf40f34..ecf7f8adbbf 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -61,13 +61,12 @@ jobs: - name: Set up Python uses: ./.github/actions/setup-hardened-python with: - python-version: "3.12" + python-version: "3.11" - name: Run CI tools unit tests run: | - python -m pip install pytest pyyaml==6.0.3 setuptools-scm - # Standalone CI tool tests; skip repo-root conftest.py (imports cuda.pathfinder). - python -m pytest -v --noconftest ci/tools/tests + python -m pip install pytest==9.1.0 pyyaml==6.0.3 "setuptools-scm[simple]>=8,!=10.1" + python -m pytest -v ci/tools/tests find-wheels: if: ${{ github.repository_owner == 'nvidia' && !inputs.documentation-links-only }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c89ec831e46..9a4fa0318d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -261,12 +261,12 @@ jobs: - name: Set up Python for CI tool tests uses: ./.github/actions/setup-hardened-python with: - python-version: "3.12" + python-version: "3.11" - name: Test CI tools run: | - python -m pip install pytest==9.1.0 pyyaml==6.0.3 setuptools-scm - python -m pytest -v --noconftest ci/tools/tests + python -m pip install pytest==9.1.0 pyyaml==6.0.3 "setuptools-scm[simple]>=8,!=10.1" + python -m pytest -v ci/tools/tests - name: Compute CI workplan id: workplan diff --git a/ci/tools/check_pixi_cuda_version.py b/ci/tools/check_pixi_cuda_version.py index dd57b15144f..30f765acafa 100644 --- a/ci/tools/check_pixi_cuda_version.py +++ b/ci/tools/check_pixi_cuda_version.py @@ -9,21 +9,17 @@ import tomllib from pathlib import Path -from .bindings_config import load_config +from . import bindings_config ROOT = Path(__file__).resolve().parents[2] -VERSIONS_FILE_PATH = ROOT / "ci" / "versions.yml" def main() -> int: """Verify current bindings and cuda_core pixi pins match ci/versions.yml.""" - if not VERSIONS_FILE_PATH.is_file(): - print(f"error: {VERSIONS_FILE_PATH} not found", file=sys.stderr) - return 2 try: - current = load_config(VERSIONS_FILE_PATH).package_for_release_status("current") + current = bindings_config.load_config().package_for_release_status("current") except (KeyError, TypeError, ValueError) as exc: - print(f"error: invalid CUDA toolkit pins in {VERSIONS_FILE_PATH}: {exc}", file=sys.stderr) + print(f"error: invalid CUDA toolkit pins in {bindings_config.DEFAULT_CONFIG}: {exc}", file=sys.stderr) return 2 build_version = current.toolkit_version diff --git a/ci/tools/tests/test_bindings_config.py b/ci/tools/tests/test_bindings_config.py index c659bab9938..2a1efb5dd7a 100644 --- a/ci/tools/tests/test_bindings_config.py +++ b/ci/tools/tests/test_bindings_config.py @@ -50,7 +50,7 @@ def pixi_checkout(tmp_path, monkeypatch, legacy_config): '[feature.cu13.dependencies]\ncuda-version = "13.4.*"\n' ) monkeypatch.setattr(check_pixi_cuda_version, "ROOT", tmp_path) - monkeypatch.setattr(check_pixi_cuda_version, "VERSIONS_FILE_PATH", legacy_config) + monkeypatch.setattr(check_pixi_cuda_version.bindings_config, "load_config", lambda: load_config(legacy_config)) return tmp_path @@ -81,7 +81,20 @@ def test_cuda12_milestone_does_not_change_current_package_versions(tmp_path, mon def git(*args): return subprocess.run( # noqa: S603 - ["git", "-c", "user.name=CI test", "-c", "user.email=ci-test@example.invalid", *args], # noqa: S607 + [ # noqa: S607 + "git", + "-c", + "user.name=CI test", + "-c", + "user.email=ci-test@example.invalid", + "-c", + "commit.gpgsign=false", + "-c", + "tag.gpgsign=false", + "-c", + "core.hooksPath=/dev/null", + *args, + ], cwd=tmp_path, check=True, capture_output=True, @@ -99,9 +112,11 @@ def git(*args): with (tmp_path / "cuda_bindings_13" / "pyproject.toml").open("rb") as stream: scm = tomllib.load(stream)["tool"]["setuptools_scm"] + assert scm["git_describe_command"][-1] == f"v{current.cuda_major}.*" bindings_version = get_version(root=tmp_path, git_describe_command=scm["git_describe_command"]) metadata = {} monkeypatch.setattr(setuptools, "setup", lambda **kwargs: metadata.update(kwargs)) + monkeypatch.setenv("CUDA_PYTHON_BUILD_MAJOR", "13") runpy.run_path(str(tmp_path / "cuda_python" / "setup.py")) assert Version(bindings_version).release[:2] == current.toolkit[:2] assert metadata["version"] == bindings_version diff --git a/cuda_bindings_13/pyproject.toml b/cuda_bindings_13/pyproject.toml index 078c9d4c036..e6261ee483d 100644 --- a/cuda_bindings_13/pyproject.toml +++ b/cuda_bindings_13/pyproject.toml @@ -112,4 +112,4 @@ markers = [ [tool.setuptools_scm] root = ".." version_file = "cuda/bindings/_version.py" -git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.4.*"] +git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.*"] diff --git a/cuda_python/setup.py b/cuda_python/setup.py index a2b9702828e..2223375aa8a 100644 --- a/cuda_python/setup.py +++ b/cuda_python/setup.py @@ -11,7 +11,7 @@ version = get_version( root="..", relative_to=__file__, - git_describe_command=["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.4.*"], + git_describe_command=["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.*"], )