Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/RELEASE-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!--
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->

# Releasing CUDA bindings

## Choose the release scope and source

Develop the current and maintenance CUDA major lines together on `main`, in
the two package roots listed in [`ci/versions.yml`](../ci/versions.yml).
Release each line independently. A bindings release also needs its matching
`cuda-python` metapackage release; it does not require a release of the other
CUDA major, `cuda-core`, or `cuda-pathfinder`.

Major-line maintenance may include selected API, Python, platform, or other
compatibility changes. Decide those changes explicitly and describe them in
the release notes. Once a patch release is frozen, admit only its approved
fixes and release prerequisites. Do not infer patch-release scope solely
from the registry's `maintenance` role.

For an ordinary release, select a tested commit on `main`. If stabilization
must continue while development advances, create a short release branch such
as `release/bindings-12.9.11` from the chosen commit. For an emergency after a
release, start from its exact tag, provided that tag contains the new package
layout. For the first release after this transition, use a validated commit
containing both roots. Keep routine development on `main`; the historical
`12.9.x` branch stays frozen except for emergency fixes using compatible
historical tooling. Reconcile those fixes into the main-tree maintenance
root. Keep the package layout and release tooling together on new release branches.

Record the source commit, intended tag, selected package root, release owner,
and accepted changes in the release checklist. A branch is a temporary scope
boundary, not a separate long-term home for shared infrastructure.

## Move fixes between integration and release branches

Prefer a reviewed fix on `main`, then backport it to the selected release
branch. Run **CI: Backport a merged PR to a release branch** from the Actions
tab on `main`, supplying the merged PR number and one existing target branch.
The workflow opens a PR; it does not merge or release the result. Inspect the
diff, request CI using the normal PR approval procedure, and review it before
merging. Workflow-file changes require a manual backport with appropriate
credentials. Conflicts and toolkit-specific generated changes require an
explicitly reviewed adaptation.

If urgency requires a fix on the release branch first, link a follow-up PR
reconciling the fix back into `main` before closing the release checklist. In either
direction, assess both bindings roots and record why a change applies to one
or both. Regenerate affected generated files from the intended toolkit and
cybind inputs instead of blindly copying generated output between majors.
See the [shared maintenance policy](../ci/README.md#maintaining-both-bindings-roots).

## Validate and publish the selected line

1. Finalize the bindings and metapackage release notes, dependency constraints,
and selected toolkit pin in the release source tree. The registry and
packaging metadata must agree. Run the CI-tool suite and pre-commit checks.
2. Test the candidate through a reviewed PR or manual CI dispatch on its
branch. Normal development CI includes dependent packages and may cover
both majors. Resolve failures in affected code before selecting the tag.
3. Create the approved immutable `vX.Y.Z` tag at the selected source commit.
The tag-triggered CI selects that line's bindings and metapackage, validates
them with the published Pathfinder dependency, and builds release artifacts.
The tag does not have to be on `main`. Wait for this exact tag's CI to pass.
4. Run **CI: Release** with `release-action=dry-run`, the tag, and
`component=cuda-bindings`; repeat for `cuda-python`. Leave `run-id` blank
to exercise automatic lookup, and leave `dry-run-docs-branch` blank to avoid
deploying docs. The workflow verifies a successful tag-triggered CI run
with the exact source SHA, validates artifacts, and builds docs.
5. Review the dry runs, then separately approve and run `full-release` for
each component under the team's release procedure. A successful candidate
rehearsal or ordinary branch CI is not a substitute for exact-tag CI.
6. After both component releases succeed, review and publish the shared
`vX.Y.Z` GitHub Release draft. Check its release notes, versioned
documentation links, source archive, checksum, and wheels. These component
workflows leave the draft for the release owner to publish.
7. Finish post-release QA and announcements, return every applicable fix to
`main`, and close the stabilization effort. Retain immutable tags and the
branch history needed for diagnosis.

For either bindings line, add the released version to
[`cuda_bindings_13/docs/nv-versions.json`](../cuda_bindings_13/docs/nv-versions.json)
and its matching metapackage version to
[`cuda_python/docs/nv-versions.json`](../cuda_python/docs/nv-versions.json)
on `main`. Documentation deployments from `main` own these shared version
selectors. Release deployments publish versioned pages without replacing the
selectors or shared `latest` pages; updating a selector only on a stabilization
branch will not update the public selector.

The workflow revision used for a release is its **control revision**; the
tagged commit is its **source revision**. Record both and the artifact CI run
ID. Release routing uses the package layout and toolkit settings in
`ci/versions.yml` at the release tag. The current tooling requires a valid
schema-2 registry in that source, for both dry runs and publication, including
Core and Pathfinder releases. Tags from before this registry was introduced
require compatible historical tooling; the current workflow configuration
cannot supply missing tagged configuration. Published releases remain unchanged.

In **CI: Release**, normal bindings and metapackage releases each require
their own exact-version, nonempty release notes in the tagged source. Notes
from the workflow control revision or a different component cannot fill a gap.
Preserve their existing exemption for `.postN` releases, which do not require
new notes.

Local synthetic tags used in publication-incapable rehearsals must remain local;
they do not authorize publishing a package, creating a remote tag, or moving
an existing tag.
33 changes: 20 additions & 13 deletions .github/RELEASE-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@

# cuda.core Release Process

This document covers the `cuda.core` release process. For other packages:
`cuda-bindings` and `cuda-python` involve a private repository and are not
documented here; `cuda-pathfinder` is largely automated by the
[CI: Release](workflows/release.yml)
workflow.
This document covers the `cuda.core` release process. See the
[bindings release guide](RELEASE-bindings.md) for `cuda-bindings` and
`cuda-python`. The `cuda-pathfinder` release process is largely automated by
the [CI: Release](workflows/release.yml) workflow.

To release `cuda-pathfinder`, dispatch **CI: Release** with
`component` set to `cuda-pathfinder`, `release-action` set
Expand Down Expand Up @@ -176,21 +175,29 @@ Pushing the tag triggers a CI run automatically. Monitor it in the

## Upload wheels to PyPI

This is a single `CI: Release` workflow run with two sequential stages:
publish to TestPyPI, then publish the same wheel set to PyPI.
First validate the release with a dry run. After reviewing that result, a
`full-release` run publishes to TestPyPI, then publishes the same wheel set to
PyPI.

1. Go to **Actions > CI: Release** and run the workflow with:
- **Component**: `cuda-core`
- **The release git tag**: `cuda-core-v0.6.0`
- **What to run**: `dry-run`

The workflow automatically looks up the successful tag-triggered CI run
for the selected release tag.
Leave `run-id` blank to exercise automatic lookup of the successful
tag-triggered CI run. Leave `dry-run-docs-branch` blank to validate the docs
without deploying them.

2. Wait for the workflow to complete. It will:
- publish the selected wheels to TestPyPI
- publish the same wheel set to PyPI
2. Review the completed dry run, then approve publication under the team's
release procedure and run the workflow for the same component and tag with
**What to run** set to `full-release`.

3. After completion, verify the final PyPI upload:
3. Wait for that run to complete. It deploys versioned documentation and uploads
the source archive, checksum, and wheels to the GitHub Release draft. It
publishes the selected wheel set to TestPyPI followed by PyPI. The release
owner publishes the GitHub Release draft in the announcement step.

4. After completion, verify the final PyPI upload:

```bash
pip install cuda-core==0.6.0
Expand Down
20 changes: 0 additions & 20 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,3 @@ updates:
time: "09:00"
timezone: "America/Los_Angeles"
open-pull-requests-limit: 1

# GitHub Actions updates targeting the 12.9.x branch
- package-ecosystem: github-actions
directory: /
target-branch: "12.9.x" # keep in sync with backport_branch in ci/versions.yml
schedule:
interval: "monthly"
time: "09:00"
timezone: "America/Los_Angeles"

# Keep churn down: only one open PR from this ecosystem at a time
open-pull-requests-limit: 1

groups:
actions-monthly:
applies-to: version-updates
patterns: ["*"]
update-types: ["major", "minor", "patch"]

labels: [CI/CD, dependencies]
105 changes: 57 additions & 48 deletions .github/workflows/backport.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,84 @@
#
# SPDX-License-Identifier: Apache-2.0

name: "CI: Backport the merged PR"
name: "CI: Backport a merged PR to a release branch"

on:
pull_request_target:
types: [closed, labeled]
branches:
- main
workflow_dispatch:
inputs:
backport-branch:
description: "Branch to backport commits onto"
required: false
description: "Existing release branch to receive the fix"
required: true
type: string
pull-request:
description: "PR to backport"
description: "Merged PR to backport"
required: true
type: number


permissions:
contents: write # so it can comment
pull-requests: write # so it can create pull requests
contents: write
pull-requests: write

concurrency:
group: backport-${{ inputs.pull-request }}-${{ inputs.backport-branch }}
cancel-in-progress: false

jobs:
backport-from-pr:
name: Backport directly from a pull request
if: ${{ github.repository_owner == 'nvidia' &&
github.event.pull_request.merged == true &&
contains( github.event.pull_request.labels.*.name, 'to-be-backported')
}}
backport:
if: github.repository == 'NVIDIA/cuda-python'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Checkout workflow source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}

- name: Load branch name
id: get-branch
- name: Validate source PR and target branch
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
BACKPORT_BRANCH: ${{ inputs.backport-branch }}
PR_NUMBER: ${{ inputs.pull-request }}
run: |
OLD_BRANCH=$(yq '.backport_branch' ci/versions.yml)
echo "OLD_BRANCH=${OLD_BRANCH}" >> $GITHUB_ENV
set -euo pipefail
if [[ ! "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
echo "::error::The source PR must be a positive integer."
exit 1
fi
if ! git check-ref-format "refs/heads/$BACKPORT_BRANCH"; then
echo "::error::Provide one exact target branch name."
exit 1
fi
if [[ "$BACKPORT_BRANCH" == "$DEFAULT_BRANCH" ]]; then
echo "::error::Choose a release branch, not the default branch."
exit 1
fi
if ! git ls-remote --exit-code --heads origin "refs/heads/$BACKPORT_BRANCH"; then
echo "::error::The target branch must already exist in this repository."
exit 1
fi
if [[ "$(gh pr view "$PR_NUMBER" --repo "$GH_REPO" --json state --jq .state)" != MERGED ]]; then
echo "::error::The source PR must already be merged in this repository."
exit 1
fi

- name: Create backport pull requests
# Workflow-file changes need a manual backport because GITHUB_TOKEN
# cannot push them. Generated changes need target-specific regeneration.
- name: Create backport pull request
id: backport
uses: korthout/backport-action@8560fb503c275d433c56f05a2f64850093baa9f7 # v4.7.0
with:
source_pr_number: ${{ inputs.pull-request }}
target_branches: ${{ inputs.backport-branch }}
# The explicit target must not be combined with source PR labels.
label_pattern: ''
copy_assignees: true
copy_labels_pattern: true
copy_labels_pattern: '^(?!to-be-backported$|backport ).*'
copy_requested_reviewers: true
target_branches: ${{ env.OLD_BRANCH }}
backport-to-branch:
name: Backport a specific PR against a specific branch
if: github.repository_owner == 'nvidia' && github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Load branch from environment name
if: inputs.backport-branch == null
- name: Require successful backport
if: steps.backport.outputs.was_successful != 'true'
run: |
BACKPORT_BRANCH=$(yq '.backport_branch' ci/versions.yml)
echo "BACKPORT_BRANCH=${BRANCH}" >> $GITHUB_ENV

- name: Load branch name from input
if: inputs.backport-branch != null
run: echo "BACKPORT_BRANCH=${{ inputs.backport-branch }}" >> $GITHUB_ENV

- name: Create backport pull requests
uses: korthout/backport-action@8560fb503c275d433c56f05a2f64850093baa9f7 # v4.7.0
with:
copy_assignees: true
copy_labels_pattern: true
copy_requested_reviewers: true
target_branches: ${{ env.BACKPORT_BRANCH }}
source_pr_number: ${{ inputs.pull-request }}
echo "::error::Backport failed; resolve the conflict or prepare the target-specific fix manually."
exit 1
23 changes: 23 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ inputs, CI selection, or release routing.
enforce their byte-for-byte invariants. Semantic differences elsewhere do
not relax those requirements.

## Synchronized code blocks

Use matching `BEGIN SYNCED <group>` and `END SYNCED <group>` comments for
deliberately duplicated snippets whose contents must remain byte-for-byte
identical. The group name identifies all copies; the boundaries define the
shared snippet, not the surrounding file.

- Before editing a marked block, search from the repository root for its exact
`BEGIN SYNCED <group>` marker, for example with `rg -n -F`. Read every copy
and update them together, including copies outside the current package.
- Compare the complete marked blocks after editing. Also check required
imports and surrounding assumptions, such as relative directory depth,
which may live outside the markers.
- During review, identify groups touched by the diff and compare every copy,
including files absent from the diff. Use the base revision too when markers
were removed or renamed. Report drift, missing boundaries, and newly copied
snippets that omit the markers as review findings.
- Preserve both markers when copying a snippet. If a copy must diverge,
explain why and explicitly revise the group membership; do not remove or
rename a marker merely to conceal drift.
- In the work or review summary, name the groups and files compared, or state
why verification could not be completed.

# Git-derived version lifecycle

Package versions are derived from reachable Git tags by `setuptools-scm`.
Expand Down
Loading
Loading