Skip to content

Commit 7de298b

Browse files
authored
ci: retire legacy release-layout compatibility (#3456)
* ci: require registry sources for release workflows Retire legacy release-layout parsing, generic metapackage artifact fallback, and old workflow Python-matrix discovery from main. Older layouts use their own workflow revision; keep the release-policy change separate from the nightly cutover cleanup. * ci: require manual copy-pr-bot approval
1 parent 9d991df commit 7de298b

9 files changed

Lines changed: 48 additions & 115 deletions

‎.github/RELEASE-bindings.md‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,8 @@ selectors or shared `latest` pages.
7171
The workflow revision used for a release is its **control revision**; the
7272
tagged commit is its **source revision**. Record both and the artifact CI run
7373
ID. Release routing uses the package layout and toolkit settings in
74-
`ci/versions.yml` at the release tag. When retrying an interrupted CUDA 13
75-
release, retained tags can use their original configuration, source layout,
76-
build matrix, and artifact names while those artifacts remain available.
77-
New CUDA 12 releases require the local `cuda_bindings_12` directory and
74+
the release tag's `ci/versions.yml`, which must contain the
75+
`schema_version: 2` package-root registry. Both bindings lines require
7876
toolkit-qualified artifacts. The workflow control revision never supplies
7977
missing tagged toolkit settings or replaces tagged release notes.
8078

@@ -88,6 +86,15 @@ Local synthetic tags used in publication-incapable rehearsals must remain local;
8886
they do not authorize publishing a package, creating a remote tag, or moving
8987
an existing tag.
9088

89+
### Older release layouts
90+
91+
For tags without the package-root registry, run the release workflow from an
92+
appropriate historical tag or maintenance branch, using that ref's own inputs.
93+
Select a branch in Actions' **Run workflow** menu, or select a branch or tag
94+
with the GitHub CLI's `--ref` option. Verify its release flow and artifact
95+
availability before use. Older-tag docs rebuilds also need a caller on a
96+
compatible ref: `build-docs.yml` is reusable and cannot be dispatched directly.
97+
9198
### Two tags at one commit
9299

93100
One tested source commit can carry tags for both bindings lines, for example

‎.github/RELEASE-core.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ docs build prevents asset uploads, package publication, and finalization.
2121

2222
The workflow revision supplies the release tools; release notes and the
2323
package registry (`ci/versions.yml`) come from `git-tag`. Current tooling
24-
uses the toolkit configuration in the tagged source, including retained CUDA 13
25-
sources that predate the package-root registry. The release-notes job exports
24+
requires that source to use the `schema_version: 2` package-root registry.
25+
For older layouts, select a compatible historical workflow ref as described
26+
in the [bindings release guide](RELEASE-bindings.md#older-release-layouts).
27+
The release-notes job exports
2628
the checked version and rejects an empty version before documentation, asset
2729
uploads, or package publication can start. Pathfinder package publication also
2830
requires successful draft metadata preparation. Existing tags stay immutable.

‎ci/README.md‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ setuptools-scm's default tag parser. Each root's `git_describe_command`
263263
selects its CUDA ABI major for source builds (`v12.*` or `v13.*`),
264264
independently of the toolkit minor used to build it.
265265

266+
Release workflows on `main` require `schema_version: 2` in the tagged source
267+
too. For older layouts, use an appropriate historical workflow ref and its
268+
own inputs; see [older release layouts](../.github/RELEASE-bindings.md#older-release-layouts).
269+
266270
The repository-private `cuda-python-ci-tools` package provides the shared
267271
configuration readers and their dependencies. Workflows use these helpers
268272
from their control revision when inspecting another source revision. Install
@@ -373,8 +377,10 @@ source revision; the helper comes from the workflow control revision.
373377
directories on `main`. The registry identifies CUDA 12 as the maintenance
374378
line. Routine CUDA 12 fixes, tests, and documentation belong in
375379
`cuda_bindings_12/`. The old `12.9.x` branch stays frozen except for emergency
376-
fixes using compatible historical tooling; reconcile any such fix into the
377-
CUDA 12 directory on `main` and assess the CUDA 13 directory too.
380+
fixes using compatible historical tooling. Release old-layout tags from a
381+
validated workflow on the appropriate maintenance branch or historical tag,
382+
rather than from `main`. Reconcile any emergency fix into the CUDA 12 directory
383+
on `main` and assess the CUDA 13 directory too.
378384

379385
For independent releases from a shared source commit, see
380386
[two tags at one commit](../.github/RELEASE-bindings.md#two-tags-at-one-commit).

‎ci/tools/bindings_config.py‎

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def wheel_python_versions(toolkit_version: str, platform: str, versions: Sequenc
120120
if platform == "win-arm64":
121121
if Version(toolkit_version).release[:2] < (13, 4):
122122
return []
123-
# Retained release tags can include Python 3.10, which has no WoA wheels.
123+
# Python 3.10 has no Windows ARM64 wheels.
124124
return [version for version in versions if version != "3.10"]
125125
return list(versions)
126126

@@ -207,7 +207,7 @@ def resolve_release_bindings_package(
207207
release_tag: str,
208208
release_source_root: Path,
209209
) -> dict[str, object]:
210-
"""Resolve a release from its tagged source, including retained CUDA 13 tags."""
210+
"""Resolve a release from the package registry in its tagged source."""
211211
if not release_source_root.is_dir():
212212
raise ValueError(f"release source root is not a directory: {release_source_root}")
213213

@@ -220,47 +220,23 @@ def resolve_release_bindings_package(
220220
raise ValueError(f"unsupported release tag: {release_tag!r}")
221221

222222
try:
223-
raw = yaml.safe_load(config_path.read_text(encoding="utf-8"))
224-
legacy_config = "schema_version" not in raw
225-
if legacy_config:
226-
current_toolkit = raw["cuda"]["build"]["version"]
227-
if _TOOLKIT_VERSION_PATTERN.fullmatch(current_toolkit) is None or not current_toolkit.startswith("13."):
228-
raise ValueError("legacy release compatibility requires a CUDA 13 build toolkit")
229-
current_root = (
230-
"cuda_bindings_13" if (release_source_root / "cuda_bindings_13").is_dir() else "cuda_bindings"
231-
)
232-
package = BindingsPackage(current_root, tuple(map(int, current_toolkit.split("."))), "current")
233-
if bindings_version is not None and bindings_version.release[0] == 12:
234-
maintenance_toolkit = raw["cuda"]["prev_build"]["version"]
235-
if _TOOLKIT_VERSION_PATTERN.fullmatch(maintenance_toolkit) is None:
236-
raise ValueError("legacy prev_build toolkit must have major.minor.patch format")
237-
package = BindingsPackage(
238-
"cuda_bindings_12", tuple(map(int, maintenance_toolkit.split("."))), "maintenance"
239-
)
240-
else:
241-
config = parse_config(raw)
242-
package = (
243-
config.package_for_release_status("current") if is_dependent_release else config.match_tag(release_tag)
244-
)
223+
config = load_config(config_path)
224+
package = (
225+
config.package_for_release_status("current") if is_dependent_release else config.match_tag(release_tag)
226+
)
245227
except (OSError, yaml.YAMLError, KeyError, TypeError, ValueError) as error:
246228
raise ValueError(f"could not resolve release source config {config_path}: {error}") from error
247229
if package is None:
248230
raise ValueError(
249231
f"no CUDA bindings package root in tagged config {config_path} matches release tag: {release_tag!r}"
250232
)
251-
if bindings_version is not None and not package.matches_tag(release_tag):
252-
raise ValueError(f"release tag {release_tag!r} does not match tagged toolkit {package.toolkit_version}")
253-
if legacy_config and not (release_source_root / package.package_root).is_dir():
254-
raise ValueError(f"tagged source has no bindings root {package.package_root!r}")
255233

256234
record: dict[str, object] = {
257235
"package_root": package.package_root,
258236
"toolkit_version": package.toolkit_version,
259237
}
260238
if bindings_version is not None:
261239
record["release_version"] = str(bindings_version)
262-
if legacy_config and package.release_status == "current":
263-
record["allow_legacy_python_artifact"] = True
264240
return record
265241

266242

‎ci/tools/download-wheels‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,6 @@ if [[ "$IS_BINDINGS_RELEASE" == true && "$COMPONENT" == "cuda-python" ]]; then
128128
ARTIFACT_NAME="cuda-python-wheel-cuda${TOOLKIT_VERSION}"
129129
if grep -Fxq "$ARTIFACT_NAME" <<< "$ARTIFACT_NAMES"; then
130130
MATCHING_ARTIFACTS+=("$ARTIFACT_NAME")
131-
elif [[ "$TOOLKIT_VERSION" == 13.* ]] \
132-
&& jq -e '.allow_legacy_python_artifact == true' \
133-
<<< "$RESOLVED_BINDINGS_PACKAGE_JSON" >/dev/null \
134-
&& grep -Fxq "cuda-python-wheel" <<< "$ARTIFACT_NAMES"; then
135-
MATCHING_ARTIFACTS+=("cuda-python-wheel")
136131
fi
137132
else
138133
if [[ "$IS_BINDINGS_RELEASE" == true ]]; then

‎ci/tools/tests/test_bindings_release_compat.py‎

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,55 +9,42 @@
99
from ci.tools.bindings_config import main, parse_config, resolve_release_bindings_package, write_github_env
1010

1111

12-
def _source(root, *, old=False, schema2=False):
13-
current_root = "cuda_bindings" if old else "cuda_bindings_13"
12+
def _source(root):
1413
packages = {
1514
"cuda_bindings_12": {"toolkit_version": "12.9.1", "release_status": "maintenance"},
16-
current_root: {"toolkit_version": "13.4.2", "release_status": "current"},
15+
"cuda_bindings_13": {"toolkit_version": "13.4.2", "release_status": "current"},
16+
}
17+
raw = {
18+
"schema_version": 2,
19+
"python": {"supported": ["3.11", "3.14t"]},
20+
"cuda": {"bindings": {"package_roots": packages}},
1721
}
18-
raw = {"cuda": {"build": {"version": "13.4.2"}, "prev_build": {"version": "12.9.1"}}}
19-
if not old:
20-
raw["python"] = {"supported": ["3.11", "3.14t"]}
21-
if schema2:
22-
raw["schema_version"] = 2
23-
raw["cuda"] = {"bindings": {"package_roots": packages}}
2422
config = root / "ci" / "versions.yml"
2523
config.parent.mkdir(parents=True)
2624
config.write_text(yaml.safe_dump(raw))
27-
(root / current_root).mkdir()
28-
if not old:
29-
(root / "cuda_bindings_12").mkdir()
25+
for package_root in packages:
26+
(root / package_root).mkdir()
3027
return root, raw
3128

3229

3330
@pytest.mark.parametrize(
34-
("old", "schema2", "tag", "expected_root", "legacy_artifact"),
31+
("tag", "expected_root"),
3532
[
36-
(True, False, "v13.4.4", "cuda_bindings", True),
37-
(False, False, "v12.9.11", "cuda_bindings_12", False),
38-
(False, True, "v12.9.11", "cuda_bindings_12", False),
39-
(False, True, "v13.4.9", "cuda_bindings_13", False),
33+
("v12.9.11", "cuda_bindings_12"),
34+
("v13.4.9", "cuda_bindings_13"),
4035
],
4136
)
4237
@pytest.mark.agent_authored(model="gpt-6")
43-
def test_tagged_source_compatibility(tmp_path, old, schema2, tag, expected_root, legacy_artifact):
44-
source, _ = _source(tmp_path, old=old, schema2=schema2)
38+
def test_tagged_source_routes_both_bindings_majors(tmp_path, tag, expected_root):
39+
source, _ = _source(tmp_path)
4540
record = resolve_release_bindings_package(tag, source)
4641
assert record["package_root"] == expected_root
4742
assert record["release_version"] == tag.removeprefix("v")
48-
assert record.get("allow_legacy_python_artifact", False) is legacy_artifact
49-
50-
51-
@pytest.mark.agent_authored(model="gpt-6")
52-
def test_old_cuda13_source_cannot_resolve_cuda12(tmp_path):
53-
source, _ = _source(tmp_path, old=True)
54-
with pytest.raises(ValueError, match="no bindings root"):
55-
resolve_release_bindings_package("v12.9.11", source)
5643

5744

5845
@pytest.mark.agent_authored(model="gpt-6")
5946
def test_release_cli_uses_tagged_source_without_control_fallback(tmp_path, capsys):
60-
source, _ = _source(tmp_path / "tagged", old=True)
47+
source, _ = _source(tmp_path / "tagged")
6148
assert (
6249
main(
6350
[
@@ -72,10 +59,9 @@ def test_release_cli_uses_tagged_source_without_control_fallback(tmp_path, capsy
7259
== 0
7360
)
7461
assert json.loads(capsys.readouterr().out) == {
75-
"package_root": "cuda_bindings",
62+
"package_root": "cuda_bindings_13",
7663
"toolkit_version": "13.4.2",
7764
"release_version": "13.4.4",
78-
"allow_legacy_python_artifact": True,
7965
}
8066
empty = tmp_path / "absent-source"
8167
empty.mkdir()
@@ -94,7 +80,7 @@ def test_release_cli_uses_tagged_source_without_control_fallback(tmp_path, capsy
9480

9581
@pytest.mark.agent_authored(model="gpt-6")
9682
def test_dependent_release_uses_current_tagged_bindings(tmp_path):
97-
source, _ = _source(tmp_path, schema2=True)
83+
source, _ = _source(tmp_path)
9884
assert resolve_release_bindings_package("cuda-core-v1.2.0", source) == {
9985
"package_root": "cuda_bindings_13",
10086
"toolkit_version": "13.4.2",
@@ -104,7 +90,7 @@ def test_dependent_release_uses_current_tagged_bindings(tmp_path):
10490
@pytest.mark.parametrize("conflict", ["roles", "majors"])
10591
@pytest.mark.agent_authored(model="gpt-6")
10692
def test_schema2_rejects_ambiguous_release_routing(tmp_path, conflict):
107-
_, raw = _source(tmp_path, schema2=True)
93+
_, raw = _source(tmp_path)
10894
maintenance = raw["cuda"]["bindings"]["package_roots"]["cuda_bindings_12"]
10995
maintenance["release_status" if conflict == "roles" else "toolkit_version"] = (
11096
"current" if conflict == "roles" else "13.4.1"

‎ci/tools/tests/test_download_wheels.py‎

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -129,22 +129,3 @@ def test_nonrelease_components_stage_wheels_without_test_artifacts(tmp_path, com
129129
)
130130
assert result.returncode == 0, result.stderr
131131
assert {path.name for path in (tmp_path / "dist").iterdir()} == {f"{artifact}.whl" for artifact in expected}
132-
133-
134-
@pytest.mark.parametrize(
135-
("toolkit", "artifacts", "expected"),
136-
[
137-
("13.4.2", ["cuda-python-wheel"], "cuda-python-wheel"),
138-
("13.4.2", ["cuda-python-wheel", "cuda-python-wheel-cuda13.4.2"], "cuda-python-wheel-cuda13.4.2"),
139-
("12.9.1", ["cuda-python-wheel"], None),
140-
],
141-
)
142-
@pytest.mark.agent_authored(model="GPT-6")
143-
def test_legacy_metapackage_fallback_is_limited_to_cuda_13(tmp_path, toolkit, artifacts, expected):
144-
package = {"toolkit_version": toolkit, "allow_legacy_python_artifact": True}
145-
result = run_download(tmp_path, artifacts, "cuda-python", package=package, tag=f"v{toolkit}")
146-
147-
assert (result.returncode == 0) == (expected is not None), result.stderr
148-
assert {path.name for path in (tmp_path / "dist").glob("*.whl")} == (
149-
set() if expected is None else {f"{expected}.whl"}
150-
)

‎ci/tools/tests/test_validate_release_wheels.py‎

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,3 @@ def test_cuda_core_uses_the_complete_binary_wheel_matrix(tmp_path):
203203
)
204204

205205
assert result.returncode == 0, result.stderr
206-
207-
208-
@pytest.mark.agent_authored(model="GPT-6")
209-
def test_retained_tag_uses_its_build_workflow_python_matrix(tmp_path):
210-
write_release_matrix(tmp_path, platforms=("linux-64", "win-arm64"))
211-
(tmp_path / "ci/versions.yml").write_text("cuda:\n build:\n version: '13.4.2'\n", encoding="utf-8")
212-
(tmp_path / ".github/workflows/build-wheel.yml").write_text(
213-
yaml.safe_dump({"jobs": {"build": {"strategy": {"matrix": {"python-version": ["3.10", "3.12"]}}}}}),
214-
encoding="utf-8",
215-
)
216-
217-
assert expected_binary_targets(tmp_path, {"toolkit_version": "13.4.2"}) == {
218-
WheelTarget("cp310", "cp310", "manylinux-x86_64"),
219-
WheelTarget("cp312", "cp312", "manylinux-x86_64"),
220-
WheelTarget("cp312", "cp312", "win_arm64"),
221-
}

‎ci/tools/validate_release_wheels.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@ def version_from_tag(
8989
def _release_python_versions(release_source_root: Path) -> list[str]:
9090
path = release_source_root / _VERSIONS_CONFIG
9191
config = yaml.safe_load(path.read_text(encoding="utf-8"))
92-
if "python" in config or "schema_version" in config:
93-
return config["python"]["supported"]
94-
# Retained CUDA 13 tags predate the shared Python-version list.
95-
workflow = release_source_root / ".github/workflows/build-wheel.yml"
96-
return yaml.safe_load(workflow.read_text(encoding="utf-8"))["jobs"]["build"]["strategy"]["matrix"]["python-version"]
92+
return config["python"]["supported"]
9793

9894

9995
def _release_platforms(release_source_root: Path) -> set[str]:

0 commit comments

Comments
 (0)