Skip to content

Commit 79d9468

Browse files
authored
ci: normalize toolkit configuration and isolate CUDA 13 versions (#3314)
* ci: normalize toolkit configuration and isolate CUDA 13 versions Signed-off-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com> * test: derive current SCM fixture version from registry [skip ci] Signed-off-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com> * Address migration review: simplify configuration and tooling Signed-off-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com> --------- Signed-off-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
1 parent ceb91b5 commit 79d9468

11 files changed

Lines changed: 218 additions & 25 deletions

File tree

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,12 @@ jobs:
6161
- name: Set up Python
6262
uses: ./.github/actions/setup-hardened-python
6363
with:
64-
python-version: "3.12"
64+
python-version: "3.11"
6565

6666
- name: Run CI tools unit tests
6767
run: |
68-
python -m pip install pytest pyyaml==6.0.3 pathspec==0.12.1
69-
# Standalone CI tool tests; skip repo-root conftest.py (imports cuda.pathfinder).
70-
python -m pytest -v --noconftest ci/tools/tests
68+
python -m pip install pytest==9.1.0 pyyaml==6.0.3 pathspec==0.12.1 "setuptools-scm[simple]>=8,!=10.1"
69+
python -m pytest -v ci/tools/tests
7170
7271
find-wheels:
7372
if: ${{ github.repository_owner == 'nvidia' && !inputs.documentation-links-only }}

‎.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.11"
265+
266+
- name: Test CI tools
267+
run: |
268+
python -m pip install pytest==9.1.0 pyyaml==6.0.3 pathspec==0.12.1 "setuptools-scm[simple]>=8,!=10.1"
269+
python -m pytest -v ci/tools/tests
263270
264271
- name: Compute CI workplan
265272
id: workplan

‎.github/workflows/test-wheel-linux.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ jobs:
146146
dependencies: "jq wget libgl1 libegl1 g++ util-linux yq"
147147
dependent_exes: "jq wget yq"
148148

149+
- name: Update container CA certificates
150+
run: |
151+
apt-get update
152+
apt-get install --only-upgrade -y ca-certificates
153+
149154
- name: Checkout driver install scripts (workflow revision)
150155
if: ${{ matrix.DRIVER != 'latest' && matrix.DRIVER != 'earliest' }}
151156
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

‎.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 & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,27 @@
99
import tomllib
1010
from pathlib import Path
1111

12-
import yaml
12+
from . import bindings_config
1313

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

1816

1917
def main() -> int:
20-
"""Verify cuda_bindings_13/cuda_core pixi pins match ci/versions.yml."""
21-
if not VERSIONS_FILE_PATH.is_file():
22-
print(f"error: {VERSIONS_FILE_PATH} not found", file=sys.stderr)
23-
return 2
18+
"""Verify current bindings and cuda_core pixi pins match ci/versions.yml."""
2419
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)
20+
current = bindings_config.load_config().package_for_release_status("current")
21+
except (KeyError, TypeError, ValueError) as exc:
22+
print(f"error: invalid CUDA toolkit pins in {bindings_config.DEFAULT_CONFIG}: {exc}", file=sys.stderr)
2823
return 2
2924

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

3429
errors: list[str] = []
3530
checked: list[str] = []
36-
for path in PIXI_FILES:
31+
for package_root in (current.package_root, "cuda_core"):
32+
path = ROOT / package_root / "pixi.toml"
3733
if not path.is_file():
3834
print(f"error: {path} not found", file=sys.stderr)
3935
return 2
@@ -69,7 +65,7 @@ def main() -> int:
6965

7066
if errors:
7167
print(
72-
f"error: cuda_bindings_13/cuda_core pixi cuda-version pins out of sync with "
68+
f"error: {current.package_root}/cuda_core pixi cuda-version pins out of sync with "
7369
f"ci/versions.yml cuda.build.version={build_version!r} "
7470
f"(expected pin {expected!r}):",
7571
file=sys.stderr,
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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.bindings_config, "load_config", lambda: load_config(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+
current = load_config(ROOT / "ci" / "versions.yml").package_for_release_status("current")
77+
for package_root, filename in (("cuda_bindings_13", "pyproject.toml"), ("cuda_python", "setup.py")):
78+
destination = tmp_path / package_root / filename
79+
destination.parent.mkdir()
80+
shutil.copyfile(ROOT / package_root / filename, destination)
81+
82+
def git(*args):
83+
return subprocess.run( # noqa: S603
84+
[ # noqa: S607
85+
"git",
86+
"-c",
87+
"user.name=CI test",
88+
"-c",
89+
"user.email=ci-test@example.invalid",
90+
"-c",
91+
"commit.gpgsign=false",
92+
"-c",
93+
"tag.gpgsign=false",
94+
"-c",
95+
"core.hooksPath=/dev/null",
96+
*args,
97+
],
98+
cwd=tmp_path,
99+
check=True,
100+
capture_output=True,
101+
text=True,
102+
)
103+
104+
git("init", "--quiet")
105+
git("add", ".")
106+
git("commit", "--quiet", "-m", "Current CUDA 13 release")
107+
git("tag", f"v{current.toolkit_version}")
108+
(tmp_path / "maintenance-change").touch()
109+
git("add", ".")
110+
git("commit", "--quiet", "-m", "CUDA 12 maintenance milestone")
111+
git("tag", "v12.9.11a0")
112+
113+
with (tmp_path / "cuda_bindings_13" / "pyproject.toml").open("rb") as stream:
114+
scm = tomllib.load(stream)["tool"]["setuptools_scm"]
115+
assert scm["git_describe_command"][-1] == f"v{current.cuda_major}.*"
116+
bindings_version = get_version(root=tmp_path, git_describe_command=scm["git_describe_command"])
117+
metadata = {}
118+
monkeypatch.setattr(setuptools, "setup", lambda **kwargs: metadata.update(kwargs))
119+
monkeypatch.setenv("CUDA_PYTHON_BUILD_MAJOR", "13")
120+
runpy.run_path(str(tmp_path / "cuda_python" / "setup.py"))
121+
assert Version(bindings_version).release[:2] == current.toolkit[:2]
122+
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.*"]

0 commit comments

Comments
 (0)