Skip to content

Commit 8ba39e8

Browse files
authored
[doc-only] Fix backport release checks and add release dry-run (#2161)
* Check release notes for planned backports Require mainline cuda-bindings and cuda-python releases to explicitly declare a planned backport tag or mark it not planned. Keep actual backport releases unblocked while surfacing missing notes as warnings, and preserve docs builds for older tags that still use ci/versions.json. * Add dry-run release workflow mode Validate release docs, archives, and wheels without publishing to GitHub Releases, GitHub Pages, TestPyPI, or PyPI. * Allow dry-run docs branch deployment Add an explicit dry-run docs branch input so release dry-runs can optionally write generated docs to a seeded non-production branch while keeping artifact-only dry-runs as the default. * Default release workflow dispatches to dry-run Make dry-run the first and default release action so production publishing must be deliberately selected for manual release workflow runs. * Constrain dry-run docs deploy branches Require optional dry-run docs deployments to target a non-production gh-pages-* branch so manual release dry-runs cannot accidentally publish docs to production or source branches. * CI: fetch tags for release dry-run validation PR #2177 made the release workflow checkout shallow, so the dry-run tag validation now needs tags fetched explicitly while keeping history shallow. * CI: document release dry-run retest matrix Add workflow-local guidance for validating non-trivial release workflow changes with focused dry-run coverage.
1 parent c128381 commit 8ba39e8

5 files changed

Lines changed: 434 additions & 17 deletions

File tree

.github/workflows/build-docs.yml

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ on:
3333
required: false
3434
default: false
3535
type: boolean
36+
deploy-docs:
37+
description: "Deploy generated docs to GitHub Pages or preview branches"
38+
required: false
39+
default: true
40+
type: boolean
41+
docs-branch:
42+
description: "Branch that receives deployed docs"
43+
required: false
44+
default: "gh-pages"
45+
type: string
3646

3747
jobs:
3848
build:
@@ -52,7 +62,14 @@ jobs:
5262

5363
- name: Read build CTK version
5464
run: |
55-
BUILD_CTK_VER=$(yq '.cuda.build.version' ci/versions.yml)
65+
if [[ -f ci/versions.yml ]]; then
66+
BUILD_CTK_VER=$(yq '.cuda.build.version' ci/versions.yml)
67+
elif [[ -f ci/versions.json ]]; then
68+
BUILD_CTK_VER=$(jq -r '.cuda.build.version' ci/versions.json)
69+
else
70+
echo "error: cannot find ci/versions.yml or ci/versions.json" >&2
71+
exit 1
72+
fi
5673
if [[ ! "${BUILD_CTK_VER}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
5774
echo "error: derived CTK build version ${BUILD_CTK_VER} does not match MAJOR.MINOR.MICRO" >&2
5875
exit 1
@@ -305,25 +322,35 @@ jobs:
305322
key: ${{ steps.restore-lychee-cache.outputs.cache-primary-key }}
306323

307324
- name: Upload docs GitHub Pages artifact
325+
if: ${{ inputs.deploy-docs }}
308326
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
309327
with:
310328
path: artifacts/
311329
retention-days: 3
312330

331+
- name: Upload dry-run docs artifact
332+
if: ${{ !inputs.deploy-docs || (inputs.is-release && inputs.docs-branch != 'gh-pages') }}
333+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
334+
with:
335+
name: release-docs-dry-run-${{ inputs.component }}-${{ inputs.git-tag }}
336+
path: artifacts/docs/
337+
retention-days: 3
338+
313339
- name: Deploy or clean up doc preview
314-
if: ${{ !inputs.is-release }}
340+
if: ${{ inputs.deploy-docs && !inputs.is-release }}
315341
uses: ./.github/actions/doc_preview
316342
with:
317343
source-folder: ${{ (github.ref_name != 'main' && 'artifacts/docs') ||
318344
'artifacts/empty_docs' }}
319345
pr-number: ${{ env.PR_NUMBER }}
320346

321347
- name: Deploy doc update
322-
if: ${{ github.ref_name == 'main' || inputs.is-release }}
348+
if: ${{ inputs.deploy-docs && (github.ref_name == 'main' || inputs.is-release) }}
323349
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
324350
with:
325351
git-config-name: cuda-python-bot
326352
git-config-email: cuda-python-bot@users.noreply.github.com
353+
branch: ${{ inputs.docs-branch }}
327354
folder: artifacts/docs/
328355
target-folder: docs/
329356
commit-message: "Deploy ${{ (inputs.is-release && 'release') || 'latest' }} docs: ${{ env.CUDA_PYTHON_DOCS_GITHUB_REF }}"

.github/workflows/release-upload.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

@@ -18,6 +18,11 @@ on:
1818
description: "Component to download wheels for"
1919
type: string
2020
required: true
21+
dry-run:
22+
description: "Validate release artifacts without uploading them to the GitHub release"
23+
type: boolean
24+
required: false
25+
default: false
2126

2227
concurrency:
2328
# Concurrency group that uses the workflow name and PR number if available
@@ -64,6 +69,7 @@ jobs:
6469
> "release/${{ env.ARCHIVE_NAME }}.tar.gz.sha256sum"
6570
6671
- name: Upload Archive
72+
if: ${{ !inputs.dry-run }}
6773
env:
6874
GH_TOKEN: ${{ github.token }}
6975
run: >
@@ -72,7 +78,7 @@ jobs:
7278
--repo "${{ github.repository }}"
7379
release/*
7480
75-
- name: Download and Upload Wheels
81+
- name: Download and Validate Wheels
7682
env:
7783
GH_TOKEN: ${{ github.token }}
7884
run: |
@@ -82,8 +88,20 @@ jobs:
8288
# Validate that release wheels match the expected version from tag.
8389
./ci/tools/validate-release-wheels "${{ inputs.git-tag }}" "${{ inputs.component }}" "release/wheels"
8490
85-
# Upload wheels to the release
91+
- name: Upload Wheels
92+
if: ${{ !inputs.dry-run }}
93+
env:
94+
GH_TOKEN: ${{ github.token }}
95+
run: |
8696
if [[ -d "release/wheels" && $(ls -A release/wheels 2>/dev/null | wc -l) -gt 0 ]]; then
8797
echo "Uploading wheels to release ${{ inputs.git-tag }}"
8898
gh release upload --clobber "${{ inputs.git-tag }}" --repo "${{ github.repository }}" release/wheels/*
8999
fi
100+
101+
- name: Upload dry-run release artifacts
102+
if: ${{ inputs.dry-run }}
103+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
104+
with:
105+
name: release-artifacts-dry-run-${{ inputs.component }}-${{ inputs.git-tag }}
106+
path: release/
107+
retention-days: 3

.github/workflows/release.yml

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

55
name: "CI: Release"
66

77
# Manually-triggered release workflow. Creates a release draft if one doesn't exist
88
# for the given tag, or uses an existing draft, then publishes the selected wheels
9-
# to TestPyPI followed by PyPI.
9+
# to TestPyPI followed by PyPI. The dry-run mode validates the release path
10+
# without publishing to external release surfaces.
11+
#
12+
# Maintenance note: non-trivial changes to this workflow should be validated
13+
# with dry-run workflow_dispatch runs before merging. Suggested focused matrix:
14+
# - mainline:
15+
# component=cuda-bindings
16+
# git-tag=<latest cuda-bindings release tag>
17+
# backport-git-tag=not planned
18+
# run-id=<blank>
19+
# dry-run-docs-branch=gh-pages-dry-run
20+
# - backport sequence:
21+
# 1. component=cuda-bindings
22+
# git-tag=<latest cuda-bindings backport release tag>
23+
# backport-git-tag=<blank>
24+
# 2. component=cuda-python
25+
# git-tag=<matching cuda-python backport release tag>
26+
# backport-git-tag=<blank>
27+
# run-id=<blank>
28+
# dry-run-docs-branch=gh-pages-dry-run
29+
# Leave run-id blank so determine-run-id is exercised. For exhaustive coverage,
30+
# add a mainline cuda-python dry-run when changes could affect metapackage
31+
# artifact validation, docs routing, or component-specific release behavior.
1032

1133
on:
1234
workflow_dispatch:
@@ -20,15 +42,33 @@ on:
2042
- cuda-bindings
2143
- cuda-pathfinder
2244
- cuda-python
45+
release-action:
46+
description: "What to run"
47+
required: true
48+
type: choice
49+
options:
50+
- dry-run
51+
- full-release
52+
default: dry-run
2353
git-tag:
2454
description: "The release git tag"
2555
required: true
2656
type: string
57+
backport-git-tag:
58+
description: "Mainline cuda-bindings/cuda-python only: planned backport tag, or 'not planned'. Leave blank for backport releases."
59+
required: false
60+
type: string
61+
default: ""
2762
run-id:
2863
description: "The GHA run ID that generated validated artifacts (optional - auto-detects successful tag-triggered CI run for git-tag)"
2964
required: false
3065
type: string
3166
default: ""
67+
dry-run-docs-branch:
68+
description: "Dry-run only: optional gh-pages-* branch to receive generated docs, for example gh-pages-dry-run"
69+
required: false
70+
type: string
71+
default: ""
3272

3373
defaults:
3474
run:
@@ -52,9 +92,14 @@ jobs:
5292
env:
5393
GH_TOKEN: ${{ github.token }}
5494
run: |
55-
echo "Auto-detecting successful tag-triggered run ID for tag: ${{ inputs.git-tag }}"
56-
RUN_ID=$(./ci/tools/lookup-run-id "${{ inputs.git-tag }}" "${{ github.repository }}")
57-
echo "Auto-detected run ID: $RUN_ID"
95+
if [[ -n "${{ inputs.run-id }}" ]]; then
96+
echo "Using provided run ID: ${{ inputs.run-id }}"
97+
RUN_ID="${{ inputs.run-id }}"
98+
else
99+
echo "Auto-detecting successful tag-triggered run ID for tag: ${{ inputs.git-tag }}"
100+
RUN_ID=$(./ci/tools/lookup-run-id "${{ inputs.git-tag }}" "${{ github.repository }}")
101+
echo "Auto-detected run ID: $RUN_ID"
102+
fi
58103
echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT"
59104
60105
check-tag:
@@ -63,12 +108,31 @@ jobs:
63108
- name: Checkout Source
64109
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
65110
with:
111+
# Dry-run validation resolves the requested tag locally; we need tags but not history.
66112
fetch-depth: 1
113+
fetch-tags: true
67114

68-
- name: Check or create draft release for the tag
115+
- name: Check release tag and draft state
69116
env:
70117
GH_TOKEN: ${{ github.token }}
118+
RELEASE_ACTION: ${{ inputs.release-action }}
119+
RELEASE_GIT_TAG: ${{ inputs.git-tag }}
120+
DRY_RUN_DOCS_BRANCH: ${{ inputs.dry-run-docs-branch }}
71121
run: |
122+
if [[ "$RELEASE_ACTION" == "full-release" && -n "$DRY_RUN_DOCS_BRANCH" ]]; then
123+
echo "error: dry-run-docs-branch is only valid with release-action=dry-run" >&2
124+
exit 1
125+
fi
126+
if [[ "$RELEASE_ACTION" == "dry-run" && -n "$DRY_RUN_DOCS_BRANCH" && ! "$DRY_RUN_DOCS_BRANCH" =~ ^gh-pages-[[:alnum:]._/-]+$ ]]; then
127+
echo "error: dry-run-docs-branch must be a non-production gh-pages-* branch" >&2
128+
exit 1
129+
fi
130+
if [[ "$RELEASE_ACTION" == "dry-run" ]]; then
131+
git rev-parse --verify "${RELEASE_GIT_TAG}^{commit}"
132+
echo "Dry-run selected; not checking or creating a GitHub release draft."
133+
exit 0
134+
fi
135+
72136
mapfile -t tags < <(gh release list -R "${{ github.repository }}" --json tagName --jq '.[] | .tagName')
73137
mapfile -t is_draft < <(gh release list -R "${{ github.repository }}" --json isDraft --jq '.[] | .isDraft')
74138
@@ -94,8 +158,6 @@ jobs:
94158
steps:
95159
- name: Checkout Source
96160
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
97-
with:
98-
ref: ${{ inputs.git-tag }}
99161

100162
- name: Set up Python
101163
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
@@ -111,7 +173,8 @@ jobs:
111173
run: |
112174
python ci/tools/check_release_notes.py \
113175
--git-tag "${{ inputs.git-tag }}" \
114-
--component "${{ inputs.component }}"
176+
--component "${{ inputs.component }}" \
177+
--backport-git-tag "${{ inputs.backport-git-tag }}"
115178
116179
doc:
117180
name: Build release docs
@@ -132,9 +195,11 @@ jobs:
132195
git-tag: ${{ inputs.git-tag }}
133196
run-id: ${{ needs.determine-run-id.outputs.run-id }}
134197
is-release: true
198+
deploy-docs: ${{ inputs.release-action == 'full-release' || inputs.dry-run-docs-branch != '' }}
199+
docs-branch: ${{ (inputs.release-action == 'dry-run' && inputs.dry-run-docs-branch) || 'gh-pages' }}
135200

136201
upload-archive:
137-
name: Upload source archive
202+
name: Validate release artifacts
138203
permissions:
139204
contents: write
140205
needs:
@@ -148,9 +213,11 @@ jobs:
148213
git-tag: ${{ inputs.git-tag }}
149214
run-id: ${{ needs.determine-run-id.outputs.run-id }}
150215
component: ${{ inputs.component }}
216+
dry-run: ${{ inputs.release-action == 'dry-run' }}
151217

152218
publish-testpypi:
153219
name: Publish wheels to TestPyPI
220+
if: ${{ inputs.release-action == 'full-release' }}
154221
runs-on: ubuntu-latest
155222
needs:
156223
- check-tag
@@ -183,6 +250,7 @@ jobs:
183250

184251
publish-pypi:
185252
name: Publish wheels to PyPI
253+
if: ${{ inputs.release-action == 'full-release' }}
186254
runs-on: ubuntu-latest
187255
needs:
188256
- determine-run-id

0 commit comments

Comments
 (0)