Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
114 changes: 114 additions & 0 deletions .github/actions/check-doc-links/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

name: Check documentation links
description: Check authored or rendered documentation with shared nightly caches and bounded retries

inputs:
kind:
description: "Input kind: authored or rendered"
required: true
refresh-cache:
description: Check links afresh and publish a new cache baseline
default: "false"
failure-policy:
description: "check allows up to 10 rate-limited URLs; cache-warm warns on all link failures"
default: check

# Callers provide Python 3.10+ on PATH; the helpers use only its standard library.
runs:
using: composite
steps:
- name: Prepare link-check inputs and cache policy
id: inputs
shell: bash --noprofile --norc -euo pipefail {0}
env:
KIND: ${{ inputs.kind }}
LYCHEE_VERSION: v0.24.2 # Must match the pinned rev in .pre-commit-config.yaml
POLICY_HASH: ${{ hashFiles('lychee.toml', '.github/actions/check-doc-links/action.yml', 'ci/tools/prepare_lychee_inputs.py', 'ci/tools/retry_lychee.py') }}
# The first pass and all retries use the same inputs and cache policy.
LYCHEE_ARGS: >-
--files-from "${{ github.workspace }}/lychee-${{ inputs.kind }}-files.txt"
${{ inputs.kind == 'rendered' && '--include-fragments=full' || '' }}
--cache
--max-cache-age 1d
--max-concurrency 16
--host-concurrency 2
--host-request-interval 250ms
--max-retries 3
--retry-wait-time 5
--timeout 30
--no-progress
--config "${{ github.workspace }}/lychee.toml"
run: |
python "${GITHUB_WORKSPACE}/ci/tools/prepare_lychee_inputs.py" "${KIND}" \
--output "${GITHUB_WORKSPACE}/lychee-${KIND}-files.txt"
# Keep authored checks separate from rendered checks, which also
# validate fragments. Both callers must use the same cache paths.
mkdir -p "${RUNNER_TEMP}/lychee-${KIND}"
prefix="lychee-v1-${LYCHEE_VERSION}-${KIND}-${POLICY_HASH}-baseline-"
echo "prefix=${prefix}" >> "${GITHUB_OUTPUT}"
echo "key=${prefix}${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "${GITHUB_OUTPUT}"
echo "version=${LYCHEE_VERSION}" >> "${GITHUB_OUTPUT}"
echo "args=${LYCHEE_ARGS}" >> "${GITHUB_OUTPUT}"

- name: Restore successful checks from the nightly baseline
if: ${{ inputs.refresh-cache != 'true' }}
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ runner.temp }}/lychee-${{ inputs.kind }}/.lycheecache
key: ${{ steps.inputs.outputs.key }}
restore-keys: ${{ steps.inputs.outputs.prefix }}

- name: Check documentation links
id: lychee
uses: lycheeverse/lychee-action@6da1d14f3a43098a294b7696d93d938aa8d20fc0 # supports v0.24.x archive layout
with:
args: ${{ steps.inputs.outputs.args }}
# The next step owns bounded retries and the final success gate.
fail: false
# The action's empty-report guard only understands Markdown.
# Our helper validates the JSON report and rejects zero checked links.
failIfEmpty: false
format: json
jobSummary: false
lycheeVersion: ${{ steps.inputs.outputs.version }}
workingDirectory: ${{ runner.temp }}/lychee-${{ inputs.kind }}
output: ${{ github.workspace }}/lychee-${{ inputs.kind }}.json
token: ${{ github.token }}

- name: Retry transient link failures using cached successful checks
if: ${{ steps.lychee.outcome == 'success' }}
shell: bash --noprofile --norc -euo pipefail {0}
working-directory: ${{ runner.temp }}/lychee-${{ inputs.kind }}
env:
EXIT_CODE: ${{ steps.lychee.outputs.exit_code }}
REPORT: ${{ github.workspace }}/lychee-${{ inputs.kind }}.json
LYCHEE_ARGS: ${{ steps.inputs.outputs.args }}
FAILURE_POLICY: ${{ inputs.failure-policy }}
GITHUB_TOKEN: ${{ github.token }}
run: |
python "${GITHUB_WORKSPACE}/ci/tools/retry_lychee.py" \
--initial-exit-code "${EXIT_CODE}" --report "${REPORT}" \
--max-attempts 3 --policy "${FAILURE_POLICY}"

- name: Publish fresh successful checks
# Manual branch tests publish isolated branch caches. Nightly callers
# on the default branch publish the baseline that all PRs can restore.
if: ${{ always() && inputs.refresh-cache == 'true' && steps.inputs.outputs.key != '' }}
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ runner.temp }}/lychee-${{ inputs.kind }}/.lycheecache
key: ${{ steps.inputs.outputs.key }}

- name: Upload link-check report
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: lychee-${{ inputs.kind }}-${{ github.run_id }}-${{ github.run_attempt }}
path: |
${{ github.workspace }}/lychee-${{ inputs.kind }}*.json
${{ github.workspace }}/lychee-${{ inputs.kind }}*.md
if-no-files-found: ignore
retention-days: 7
57 changes: 10 additions & 47 deletions .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,58 +284,21 @@ jobs:
fi
mv ${COMPONENT}/docs/build/html/* artifacts/docs/${TARGET}

- name: Write rendered docs file list
# Keep link checks in the existing PR docs build, using the same revision
# as its wheels. Nightly supplies shared caches for both kinds of inputs.
- name: Check authored documentation links
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
if [[ ! -s lychee-rendered-html-files.txt ]]; then
echo "error: no rendered HTML pages found for lychee" >&2
exit 1
fi
wc -l lychee-rendered-html-files.txt

- name: Restore lychee cache
if: ${{ !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }}
id: restore-lychee-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
timeout-minutes: 60
uses: ./.github/actions/check-doc-links
with:
path: .lycheecache
key: docs-rendered-lychee-${{ env.PR_NUMBER }}-${{ github.sha }}
restore-keys: |
docs-rendered-lychee-${{ env.PR_NUMBER }}-
kind: authored

- name: Check rendered docs links
- name: Check rendered documentation links
if: ${{ !inputs.is-release && startsWith(github.ref_name, 'pull-request/') }}
uses: lycheeverse/lychee-action@6da1d14f3a43098a294b7696d93d938aa8d20fc0 # unreleased: supports v0.24.x archive layout
with:
args: >-
--files-from ${{ github.workspace }}/lychee-rendered-html-files.txt
--include-fragments=full
--cache
--max-cache-age 1d
--max-concurrency 16
--host-concurrency 2
--host-request-interval 250ms
--max-retries 3
--retry-wait-time 5
--timeout 30
--no-progress
--config ${{ github.workspace }}/lychee.toml
fail: true
failIfEmpty: true
format: markdown
jobSummary: false
lycheeVersion: v0.24.2
output: lychee-rendered-html.md
token: ${{ github.token }}

- name: Save lychee cache
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
timeout-minutes: 60
uses: ./.github/actions/check-doc-links
with:
path: .lycheecache
key: ${{ steps.restore-lychee-cache.outputs.cache-primary-key }}
kind: rendered

- name: Upload docs GitHub Pages artifact
if: ${{ inputs.deploy-docs }}
Expand Down
37 changes: 34 additions & 3 deletions .github/workflows/ci-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
# against the latest cuda-python wheels built on main, runs the standard
# test suite on runners reserved for nightly-only use (e.g. arm64 l4×2), and
# exercises release-time CI helper scripts so they do not silently rot between releases.
# It also builds documentation from source to warm the shared Lychee cache;
# unresolved links are warnings, while build and checker errors remain failures.
#
# This workflow does NOT build wheels — it downloads them from the latest
# successful CI run on main and runs integration/standard tests.

name: "CI: Nightly optional-deps"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.documentation-links-only || false }}
cancel-in-progress: true

on:
Expand All @@ -24,6 +26,10 @@ on:
- cron: "17 2 * * *"
workflow_dispatch:
inputs:
documentation-links-only:
description: "Test the nightly link checker without wheel or GPU jobs"
type: boolean
default: false
run-id:
description: >
Override the CI run ID to download artifacts from.
Expand All @@ -32,6 +38,16 @@ on:
default: ''

jobs:
documentation-links:
name: "Nightly: Documentation links"
if: ${{ github.repository_owner == 'nvidia' }}
permissions:
contents: read
uses: ./.github/workflows/lychee.yml
with:
concurrency-suffix: ${{ inputs.documentation-links-only && 'links-only' || 'full' }}
refresh-cache: true

test-ci-tools-for-release:
name: "Nightly: CI tools for release"
if: ${{ github.repository_owner == 'nvidia' }}
Expand All @@ -49,7 +65,7 @@ jobs:
python -m pytest -v --noconftest ci/tools/tests

find-wheels:
if: ${{ github.repository_owner == 'nvidia' }}
if: ${{ github.repository_owner == 'nvidia' && !inputs.documentation-links-only }}
runs-on: ubuntu-latest
outputs:
RUN_ID: ${{ steps.find.outputs.run_id }}
Expand Down Expand Up @@ -313,6 +329,7 @@ jobs:
if: ${{ always() && github.repository_owner == 'nvidia' }}
runs-on: ubuntu-latest
needs:
- documentation-links
- test-ci-tools-for-release
- find-wheels
- test-pytorch-linux
Expand All @@ -328,13 +345,27 @@ jobs:
- test-standard-linux-aarch64
steps:
- name: Exit
env:
LINKS_ONLY: ${{ inputs.documentation-links-only || false }}
NEEDS_JSON: ${{ toJSON(needs) }}
run: |
if [[ "${LINKS_ONLY}" == "true" ]]; then
# GPU jobs depend on find-wheels and must stay skipped in this mode.
jq -e 'all(to_entries[];
if .key == "documentation-links" or .key == "test-ci-tools-for-release"
then .value.result == "success"
else .value.result == "skipped"
end)' <<< "${NEEDS_JSON}"
exit 0
fi

# If any dependency was cancelled or failed, that's a failure.
#
# See ci.yml for the full rationale on why we must use always()
# and explicitly check each result rather than relying on the
# default behaviour.
if ${{ needs.test-ci-tools-for-release.result == 'cancelled' ||
if ${{ needs.documentation-links.result != 'success' ||
needs.test-ci-tools-for-release.result == 'cancelled' ||
needs.test-ci-tools-for-release.result == 'failure' ||
needs.find-wheels.result != 'success' }}; then
exit 1
Expand Down
40 changes: 1 addition & 39 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -622,41 +622,6 @@ jobs:
with:
is-release: ${{ github.ref_type == 'tag' }}

precommit-windows:
name: Pre-commit on Windows
runs-on: windows-latest
if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) }}
needs:
- should-skip
permissions:
contents: read
steps:
- name: Enable Git symlinks (Windows, must precede checkout)
run: git config --global core.symlinks true

- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false

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

- name: Install pre-commit
shell: bash
run: |
set -euxo pipefail
python -m pip install --upgrade pip pre-commit

- name: Run pre-commit
shell: bash
run: |
set -euxo pipefail
SKIP=lychee pre-commit run --all-files

checks:
name: Check job status
if: ${{ always() && github.repository_owner == 'nvidia' }}
Expand All @@ -677,7 +642,6 @@ jobs:
- api-check-core-vs-release
- api-check-core-vs-base
- doc
- precommit-windows
steps:
- name: Exit
env:
Expand Down Expand Up @@ -715,14 +679,12 @@ jobs:
fi
}

# Control jobs, the universal linux build, docs, and Windows
# pre-commit checks always run.
# Control jobs, the universal Linux build, and docs always run.
check_result "ci-vars" "success"
check_result "should-skip" "success"
check_result "detect-changes" "success"
check_result "build-linux-64" "success"
check_result "doc" "success"
check_result "precommit-windows" "success"

# Optional platform builds and wheel tests share the platform plan.
linux_expected="skipped"
Expand Down
Loading
Loading