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
55name : " 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
1133on :
1234 workflow_dispatch :
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
3373defaults :
3474 run :
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
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