Skip to content

CI: Release

CI: Release #174

Workflow file for this run

# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
name: "CI: Release"
# Manually-triggered release workflow. Creates a release draft if one doesn't exist
# for the given tag, or uses an existing draft, then publishes the selected wheels
# to TestPyPI followed by PyPI. The dry-run mode validates the release path
# without publishing to external release surfaces.
on:
workflow_dispatch:
inputs:
component:
description: "Component to release"
required: true
type: choice
options:
- cuda-core
- cuda-bindings
- cuda-pathfinder
- cuda-python
release-action:
description: "What to run"
required: true
type: choice
options:
- full-release
- dry-run
default: full-release
git-tag:
description: "The release git tag"
required: true
type: string
backport-git-tag:
description: "Mainline cuda-bindings/cuda-python only: planned backport tag, or 'not planned'. Leave blank for backport releases."
required: false
type: string
default: ""
run-id:
description: "The GHA run ID that generated validated artifacts (optional - auto-detects successful tag-triggered CI run for git-tag)"
required: false
type: string
default: ""
dry-run-docs-branch:
description: "Dry-run only: optional branch to receive generated docs, for example gh-pages-dry-run"
required: false
type: string
default: ""
defaults:
run:
shell: bash --noprofile --norc -xeuo pipefail {0}
jobs:
determine-run-id:
runs-on: ubuntu-latest
outputs:
run-id: ${{ steps.lookup-run-id.outputs.run-id }}
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# fetch-depth: 0 is required so the lookup-run-id script can access all git tags
fetch-depth: 0
- name: Determine Run ID
id: lookup-run-id
env:
GH_TOKEN: ${{ github.token }}
run: |
if [[ -n "${{ inputs.run-id }}" ]]; then
echo "Using provided run ID: ${{ inputs.run-id }}"
RUN_ID="${{ inputs.run-id }}"
else
echo "Auto-detecting successful tag-triggered run ID for tag: ${{ inputs.git-tag }}"
RUN_ID=$(./ci/tools/lookup-run-id "${{ inputs.git-tag }}" "${{ github.repository }}")
echo "Auto-detected run ID: $RUN_ID"
fi
echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT"
check-tag:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Check release tag and draft state
env:
GH_TOKEN: ${{ github.token }}
run: |
if [[ "${{ inputs.release-action }}" == "full-release" && -n "${{ inputs.dry-run-docs-branch }}" ]]; then
echo "error: dry-run-docs-branch is only valid with release-action=dry-run" >&2
exit 1
fi
if [[ "${{ inputs.release-action }}" == "dry-run" && "${{ inputs.dry-run-docs-branch }}" == "gh-pages" ]]; then
echo "error: dry-run-docs-branch must not be gh-pages" >&2
exit 1
fi
if [[ "${{ inputs.release-action }}" == "dry-run" ]]; then
git rev-parse --verify "${{ inputs.git-tag }}^{commit}"
echo "Dry-run selected; not checking or creating a GitHub release draft."
exit 0
fi
mapfile -t tags < <(gh release list -R "${{ github.repository }}" --json tagName --jq '.[] | .tagName')
mapfile -t is_draft < <(gh release list -R "${{ github.repository }}" --json isDraft --jq '.[] | .isDraft')
found=0
for idx in "${!tags[@]}"; do
if [[ "${tags[$idx]}" == "${{ inputs.git-tag }}" ]]; then
echo "found existing release for ${{ inputs.git-tag }}"
found=1
if [[ "${is_draft[$idx]}" != "true" ]]; then
echo "the release note is not in draft state"
exit 1
fi
break
fi
done
if [[ "$found" == 0 ]]; then
echo "no release found for ${{ inputs.git-tag }}, creating draft release"
gh release create "${{ inputs.git-tag }}" --draft --repo "${{ github.repository }}" --title "Release ${{ inputs.git-tag }}" --notes "Release ${{ inputs.git-tag }}"
fi
check-release-notes:
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
# - name: Self-test release-notes checker
# run: |
# pip install pytest
# pytest ci/tools/tests
- name: Check versioned release notes exist
run: |
python ci/tools/check_release_notes.py \
--git-tag "${{ inputs.git-tag }}" \
--component "${{ inputs.component }}" \
--backport-git-tag "${{ inputs.backport-git-tag }}"
doc:
name: Build release docs
if: ${{ github.repository_owner == 'nvidia' }}
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
id-token: write
contents: write
pull-requests: write
needs:
- check-tag
- check-release-notes
- determine-run-id
secrets: inherit
uses: ./.github/workflows/build-docs.yml
with:
component: ${{ inputs.component }}
git-tag: ${{ inputs.git-tag }}
run-id: ${{ needs.determine-run-id.outputs.run-id }}
is-release: true
deploy-docs: ${{ inputs.release-action == 'full-release' || inputs.dry-run-docs-branch != '' }}
docs-branch: ${{ (inputs.release-action == 'dry-run' && inputs.dry-run-docs-branch) || 'gh-pages' }}
upload-archive:
name: Validate release artifacts
permissions:
contents: write
needs:
- check-tag
- check-release-notes
- determine-run-id
- doc
secrets: inherit
uses: ./.github/workflows/release-upload.yml
with:
git-tag: ${{ inputs.git-tag }}
run-id: ${{ needs.determine-run-id.outputs.run-id }}
component: ${{ inputs.component }}
dry-run: ${{ inputs.release-action == 'dry-run' }}
publish-testpypi:
name: Publish wheels to TestPyPI
if: ${{ inputs.release-action == 'full-release' }}
runs-on: ubuntu-latest
needs:
- check-tag
- check-release-notes
- determine-run-id
- doc
environment:
name: testpypi
url: https://test.pypi.org/p/${{ inputs.component }}/
permissions:
id-token: write
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download component wheels
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./ci/tools/download-wheels "${{ needs.determine-run-id.outputs.run-id }}" "${{ inputs.component }}" "${{ github.repository }}" "dist"
- name: Validate wheel versions for release tag
run: |
./ci/tools/validate-release-wheels "${{ inputs.git-tag }}" "${{ inputs.component }}" "dist"
- name: Publish package distributions to TestPyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
repository-url: https://test.pypi.org/legacy/
publish-pypi:
name: Publish wheels to PyPI
if: ${{ inputs.release-action == 'full-release' }}
runs-on: ubuntu-latest
needs:
- determine-run-id
- publish-testpypi
environment:
name: pypi
url: https://pypi.org/p/${{ inputs.component }}/
permissions:
id-token: write
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download component wheels
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./ci/tools/download-wheels "${{ needs.determine-run-id.outputs.run-id }}" "${{ inputs.component }}" "${{ github.repository }}" "dist"
- name: Validate wheel versions for release tag
run: |
./ci/tools/validate-release-wheels "${{ inputs.git-tag }}" "${{ inputs.component }}" "dist"
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
# TODO: add another job to make the release leave the draft state?