Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ self-hosted-runner:
# GitHub supports queued concurrency runs, but the latest actionlint release
# does not yet recognize the concurrency.queue key.
paths:
".github/workflows/build-docs.yml":
ignore:
- 'unexpected key "queue" for "concurrency" section'
".github/workflows/cleanup-pr-previews.yml":
ignore:
- 'unexpected key "queue" for "concurrency" section'
".github/workflows/ci-workflow-health.yml":
ignore:
- 'unexpected key "queue" for "concurrency" section'
39 changes: 9 additions & 30 deletions .github/actions/doc_preview/action.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

name: Docs in PRs
description: Preview or clean up docs built from PRs
description: Preview docs built from PRs

# A re-implementation based on the logic of https://github.com/rossjrw/pr-preview-action/blob/41a957c44a456a34718e9bcf825363194db5e6d5/README.md, due to limitations illustrated in NVIDIA/cuda-python#380.

Expand All @@ -18,20 +18,23 @@ inputs:
runs:
using: composite
steps:
# The steps below are executed only when testing in a PR.
# Note: the PR previews will be removed once merged to main or release/* (see below)
- name: Deploy doc preview
if: ${{ github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }}
if: ${{ startsWith(github.ref_name, 'pull-request/') }}
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
with:
git-config-name: cuda-python-bot
git-config-email: cuda-python-bot@users.noreply.github.com
folder: ${{ inputs.source-folder }}
target-folder: docs/pr-preview/pr-${{ inputs.pr-number }}/
commit-message: "Deploy doc preview for PR ${{ inputs.pr-number }} (${{ github.sha }})"
# Every PR preview, the main docs deploy, and the coverage deploy commit to
# the shared gh-pages branch. Rebase onto a deployment that landed in the
# meantime instead of force-pushing over it, which the branch ruleset
# rejects (and which used to discard the other deployment).
force: false

- name: Leave a comment after deployment
if: ${{ github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }}
if: ${{ startsWith(github.ref_name, 'pull-request/') }}
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
with:
header: pr-preview
Expand All @@ -45,27 +48,3 @@ runs:
| <br> https://nvidia.github.io/cuda-python/pr-preview/pr-${{ inputs.pr-number }}/cuda-bindings/ <br>
| <br> https://nvidia.github.io/cuda-python/pr-preview/pr-${{ inputs.pr-number }}/cuda-pathfinder/ <br><br>
| <h6><br> Preview will be ready when the GitHub Pages deployment is complete. <br><br></h6>

# The steps below are executed only when building on main or release/*.
- name: Remove doc preview
if: ${{ github.ref_name == 'main' || startsWith(github.ref_name, 'release/') }}
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
with:
git-config-name: cuda-python-bot
git-config-email: cuda-python-bot@users.noreply.github.com
folder: ${{ inputs.source-folder }}
target-folder: docs/pr-preview/pr-${{ inputs.pr-number }}/
commit-message: "Clean up doc preview for PR ${{ inputs.pr-number }} (${{ github.sha }})"

- name: Leave a comment after removal
if: ${{ github.ref_name == 'main' || startsWith(github.ref_name, 'release/') }}
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
with:
header: pr-preview
number: ${{ inputs.pr-number }}
hide_and_recreate: true
hide_classify: "OUTDATED"
message: |
Doc Preview CI
:---:
Preview removed because the pull request was closed or merged.
Comment thread
jpascucci-nv marked this conversation as resolved.
38 changes: 3 additions & 35 deletions .github/actions/get_pr_number/action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

Expand All @@ -9,13 +9,11 @@ description: Get the PR number without relying on the pull_request* event trigge
runs:
using: composite
steps:
- name: Get PR info (non-main, non-release branch)
if: ${{ github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }}
- name: Get PR info
uses: nv-gha-runners/get-pr-info@main
id: get-pr-info

- name: Extract PR number (non-main, non-release branch)
if: ${{ github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }}
- name: Extract PR number
shell: bash --noprofile --norc -xeuo pipefail {0}
run: |
trap 'echo "Error at line $LINENO"; exit 1' ERR
Expand All @@ -26,33 +24,3 @@ runs:
fi
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
echo "BUILD_PREVIEW=1" >> $GITHUB_ENV

- name: Get PR data (main or release/* branch)
if: ${{ github.ref_name == 'main' || startsWith(github.ref_name, 'release/') }}
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
id: get-pr-data
with:
script: |
const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
commit_sha: context.sha,
owner: context.repo.owner,
repo: context.repo.repo,
});
if (!prs.data.length) {
core.setFailed("No PR associated with this commit on 'main' or 'release/*'.");
} else {
return prs.data[0];
}

- name: Extract PR number (main or release/* branch)
if: ${{ github.ref_name == 'main' || startsWith(github.ref_name, 'release/') }}
shell: bash --noprofile --norc -xeuo pipefail {0}
run: |
trap 'echo "Error at line $LINENO"; exit 1' ERR
PR_NUMBER="${{ fromJSON(steps.get-pr-data.outputs.result).number }}"
if [[ -z "$PR_NUMBER" ]]; then
echo "No associated PR found for the commit in 'main' or 'release/*'."
exit 1
fi
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
echo "BUILD_LATEST=1" >> $GITHUB_ENV
84 changes: 68 additions & 16 deletions .github/workflows/build-docs.yml
Comment thread
leofang marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ jobs:
# The build stage could fail but we want the CI to keep moving.
if: ${{ github.repository_owner == 'nvidia' && !cancelled() }}
runs-on: ubuntu-latest
outputs:
deployment-artifact-name: ${{ steps.deployment-metadata.outputs.deployment-artifact-name }}
docs-github-ref: ${{ steps.deployment-metadata.outputs.docs-github-ref }}
pr-number: ${{ steps.deployment-metadata.outputs.pr-number }}
defaults:
run:
shell: bash -el {0}
Expand Down Expand Up @@ -216,16 +220,24 @@ jobs:
# --no-deps avoids re-resolving cuda-core from PyPI during tag releases.
pip install --no-deps cuda_python*.whl

# This step sets the PR_NUMBER/BUILD_LATEST/BUILD_PREVIEW env vars.
# PR CI uses pull-request/<number> refs regardless of the target branch.
- name: Get PR number
if: ${{ !inputs.is-release }}
if: ${{ !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }}
uses: ./.github/actions/get_pr_number

- name: Set up artifact directories
- name: Mark latest docs build
if: ${{ !inputs.is-release && !startsWith(github.ref_name, 'pull-request/') }}
run: echo "BUILD_LATEST=1" >> "$GITHUB_ENV"

- name: Record deployment metadata
id: deployment-metadata
run: |
mkdir -p artifacts/docs
# create an empty folder for removal use
mkdir -p artifacts/empty_docs
echo "deployment-artifact-name=docs-deployment-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"
echo "docs-github-ref=${CUDA_PYTHON_DOCS_GITHUB_REF}" >> "$GITHUB_OUTPUT"
echo "pr-number=${PR_NUMBER:-}" >> "$GITHUB_OUTPUT"

- name: Set up artifact directories
run: mkdir -p artifacts/docs

- name: Build all docs
if: ${{ inputs.component == 'all' }}
Expand Down Expand Up @@ -265,7 +277,7 @@ jobs:
mv ${COMPONENT}/docs/build/html/* artifacts/docs/${TARGET}

- name: Write rendered docs file list
if: ${{ !inputs.is-release && github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }}
if: ${{ !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }}
run: |
find "${GITHUB_WORKSPACE}/artifacts/docs" -type f -name '*.html' ! -path '*/_static/*' \
| LC_ALL=C sort > lychee-rendered-html-files.txt
Expand All @@ -276,7 +288,7 @@ jobs:
wc -l lychee-rendered-html-files.txt

- name: Restore lychee cache
if: ${{ !inputs.is-release && github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }}
if: ${{ !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }}
id: restore-lychee-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
Expand All @@ -286,7 +298,7 @@ jobs:
docs-rendered-lychee-${{ env.PR_NUMBER }}-

- name: Check rendered docs links
if: ${{ !inputs.is-release && github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }}
if: ${{ !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }}
uses: lycheeverse/lychee-action@6da1d14f3a43098a294b7696d93d938aa8d20fc0 # unreleased: supports v0.24.x archive layout
with:
# PR-preview canonical URLs are checked by the preview deployment workflow.
Expand Down Expand Up @@ -317,7 +329,7 @@ jobs:
token: ${{ github.token }}

- name: Save lychee cache
if: ${{ always() && !inputs.is-release && github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') && steps.restore-lychee-cache.outputs.cache-hit != 'true' && steps.restore-lychee-cache.outputs.cache-primary-key != '' }}
if: ${{ always() && !inputs.is-release && startsWith(github.ref_name, 'pull-request/') && steps.restore-lychee-cache.outputs.cache-hit != 'true' && steps.restore-lychee-cache.outputs.cache-primary-key != '' }}
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .lycheecache
Expand All @@ -330,6 +342,16 @@ jobs:
path: artifacts/
retention-days: 3

- name: Upload docs deployment payload
if: ${{ inputs.deploy-docs }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ steps.deployment-metadata.outputs.deployment-artifact-name }}
path: artifacts/docs/
retention-days: 3
include-hidden-files: true
if-no-files-found: error

- name: Upload dry-run docs artifact
if: ${{ !inputs.deploy-docs || (inputs.is-release && inputs.docs-branch != 'gh-pages') }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand All @@ -338,13 +360,40 @@ jobs:
path: artifacts/docs/
retention-days: 3

- name: Deploy or clean up doc preview
if: ${{ inputs.deploy-docs && !inputs.is-release }}
deploy:
name: Deploy docs
# GitHub discards prerequisite artifacts on a new run attempt. If only this
# job fails, use "Re-run all jobs" so the build recreates its payload.
needs: build
if: ${{ inputs.deploy-docs && needs.build.result == 'success' }}
runs-on: ubuntu-latest
# Must match every job that writes to gh-pages.
concurrency:
group: cuda-python-gh-pages-publish
queue: max
cancel-in-progress: false
defaults:
run:
shell: bash -el {0}
steps:
- name: Checkout ${{ github.event.repository.name }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
ref: ${{ inputs.git-tag }}

- name: Download docs deployment payload
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ needs.build.outputs.deployment-artifact-name }}
path: artifacts/docs/

- name: Deploy doc preview
if: ${{ inputs.deploy-docs && !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }}
uses: ./.github/actions/doc_preview
with:
source-folder: ${{ (github.ref_name != 'main' && 'artifacts/docs') ||
'artifacts/empty_docs' }}
pr-number: ${{ env.PR_NUMBER }}
source-folder: artifacts/docs
pr-number: ${{ needs.build.outputs.pr-number }}

- name: Deploy doc update
if: ${{ inputs.deploy-docs && (github.ref_name == 'main' || inputs.is-release) }}
Expand All @@ -355,5 +404,8 @@ jobs:
branch: ${{ inputs.docs-branch }}
folder: artifacts/docs/
target-folder: docs/
commit-message: "Deploy ${{ (inputs.is-release && 'release') || 'latest' }} docs: ${{ env.CUDA_PYTHON_DOCS_GITHUB_REF }}"
commit-message: "Deploy ${{ (inputs.is-release && 'release') || 'latest' }} docs: ${{ needs.build.outputs.docs-github-ref }}"
clean: false
# Rebase onto concurrent PR-preview or coverage deployments instead of
# force-pushing, which the gh-pages ruleset rejects.
force: false
19 changes: 14 additions & 5 deletions .github/workflows/cleanup-pr-previews.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

Expand All @@ -19,11 +19,17 @@ on:

permissions:
contents: write # Required to push changes to gh-pages branch
pull-requests: write # Required to retire sticky preview comments

jobs:
cleanup:
name: Clean up stale PR preview folders
runs-on: ubuntu-latest
# Must match every job that writes to gh-pages.
concurrency:
group: cuda-python-gh-pages-publish
queue: max
cancel-in-progress: false
# Only run for NVIDIA org to prevent forks from running this
if: github.repository_owner == 'NVIDIA'
steps:
Expand All @@ -41,10 +47,11 @@ jobs:
git config --global user.email "cuda-python-bot@users.noreply.github.com"

- name: Run PR preview cleanup script
id: cleanup
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_PAGES_PUBLISH_LOCK: cuda-python-gh-pages-publish
run: |
# Determine if we should run in dry-run mode
if [[ "${{ inputs.dry-run }}" == "true" ]]; then
echo "Running in dry-run mode (preview only)"
./ci/cleanup-pr-previews --dry-run
Expand All @@ -58,11 +65,13 @@ jobs:
run: |
echo "### PR Preview Cleanup Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "${{ inputs.dry-run }}" == "true" ]]; then
if [[ "${{ steps.cleanup.outcome }}" != "success" ]]; then
echo "❌ Cleanup failed; no successful update was confirmed" >> $GITHUB_STEP_SUMMARY
elif [[ "${{ inputs.dry-run }}" == "true" ]]; then
echo "✅ Dry-run completed successfully" >> $GITHUB_STEP_SUMMARY
echo "No changes were made to the gh-pages branch" >> $GITHUB_STEP_SUMMARY
echo "No changes were made to the gh-pages branch or PR comments" >> $GITHUB_STEP_SUMMARY
else
echo "✅ Cleanup completed and changes pushed to gh-pages" >> $GITHUB_STEP_SUMMARY
echo "✅ Cleanup completed; gh-pages is up to date" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_STEP_SUMMARY
29 changes: 28 additions & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ jobs:
if-no-files-found: error

combine-and-deploy:
name: Combine Coverage and Deploy
name: Combine Coverage
needs: [coverage-linux, coverage-windows]
runs-on: ubuntu-latest
if: ${{ always() && github.repository_owner == 'nvidia' }}
Expand Down Expand Up @@ -566,6 +566,30 @@ jobs:
retention-days: 7
include-hidden-files: true

deploy:
name: Deploy Coverage
# GitHub discards prerequisite artifacts on a new run attempt. If only this
# job fails, use "Re-run all jobs" so coverage recreates its payload.
needs: combine-and-deploy
if: ${{ needs.combine-and-deploy.result == 'success' && github.repository_owner == 'nvidia' }}
runs-on: ubuntu-latest
permissions:
contents: write
# Must match every job that writes to gh-pages.
concurrency:
group: cuda-python-gh-pages-publish
queue: max
cancel-in-progress: false
steps:
- name: Checkout ${{ github.event.repository.name }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Download combined coverage results
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: coverage-combined
path: docs/coverage/

- name: Deploy to gh-pages
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0
with:
Expand All @@ -575,3 +599,6 @@ jobs:
target-folder: docs/
commit-message: "Deploy combined coverage (Linux + Windows): ${{ github.sha }}"
clean: false
# Rebase onto concurrent docs deployments instead of force-pushing,
# which the gh-pages ruleset rejects.
force: false
Loading
Loading