Skip to content

Commit 5841a92

Browse files
committed
ci: normalize toolkit configuration and isolate CUDA 13 versions
Signed-off-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
1 parent 57b2709 commit 5841a92

10 files changed

Lines changed: 195 additions & 18 deletions

File tree

‎.github/workflows/ci-nightly.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565

6666
- name: Run CI tools unit tests
6767
run: |
68-
python -m pip install pytest pyyaml==6.0.3
68+
python -m pip install pytest pyyaml==6.0.3 setuptools-scm
6969
# Standalone CI tool tests; skip repo-root conftest.py (imports cuda.pathfinder).
7070
python -m pytest -v --noconftest ci/tools/tests
7171

‎.github/workflows/ci.yml‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,15 @@ jobs:
258258
echo "Reusable artifacts: run \`${run_id}\` at \`${merge_base}\` on \`${BASE_REF}\`."
259259
} >> "$GITHUB_STEP_SUMMARY"
260260
261-
- name: Test CI workplan planner
262-
run: python3 -m unittest ci/tools/tests/test_compute_ci_plan.py
261+
- name: Set up Python for CI tool tests
262+
uses: ./.github/actions/setup-hardened-python
263+
with:
264+
python-version: "3.12"
265+
266+
- name: Test CI tools
267+
run: |
268+
python -m pip install pytest==9.1.0 pyyaml==6.0.3 setuptools-scm
269+
python -m pytest -v --noconftest ci/tools/tests
263270
264271
- name: Compute CI workplan
265272
id: workplan

‎.pre-commit-config.yaml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ repos:
8080

8181
- id: check-pixi-cuda-version
8282
name: Check pixi cuda-version pins track ci/versions.yml
83-
entry: python ./ci/tools/check_pixi_cuda_version.py
83+
entry: python -m ci.tools.check_pixi_cuda_version
8484
language: python
8585
additional_dependencies: [pyyaml==6.0.3]
86-
files: '^(ci/versions\.yml|cuda_bindings_13/pixi\.toml|cuda_core/pixi\.toml)$'
86+
files: '^(ci/versions\.yml|ci/tools/(bindings_config|check_pixi_cuda_version)\.py|cuda_bindings_13/pixi\.toml|cuda_core/pixi\.toml)$'
8787
pass_filenames: false
8888

8989
- id: check-mempool-hygiene

‎ci/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0

‎ci/tools/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0

‎ci/tools/bindings_config.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Read bindings package roots and release roles from the existing toolkit pins."""
5+
6+
from __future__ import annotations
7+
8+
from dataclasses import dataclass
9+
from pathlib import Path
10+
11+
import yaml
12+
13+
DEFAULT_CONFIG = Path(__file__).resolve().parents[2] / "ci" / "versions.yml"
14+
15+
16+
@dataclass(frozen=True)
17+
class BindingsPackage:
18+
package_root: str
19+
toolkit: tuple[int, int, int]
20+
release_status: str
21+
22+
@property
23+
def toolkit_version(self) -> str:
24+
return ".".join(map(str, self.toolkit))
25+
26+
@property
27+
def ctk_target(self) -> str:
28+
return f"{self.toolkit[0]}.{self.toolkit[1]}"
29+
30+
@property
31+
def cuda_major(self) -> str:
32+
return str(self.toolkit[0])
33+
34+
@property
35+
def cuda_variant(self) -> str:
36+
return f"cu{self.cuda_major}"
37+
38+
39+
@dataclass(frozen=True)
40+
class BindingsConfig:
41+
package_roots: tuple[BindingsPackage, ...]
42+
43+
def get_package(self, package_root: str) -> BindingsPackage:
44+
for package in self.package_roots:
45+
if package.package_root == package_root:
46+
return package
47+
raise ValueError(f"unknown CUDA bindings package root: {package_root!r}")
48+
49+
def package_for_release_status(self, release_status: str) -> BindingsPackage:
50+
return next(package for package in self.package_roots if package.release_status == release_status)
51+
52+
53+
def load_config(path: Path = DEFAULT_CONFIG) -> BindingsConfig:
54+
"""Normalize legacy build/prev_build fields without changing their consumers."""
55+
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
56+
packages = []
57+
for field, release_status in (("build", "current"), ("prev_build", "maintenance")):
58+
major, minor, patch = map(int, raw["cuda"][field]["version"].split("."))
59+
packages.append(BindingsPackage(f"cuda_bindings_{major}", (major, minor, patch), release_status))
60+
return BindingsConfig(tuple(packages))

‎ci/tools/check_pixi_cuda_version.py‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,31 @@
99
import tomllib
1010
from pathlib import Path
1111

12-
import yaml
12+
from .bindings_config import load_config
1313

1414
ROOT = Path(__file__).resolve().parents[2]
1515
VERSIONS_FILE_PATH = ROOT / "ci" / "versions.yml"
16-
PIXI_FILES = [ROOT / d / "pixi.toml" for d in ("cuda_bindings_13", "cuda_core")]
1716

1817

1918
def main() -> int:
20-
"""Verify cuda_bindings_13/cuda_core pixi pins match ci/versions.yml."""
19+
"""Verify current bindings and cuda_core pixi pins match ci/versions.yml."""
2120
if not VERSIONS_FILE_PATH.is_file():
2221
print(f"error: {VERSIONS_FILE_PATH} not found", file=sys.stderr)
2322
return 2
2423
try:
25-
build_version = yaml.safe_load(VERSIONS_FILE_PATH.read_text(encoding="utf-8"))["cuda"]["build"]["version"]
26-
except (KeyError, TypeError):
27-
print(f"error: cuda.build.version not found in {VERSIONS_FILE_PATH}", file=sys.stderr)
24+
current = load_config(VERSIONS_FILE_PATH).package_for_release_status("current")
25+
except (KeyError, TypeError, ValueError) as exc:
26+
print(f"error: invalid CUDA toolkit pins in {VERSIONS_FILE_PATH}: {exc}", file=sys.stderr)
2827
return 2
2928

30-
major, minor, *_ = build_version.split(".")
31-
expected = f"{major}.{minor}.*"
32-
cuda_feature = f"cu{major}"
29+
build_version = current.toolkit_version
30+
expected = f"{current.ctk_target}.*"
31+
cuda_feature = current.cuda_variant
3332

3433
errors: list[str] = []
3534
checked: list[str] = []
36-
for path in PIXI_FILES:
35+
for package_root in (current.package_root, "cuda_core"):
36+
path = ROOT / package_root / "pixi.toml"
3737
if not path.is_file():
3838
print(f"error: {path} not found", file=sys.stderr)
3939
return 2
@@ -69,7 +69,7 @@ def main() -> int:
6969

7070
if errors:
7171
print(
72-
f"error: cuda_bindings_13/cuda_core pixi cuda-version pins out of sync with "
72+
f"error: {current.package_root}/cuda_core pixi cuda-version pins out of sync with "
7373
f"ci/versions.yml cuda.build.version={build_version!r} "
7474
f"(expected pin {expected!r}):",
7575
file=sys.stderr,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from __future__ import annotations
5+
6+
import runpy
7+
import shutil
8+
import subprocess
9+
import tomllib
10+
from pathlib import Path
11+
12+
import pytest
13+
import setuptools
14+
from packaging.version import Version
15+
from setuptools_scm import get_version
16+
17+
from ci.tools import check_pixi_cuda_version
18+
from ci.tools.bindings_config import BindingsPackage, load_config
19+
20+
ROOT = Path(__file__).resolve().parents[3]
21+
22+
23+
@pytest.fixture
24+
def legacy_config(tmp_path):
25+
path = tmp_path / "versions.yml"
26+
path.write_text('cuda:\n build:\n version: "13.4.2"\n prev_build:\n version: "12.9.1"\n')
27+
return path
28+
29+
30+
@pytest.mark.agent_authored(model="gpt-6")
31+
def test_normalizes_legacy_toolkit_pins(legacy_config):
32+
config = load_config(legacy_config)
33+
current = config.package_for_release_status("current")
34+
maintenance = config.get_package("cuda_bindings_12")
35+
assert current == BindingsPackage("cuda_bindings_13", (13, 4, 2), "current")
36+
assert maintenance == config.package_for_release_status("maintenance")
37+
assert maintenance.toolkit_version == "12.9.1"
38+
assert maintenance.ctk_target == "12.9"
39+
assert maintenance.cuda_major == "12"
40+
assert maintenance.cuda_variant == "cu12"
41+
42+
43+
@pytest.fixture
44+
def pixi_checkout(tmp_path, monkeypatch, legacy_config):
45+
for package_root in ("cuda_bindings_13", "cuda_core"):
46+
path = tmp_path / package_root / "pixi.toml"
47+
path.parent.mkdir()
48+
path.write_text(
49+
'[workspace.build-variants]\ncuda-version = ["13.4.*"]\n'
50+
'[feature.cu13.dependencies]\ncuda-version = "13.4.*"\n'
51+
)
52+
monkeypatch.setattr(check_pixi_cuda_version, "ROOT", tmp_path)
53+
monkeypatch.setattr(check_pixi_cuda_version, "VERSIONS_FILE_PATH", legacy_config)
54+
return tmp_path
55+
56+
57+
@pytest.mark.agent_authored(model="gpt-6")
58+
def test_pixi_check_keeps_current_bindings_and_core_contract(pixi_checkout, capsys):
59+
# The hook requires only the current bindings workspace.
60+
assert check_pixi_cuda_version.main() == 0
61+
output = capsys.readouterr().out
62+
assert "cuda_bindings_13/pixi.toml" in output
63+
assert "cuda_core/pixi.toml" in output
64+
65+
66+
@pytest.mark.agent_authored(model="gpt-6")
67+
def test_pixi_check_reports_core_pin_drift(pixi_checkout, capsys):
68+
path = pixi_checkout / "cuda_core" / "pixi.toml"
69+
path.write_text(path.read_text().replace('cuda-version = "13.4.*"', 'cuda-version = "13.3.*"'))
70+
assert check_pixi_cuda_version.main() == 1
71+
assert "cuda_core/pixi.toml: feature.cu13.dependencies.cuda-version='13.3.*'" in capsys.readouterr().err
72+
73+
74+
@pytest.mark.agent_authored(model="gpt-6")
75+
def test_cuda12_milestone_does_not_change_current_package_versions(tmp_path, monkeypatch):
76+
for package_root, filename in (("cuda_bindings_13", "pyproject.toml"), ("cuda_python", "setup.py")):
77+
destination = tmp_path / package_root / filename
78+
destination.parent.mkdir()
79+
shutil.copyfile(ROOT / package_root / filename, destination)
80+
81+
def git(*args):
82+
return subprocess.run( # noqa: S603
83+
["git", "-c", "user.name=CI test", "-c", "user.email=ci-test@example.invalid", *args], # noqa: S607
84+
cwd=tmp_path,
85+
check=True,
86+
capture_output=True,
87+
text=True,
88+
)
89+
90+
git("init", "--quiet")
91+
git("add", ".")
92+
git("commit", "--quiet", "-m", "Current CUDA 13 release")
93+
git("tag", "v13.4.3")
94+
(tmp_path / "maintenance-change").touch()
95+
git("add", ".")
96+
git("commit", "--quiet", "-m", "CUDA 12 maintenance milestone")
97+
git("tag", "v12.9.11a0")
98+
99+
with (tmp_path / "cuda_bindings_13" / "pyproject.toml").open("rb") as stream:
100+
scm = tomllib.load(stream)["tool"]["setuptools_scm"]
101+
bindings_version = get_version(root=tmp_path, git_describe_command=scm["git_describe_command"])
102+
metadata = {}
103+
monkeypatch.setattr(setuptools, "setup", lambda **kwargs: metadata.update(kwargs))
104+
runpy.run_path(str(tmp_path / "cuda_python" / "setup.py"))
105+
assert Version(bindings_version).release[:2] == (13, 4)
106+
assert metadata["version"] == bindings_version

‎cuda_bindings_13/pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,4 @@ markers = [
112112
[tool.setuptools_scm]
113113
root = ".."
114114
version_file = "cuda/bindings/_version.py"
115-
git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v*[0-9]*"]
115+
git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.4.*"]

‎cuda_python/setup.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
version = get_version(
1212
root="..",
1313
relative_to=__file__,
14-
git_describe_command=["git", "describe", "--dirty", "--tags", "--long", "--match", "v*[0-9]*"],
14+
git_describe_command=["git", "describe", "--dirty", "--tags", "--long", "--match", "v13.4.*"],
1515
)
1616

1717

0 commit comments

Comments
 (0)