Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ closes <!-- Link issue here -->
<!-- TODO: - [ ] I am familiar with the [Contributing Guidelines](). -->
- [ ] New or existing tests cover these changes.
- [ ] The documentation is up to date with these changes.
- [ ] A release note is added (or the `skip-release-note` label is applied if none is needed).
33 changes: 28 additions & 5 deletions .github/RELEASE-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,32 @@ functions, classes, or features were added and whether they have complete
docstrings. Add or edit docstrings as needed — touching docstrings and
type annotations in code is OK during code freeze.

Write the release notes in `cuda_core/docs/source/release/`. Look at
historical release notes for guidance on format and structure. Balance all
entries for length, specificity, tone, and consistency. Highlight a few
notable items in the highlights section, keeping their full entries in the
appropriate sections below.
Release notes are not written at release time. Since 1.3.0 they are
per-change note files, added by each PR under `cuda_core/releasenotes/` (see "Release notes" in
[CONTRIBUTING.md](../CONTRIBUTING.md#release-notes)). The docs build generates
the page for each release from the notes in git: a minor release lists the
notes added since the previous minor release, and a patch release lists those
added since the previous release of the same `X.Y` line. Earlier releases keep
their hand-written pages in `cuda_core/docs/source/release/`.

So the work here is to review and polish the notes, and to do it *before* the
tag is cut, because the page is built from the tagged commit:

- Read the "In development" page of the latest docs, which shows the notes that
will go into the next release (or build the docs locally from a full clone).
Balance all entries for length, specificity, tone, and consistency, and edit
the note files in a PR. Edits to notes that already shipped in an earlier
release also change that release's page the next time the docs are published
from `main`, so older notes can be corrected too.
- Highlight a few notable items by adding a note with a `prelude` section. All
preludes in the release are combined at the top of its page, so the full
entries stay in their own sections below.
- Check the known issues (`issues` entries). They are listed on every release
until their entry is deleted, so delete the ones that this release resolves
and make sure the remaining ones are still true.
- Make sure the notes make it to `main`. The release workflow fails if no note
was added in the release's range, but that does not mean every PR had one
(the PR check allows a `skip-release-note` label); skim the list of PRs.

---

Expand All @@ -126,6 +147,8 @@ certain the tag points to the correct commit before pushing.

Tags should be GPG-signed. The tag name format is `cuda-core-v<VERSION>`
(e.g. `cuda-core-v0.6.0`). The tag must point to a commit on `main`.
All release notes and docstring edits must already be merged, because the
release notes page is generated from the tagged commit.

```bash
git checkout main
Expand Down
28 changes: 28 additions & 0 deletions .github/actions/fetch-release-history/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

name: Fetch release history

description: >-
Complete a shallow checkout with the git history needed to build release
notes (see cuda_python/docs/exts/release_notes.py): the history of the
checked-out commit and of every tag. No branches are fetched, which also
leaves out the large gh-pages branch that holds only built docs.

runs:
using: composite
steps:
- name: Fetch the checked-out commit's history and all tags
shell: bash --noprofile --norc -xeuo pipefail {0}
run: |
# Run after actions/checkout with its default fetch-depth of 1.
# Release notes are read from the history reachable from release tags
# (patch tags may sit on maintenance branches) and from the commit being
# built, so no branch names are needed.
refspecs=("$(git rev-parse HEAD)" '+refs/tags/*:refs/tags/*')
if [[ "$(git rev-parse --is-shallow-repository)" == "true" ]]; then
git fetch --unshallow --no-tags origin "${refspecs[@]}"
else
git fetch --no-tags origin "${refspecs[@]}"
fi
4 changes: 4 additions & 0 deletions .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ jobs:
fetch-depth: 1
ref: ${{ inputs.git-tag }}

# The release notes are built from the git history between release tags.
- name: Fetch release history
uses: ./.github/actions/fetch-release-history

- name: Read build CTK version
run: |
if [[ -f ci/versions.yml ]]; then
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:

- name: Run CI tools unit tests
run: |
python -m pip install pytest
python -m pip install -r ci/tools/tests/requirements.txt
# Standalone CI tool tests; skip repo-root conftest.py (imports cuda.pathfinder).
python -m pytest -v --noconftest ci/tools/tests

Expand Down
36 changes: 30 additions & 6 deletions .github/workflows/pr-metadata-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
- ready_for_review

permissions:
contents: read
pull-requests: read

jobs:
Expand All @@ -26,7 +27,17 @@ jobs:
if: github.repository_owner == 'NVIDIA'
runs-on: ubuntu-latest
steps:
- name: Check metadata and blocking labels
# pull_request_target checks out the base branch, never the PR's code, so
# the scripts that run below are the trusted ones.
- name: Checkout check scripts
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
ci/tools
cuda_python/docs/exts
persist-credentials: false

- name: Check metadata, blocking labels, and release notes
env:
PR_URL: ${{ github.event.pull_request.html_url }}
PR_NUMBER: ${{ github.event.pull_request.number }}
Expand Down Expand Up @@ -72,14 +83,27 @@ jobs:
ERRORS="${ERRORS}- **Blocked label detected**: label \`$label\` prevents merging. Remove it when the PR is ready.\n"
done <<<"$BLOCKED_LABELS"

# Release notes are required for every PR that changes package sources.
set +e
RELEASE_NOTE_PROBLEMS=$(python3 ci/tools/check_pr_release_notes.py \
--repo "$GH_REPO" --pr "$PR_NUMBER" --labels-json "$LABELS")
RELEASE_NOTE_STATUS=$?
set -e
if [ "$RELEASE_NOTE_STATUS" -eq 3 ]; then
ERRORS="${ERRORS}${RELEASE_NOTE_PROBLEMS}\n"
elif [ "$RELEASE_NOTE_STATUS" -ne 0 ]; then
echo "::error::The release-note check itself failed (exit status ${RELEASE_NOTE_STATUS})."
exit "$RELEASE_NOTE_STATUS"
fi

# Bots cannot provide positive metadata such as an assignee or
# milestone. Once blocking labels have been checked, exempt them
# from the remaining requirements.
# milestone. Once blocking labels and release notes have been
# checked, exempt them from the remaining requirements.
if [ "$IS_BOT" = "true" ]; then
if [ -n "$ERRORS" ]; then
echo "::error::This PR has a label that prevents merging. See the job summary for details."
echo "::error::This PR has a label that prevents merging or is missing a release note. See the job summary for details."
{
echo "## PR Blocking Label Check Failed"
echo "## PR Check Failed"
echo ""
printf '%b' "$ERRORS"
echo ""
Expand All @@ -89,7 +113,7 @@ jobs:
fi

{
echo "## PR Blocking Label Check Passed"
echo "## PR Blocking Label and Release Note Check Passed"
echo ""
echo "Bot-authored PR; positive metadata requirements are exempt."
} >> "$GITHUB_STEP_SUMMARY"
Expand Down
19 changes: 12 additions & 7 deletions .github/workflows/release-cuda-pathfinder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,21 @@ jobs:
# Resolve only the exact tag ref; checkout fails if the tag does not exist.
ref: refs/tags/${{ steps.vars.outputs.tag }}

# The release notes are found from the git history between release tags.
- name: Fetch release history
uses: ./.github/actions/fetch-release-history

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

# Hand-written notes (before 1.9.0) or note files added in the release's range.
- name: Check release notes exist
env:
VERSION: ${{ steps.vars.outputs.version }}
TAG: ${{ steps.vars.outputs.tag }}
run: |
notes="cuda_pathfinder/docs/source/release/${VERSION}-notes.rst"
if [[ ! -f "${notes}" ]]; then
echo "::error::Release notes not found: ${notes}"
echo "Create the release notes file before running this workflow."
exit 1
fi
python ci/tools/check_release_notes.py --git-tag "${TAG}" --component cuda-pathfinder

- name: Detect CI run ID
id: detect-run
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ jobs:
with:
ref: ${{ inputs.git-tag }}

# The release notes are found from the git history between release tags.
- name: Fetch release history
uses: ./.github/actions/fetch-release-history

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
Expand Down
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ repos:
files: '^(ci/versions\.yml|cuda_bindings/pixi\.toml|cuda_core/pixi\.toml)$'
pass_filenames: false

- id: release-notes-lint
name: Lint release notes
entry: python ./ci/tools/lint_release_notes.py
language: python
additional_dependencies: [pyyaml==6.0.3]
files: '^(cuda_bindings|cuda_core|cuda_pathfinder|cuda_python)/releasenotes/'
pass_filenames: false

- id: check-mempool-hygiene
name: Check tests do not create uncapped memory pools
entry: python ./ci/tools/check_mempool_hygiene.py
Expand Down
103 changes: 103 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ Thank you for your interest in contributing to CUDA Python! Based on the type of
- [Pre-commit lychee workaround](#pre-commit-lychee-workaround)
- [Type stubs for cuda.core](#type-stubs-for-cudacore)
- [Pre-commit](#pre-commit)
- [Release notes](#release-notes)
- [Writing a release note](#writing-a-release-note)
- [Known issues](#known-issues)
- [How release notes are assembled](#how-release-notes-are-assembled)
- [Pixi lockfiles](#pixi-lockfiles)
- [Secret Scanning](#secret-scanning)
- [Signing Your Work](#signing-your-work)
- [Code signing](#code-signing)
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
Expand Down Expand Up @@ -234,6 +239,104 @@ warning reminding you to run `pre-commit install`.

Windows contributors: see [Pre-commit lychee workaround](#pre-commit-lychee-workaround) under Development on Windows.

## Release notes

Release notes are one small YAML file per change, kept next to the code in the
PR that makes the change. (The design is inspired by
[reno](https://docs.openstack.org/reno/latest/), but behaves quite differently.)
All four packages work this way: `cuda-bindings` and
`cuda-python` (13.5.0 and later), `cuda-core` (1.3.0 and later) and
`cuda-pathfinder` (1.9.0 and later), with notes in
`<package>/releasenotes/`. Earlier releases keep their hand-written pages
under `<package>/docs/source/release/`.

### Writing a release note

Every pull request that changes the sources of one of these packages needs a
release note for it, or an edit to an existing one (a PR touching multiple
packages needs one for each). Changes to docs, tests, examples, and CI do not
need one. A CI check enforces this. If a PR touches the sources but truly needs
no note, a maintainer can apply the `skip-release-note` label.

Create a note with the `add_note.py` script. It can be run from any directory,
and takes the package and a few words describing the change:

```bash
python toolshed/add_note.py cuda-core "Fix the thing" [--edit]
```

The package is one of `cuda-bindings`, `cuda-core`, `cuda-pathfinder` and
`cuda-python` (the `cuda-` prefix is optional). The new file is created in
`<package>/releasenotes/`. `--edit` opens it in `$VISUAL` or `$EDITOR`.

Edit the new file: keep the sections that apply and delete the rest, and replace
the `TODO` text. Each entry is reStructuredText, written for users of the
package. A pre-commit hook checks the notes for obvious mistakes, such as a
misspelled section or leftover `TODO` text.

| Section | Use for |
|---|---|
| `features` | New functionality, including experimental APIs (say so in the text) |
| `upgrade` | Changes users may need to act on, including behavior changes |
| `deprecations` | Newly deprecated functionality |
| `critical` | Critical issues users must know about |
| `security` | Security issues |
| `fixes` | Bug fixes |
| `issues` | Known issues (see below) |
| `other` | Anything else |
| `prelude` | An introduction to a release's notes. Useful to highlight important themes in a release. |

Some details:

* You do not need to add a link to the pull request: one is added automatically,
taken from the `(#N)` in the squash-merge commit that introduced the note.
* To name the pull request(s) yourself instead, write `(#1234)` or
`(#1234, #1235)` anywhere in an entry, usually at the end. Each marker is
replaced with links, and the automatic lookup is skipped for that entry. This
is rarely needed. It is for a note whose file is added by a different PR than
the one that made the change, for example a note written afterwards or notes
moved in from another place. The marker can go anywhere in ordinary text. A
`(#N)` inside backquoted text, such as an inline literal, is left alone, so
it can be used in an example. Do not put a marker inside a literal block, or
the rows of a table or other directive content, where the link text could
break the markup.
* Do not rename or move a note after it is merged.
* Which releases list a note is decided from git, so a note shows up in a local
docs build only once it is committed.
* You can fix a note that is already part of a release (a typo, or a clearer
wording). The docs are published from `main`, and every page shows a note's
current text, so the fix appears on the pages of the releases that list it
the next time the docs are published. Editing a note never moves it to
another release.

### Known issues

Unlike the other sections, which appear only on the release that first contains
them, known issues are listed on **every** release until they are resolved. To
record one, add an `issues` entry to a note. To resolve it, delete the entry (or
the whole note, if it has nothing else) in the PR that fixes the problem, and
describe the fix in a new `fixes` entry. The pages of releases that were already
tagged keep listing the issue, with the text it had when its note was deleted.

### How release notes are assembled

The docs build generates a page for every release from the notes in git,
with a final "In development" page for notes that are not in any release yet.
Only plain `vX.Y.Z` tags count as releases. Pre-release tags (`rc`, `a`, `b`)
are ignored, and their notes roll into the final release.

* A minor release (`X.Y.0`) lists the notes added since the previous minor
release.
* A patch release lists the notes added since the previous release of the same
`X.Y` line.
* A note that is backported appears on the patch release page and again on the
page of the next minor release that contains it. This is intentional.

Building the docs needs the history of the checked-out commit and of every release
tag (no branches), and the build fails on a shallow clone rather than produce incomplete
pages. In CI, the `.github/actions/fetch-release-history` action does this. The
rules are documented in `cuda_python/docs/exts/release_notes.py`.

## Pixi lockfiles

The repository checks in a `pixi.lock` next to each `pixi.toml`. Those lockfiles
Expand Down
Loading
Loading