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
53 changes: 0 additions & 53 deletions .github/workflows/build-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,59 +284,6 @@ jobs:
fi
mv ${COMPONENT}/docs/build/html/* artifacts/docs/${TARGET}

- name: Write rendered docs file list
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
with:
path: .lycheecache
key: docs-rendered-lychee-${{ env.PR_NUMBER }}-${{ github.sha }}
restore-keys: |
docs-rendered-lychee-${{ env.PR_NUMBER }}-

- name: Check rendered docs 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
with:
path: .lycheecache
key: ${{ steps.restore-lychee-cache.outputs.cache-primary-key }}

- name: Upload docs GitHub Pages artifact
if: ${{ inputs.deploy-docs }}
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
Expand Down
35 changes: 32 additions & 3 deletions .github/workflows/ci-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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 +24,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 +36,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 +63,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 +327,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 +343,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
37 changes: 1 addition & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -622,38 +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: 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 @@ -674,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 @@ -712,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
177 changes: 177 additions & 0 deletions .github/workflows/lychee.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

# Check checked-in documentation and rendered HTML on PR updates and nightly.
# Build fresh HTML from sources to check Sphinx-generated links and anchors.
# This runs independently of ci.yml → build-docs.yml, which builds and deploys
# docs using CI-built wheels. Nightly caches successful link-check results for
# reuse by PR runs of this workflow.

name: "CI: Documentation links"

on:
pull_request:
types: [opened, reopened, synchronize]
workflow_call:
inputs:
concurrency-suffix:
description: "Keep a caller's test mode separate from its full run"
type: string
default: full
refresh-cache:
description: "Check links afresh and publish a new cache baseline"
type: boolean
default: false
workflow_dispatch:
inputs:
refresh-cache:
description: "Check links afresh and publish a new cache baseline"
type: boolean
default: false

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}-${{ inputs.refresh-cache || false }}-${{ inputs.concurrency-suffix || 'full' }}
cancel-in-progress: true

defaults:
run:
shell: bash --noprofile --norc -euo pipefail {0}

env:
PIXI_LOCKED: "true"
LYCHEE_VERSION: v0.24.2 # Must match the pinned rev in .pre-commit-config.yaml

jobs:
links:
name: Lychee (${{ matrix.kind }})
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
kind: [authored, rendered]
env:
# The first pass and all retries use the same inputs and cache policy.
LYCHEE_ARGS: >-
--files-from "${{ github.workspace }}/lychee-files.txt"
${{ matrix.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"
steps:
- name: Checkout checked revision
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
# Keep SCM history/tags without downloading historical source blobs.
filter: blob:none
persist-credentials: false

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

- name: Setup pixi
if: ${{ matrix.kind == 'rendered' }}
uses: ./.github/actions/setup-pixi

- name: Build all documentation from sources
if: ${{ matrix.kind == 'rendered' }}
run: |
CUDA_PYTHON_DOCS_GITHUB_REF=$(git rev-parse HEAD)
export CUDA_PYTHON_DOCS_GITHUB_REF
pixi run --manifest-path cuda_core/pixi.toml -e docs docs-build-all-latest

- name: Prepare link-check inputs and cache policy
id: inputs
env:
KIND: ${{ matrix.kind }}
POLICY_HASH: ${{ hashFiles('lychee.toml', '.github/workflows/lychee.yml', 'ci/tools/prepare_lychee_inputs.py', 'ci/tools/retry_lychee.py') }}
run: |
python ci/tools/prepare_lychee_inputs.py "${KIND}" \
--output "${GITHUB_WORKSPACE}/lychee-files.txt"
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}"

- name: Restore successful checks from the nightly baseline
if: ${{ !inputs.refresh-cache }}
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .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: ${{ env.LYCHEE_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: ${{ env.LYCHEE_VERSION }}
output: lychee-${{ matrix.kind }}.json
token: ${{ github.token }}

- name: Retry transient link failures using cached successful checks
if: ${{ steps.lychee.outcome == 'success' }}
env:
EXIT_CODE: ${{ steps.lychee.outputs.exit_code }}
REPORT: lychee-${{ matrix.kind }}.json
GITHUB_TOKEN: ${{ github.token }}
run: |
python ci/tools/retry_lychee.py --initial-exit-code "${EXIT_CODE}" \
--report "${REPORT}" --max-attempts 10

- 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 && steps.inputs.outputs.key != '' && hashFiles('.lycheecache') != '' }}
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: .lycheecache
key: ${{ steps.inputs.outputs.key }}

- name: Upload link-check report
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: lychee-${{ matrix.kind }}-${{ github.run_id }}-${{ github.run_attempt }}
path: |
lychee-${{ matrix.kind }}*.json
lychee-${{ matrix.kind }}*.md
if-no-files-found: ignore
retention-days: 7

checks:
name: Documentation links
if: ${{ always() }}
needs: links
runs-on: ubuntu-latest
steps:
- name: Require both link checks to succeed
env:
RESULT: ${{ needs.links.result }}
run: |
if [[ "${RESULT}" != "success" ]]; then
echo "::error::Documentation link checks did not succeed (${RESULT})."
exit 1
fi
Loading
Loading