Merge remote-tracking branch 'upstream/main' into cuda-core-system-ju… #3383
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # Note: This name is referred to in the test job, so make sure any changes are sync'd up! | |
| # Further this is referencing a run in the backport branch to fetch old bindings. | |
| name: "CI" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} | |
| cancel-in-progress: true | |
| on: | |
| push: | |
| branches: | |
| - "pull-request/[0-9]+" | |
| - "main" | |
| tags: | |
| # Build release artifacts from tag refs so setuptools-scm resolves exact | |
| # release versions instead of .dev+local variants. | |
| - "v*" | |
| - "cuda-core-v*" | |
| - "cuda-pathfinder-v*" | |
| schedule: | |
| # every 24 hours at midnight UTC | |
| - cron: "0 0 * * *" | |
| jobs: | |
| ci-vars: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| CUDA_BUILD_VER: ${{ steps.get-vars.outputs.cuda_build_ver }} | |
| CUDA_PREV_BUILD_VER: ${{ steps.get-vars.outputs.cuda_prev_build_ver }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get CUDA build versions | |
| id: get-vars | |
| run: | | |
| cuda_build_ver=$(yq '.cuda.build.version' ci/versions.yml) | |
| echo "cuda_build_ver=$cuda_build_ver" >> $GITHUB_OUTPUT | |
| cuda_prev_build_ver=$(yq '.cuda.prev_build.version' ci/versions.yml) | |
| echo "cuda_prev_build_ver=$cuda_prev_build_ver" >> $GITHUB_OUTPUT | |
| should-skip: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| skip: ${{ steps.get-should-skip.outputs.skip }} | |
| doc-only: ${{ steps.get-should-skip.outputs.doc_only }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Compute whether to skip builds and tests | |
| id: get-should-skip | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euxo pipefail | |
| if ${{ startsWith(github.ref_name, 'pull-request/') }}; then | |
| pr_number="$(grep -Po '(\d+)$' <<< '${{ github.ref_name }}')" | |
| pr_title="$(gh pr view "${pr_number}" --json title --jq '.title')" | |
| skip="$(echo "${pr_title}" | grep -q '\[no-ci\]' && echo true || echo false)" | |
| doc_only="$(echo "${pr_title}" | grep -q '\[doc-only\]' && echo true || echo false)" | |
| else | |
| skip=false | |
| doc_only=false | |
| fi | |
| echo "skip=${skip}" >> "$GITHUB_OUTPUT" | |
| echo "doc_only=${doc_only}" >> "$GITHUB_OUTPUT" | |
| # Detect which top-level modules were touched by the PR so downstream build | |
| # and test jobs can avoid rebuilding/retesting modules unaffected by the | |
| # change. See issue #299. | |
| # | |
| # Dependency graph (verified in pyproject.toml files): | |
| # cuda_pathfinder -> (no internal deps) | |
| # cuda_bindings -> cuda_pathfinder | |
| # cuda_core -> cuda_pathfinder, cuda_bindings | |
| # cuda_python -> cuda_bindings (meta package) | |
| # | |
| # A change to cuda_pathfinder (or shared infra) forces a rebuild of every | |
| # downstream module. A change to cuda_bindings forces rebuild of cuda_core. | |
| # A change to cuda_core alone skips rebuilding/retesting cuda_bindings. | |
| # On push to main, tag refs, schedule, or workflow_dispatch events we | |
| # unconditionally run everything because there is no meaningful "changed | |
| # paths" baseline for those events. | |
| detect-changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| bindings: ${{ steps.compose.outputs.bindings }} | |
| core: ${{ steps.compose.outputs.core }} | |
| pathfinder: ${{ steps.compose.outputs.pathfinder }} | |
| python_meta: ${{ steps.compose.outputs.python_meta }} | |
| test_helpers: ${{ steps.compose.outputs.test_helpers }} | |
| shared: ${{ steps.compose.outputs.shared }} | |
| build_bindings: ${{ steps.compose.outputs.build_bindings }} | |
| build_core: ${{ steps.compose.outputs.build_core }} | |
| build_pathfinder: ${{ steps.compose.outputs.build_pathfinder }} | |
| test_bindings: ${{ steps.compose.outputs.test_bindings }} | |
| test_core: ${{ steps.compose.outputs.test_core }} | |
| test_pathfinder: ${{ steps.compose.outputs.test_pathfinder }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| # copy-pr-bot pushes every PR (whether it targets main or a backport | |
| # branch such as 12.9.x) to pull-request/<N>, so the base branch | |
| # cannot be inferred from github.ref_name. Look it up via the | |
| # upstream PR metadata so the diff below is rooted at the right place. | |
| - name: Resolve PR base branch | |
| id: pr-info | |
| if: ${{ startsWith(github.ref_name, 'pull-request/') }} | |
| uses: nv-gha-runners/get-pr-info@main | |
| - name: Detect changed paths | |
| id: filter | |
| if: ${{ startsWith(github.ref_name, 'pull-request/') }} | |
| env: | |
| # GitHub Actions evaluates step-level `env:` expressions eagerly — | |
| # the step's `if:` gate does NOT short-circuit them. On non-PR | |
| # events (push/tag/schedule), `pr-info` is skipped and its outputs | |
| # are empty strings, so `fromJSON('')` would raise a template error | |
| # and fail the step despite `if:` being false. Guard the | |
| # `fromJSON` call with a short-circuit so the expression resolves | |
| # to an empty string on non-PR events; the step is still gated | |
| # off by `if:`, so `BASE_REF` is never consumed there. | |
| BASE_REF: ${{ steps.pr-info.outputs.pr-info && fromJSON(steps.pr-info.outputs.pr-info).base.ref || '' }} | |
| run: | | |
| # Diff against the merge base with the PR's actual target branch. | |
| # Uses merge-base so diverged branches only show files changed on | |
| # the PR side, not upstream commits. | |
| if [[ -z "${BASE_REF}" ]]; then | |
| echo "Could not resolve PR base branch from get-pr-info output" >&2 | |
| exit 1 | |
| fi | |
| base=$(git merge-base HEAD "origin/${BASE_REF}") | |
| changed=$(git diff --name-only "$base"...HEAD) | |
| has_match() { | |
| grep -qE "$1" <<< "$changed" && echo true || echo false | |
| } | |
| { | |
| echo "bindings=$(has_match '^cuda_bindings/')" | |
| echo "core=$(has_match '^cuda_core/')" | |
| echo "pathfinder=$(has_match '^cuda_pathfinder/')" | |
| echo "python_meta=$(has_match '^cuda_python/')" | |
| echo "test_helpers=$(has_match '^cuda_python_test_helpers/')" | |
| echo "shared=$(has_match '^(\.github/|ci/|scripts/|toolshed/|conftest\.py$|pyproject\.toml$|pixi\.(toml|lock)$|pytest\.ini$|ruff\.toml$)')" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Compose gating outputs | |
| id: compose | |
| env: | |
| IS_PR: ${{ startsWith(github.ref_name, 'pull-request/') }} | |
| BINDINGS: ${{ steps.filter.outputs.bindings || 'false' }} | |
| CORE: ${{ steps.filter.outputs.core || 'false' }} | |
| PATHFINDER: ${{ steps.filter.outputs.pathfinder || 'false' }} | |
| PYTHON_META: ${{ steps.filter.outputs.python_meta || 'false' }} | |
| TEST_HELPERS: ${{ steps.filter.outputs.test_helpers || 'false' }} | |
| SHARED: ${{ steps.filter.outputs.shared || 'false' }} | |
| run: | | |
| set -euxo pipefail | |
| # Non-PR events (push to main, tag push, schedule, workflow_dispatch) | |
| # always exercise the full pipeline because there is no baseline for | |
| # a meaningful diff. | |
| if [[ "${IS_PR}" != "true" ]]; then | |
| bindings=true | |
| core=true | |
| pathfinder=true | |
| python_meta=true | |
| test_helpers=true | |
| shared=true | |
| else | |
| bindings="${BINDINGS}" | |
| core="${CORE}" | |
| pathfinder="${PATHFINDER}" | |
| python_meta="${PYTHON_META}" | |
| test_helpers="${TEST_HELPERS}" | |
| shared="${SHARED}" | |
| fi | |
| or_flag() { | |
| for v in "$@"; do | |
| if [[ "${v}" == "true" ]]; then | |
| echo "true" | |
| return | |
| fi | |
| done | |
| echo "false" | |
| } | |
| # Build gating: pathfinder change forces rebuild of bindings and | |
| # core; bindings change forces rebuild of core. shared changes force | |
| # a full rebuild. | |
| build_pathfinder="$(or_flag "${shared}" "${pathfinder}")" | |
| build_bindings="$(or_flag "${shared}" "${pathfinder}" "${bindings}")" | |
| build_core="$(or_flag "${shared}" "${pathfinder}" "${bindings}" "${core}")" | |
| # Test gating: tests for a module must run whenever that module, any | |
| # of its runtime dependencies, the shared test helper package, or | |
| # shared infra changes. pathfinder tests are cheap and always run. | |
| test_pathfinder=true | |
| test_bindings="$(or_flag "${shared}" "${pathfinder}" "${bindings}" "${test_helpers}")" | |
| test_core="$(or_flag "${shared}" "${pathfinder}" "${bindings}" "${core}" "${test_helpers}")" | |
| { | |
| echo "bindings=${bindings}" | |
| echo "core=${core}" | |
| echo "pathfinder=${pathfinder}" | |
| echo "python_meta=${python_meta}" | |
| echo "test_helpers=${test_helpers}" | |
| echo "shared=${shared}" | |
| echo "build_bindings=${build_bindings}" | |
| echo "build_core=${build_core}" | |
| echo "build_pathfinder=${build_pathfinder}" | |
| echo "test_bindings=${test_bindings}" | |
| echo "test_core=${test_core}" | |
| echo "test_pathfinder=${test_pathfinder}" | |
| } >> "$GITHUB_OUTPUT" | |
| # NOTE: Build jobs are intentionally split by platform rather than using a single | |
| # matrix. This allows each test job to depend only on its corresponding build, | |
| # so faster platforms can proceed through build & test without waiting for slower | |
| # ones. Keep these job definitions textually identical except for: | |
| # - host-platform value | |
| # - if: condition (build-linux-64 omits doc-only check since it's needed for docs) | |
| build-linux-64: | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| host-platform: | |
| - linux-64 | |
| name: Build ${{ matrix.host-platform }}, CUDA ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) }} | |
| secrets: inherit | |
| uses: ./.github/workflows/build-wheel.yml | |
| with: | |
| host-platform: ${{ matrix.host-platform }} | |
| cuda-version: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| prev-cuda-version: ${{ needs.ci-vars.outputs.CUDA_PREV_BUILD_VER }} | |
| # See build-linux-64 for why build jobs are split by platform. | |
| build-linux-aarch64: | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| host-platform: | |
| - linux-aarch64 | |
| name: Build ${{ matrix.host-platform }}, CUDA ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) }} | |
| secrets: inherit | |
| uses: ./.github/workflows/build-wheel.yml | |
| with: | |
| host-platform: ${{ matrix.host-platform }} | |
| cuda-version: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| prev-cuda-version: ${{ needs.ci-vars.outputs.CUDA_PREV_BUILD_VER }} | |
| # See build-linux-64 for why build jobs are split by platform. | |
| build-windows: | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| host-platform: | |
| - win-64 | |
| name: Build ${{ matrix.host-platform }}, CUDA ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) }} | |
| secrets: inherit | |
| uses: ./.github/workflows/build-wheel.yml | |
| with: | |
| host-platform: ${{ matrix.host-platform }} | |
| cuda-version: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| prev-cuda-version: ${{ needs.ci-vars.outputs.CUDA_PREV_BUILD_VER }} | |
| # NOTE: test-sdist jobs are split by platform (mirroring build-* and test-wheel-*) | |
| # so platform-specific sources (e.g. cuda_bindings/*_windows.pyx selected by | |
| # build_hooks.py) are exercised on their target OS. Keep these job definitions | |
| # textually identical except for: | |
| # - host-platform value | |
| # - uses: (test-sdist-linux.yml vs test-sdist-windows.yml) | |
| test-sdist-linux: | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| name: Test sdist linux-64 | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) }} | |
| secrets: inherit | |
| uses: ./.github/workflows/test-sdist-linux.yml | |
| with: | |
| host-platform: linux-64 | |
| cuda-version: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| # See test-sdist-linux for why sdist test jobs are split by platform. | |
| test-sdist-windows: | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| name: Test sdist win-64 | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) }} | |
| secrets: inherit | |
| uses: ./.github/workflows/test-sdist-windows.yml | |
| with: | |
| host-platform: win-64 | |
| cuda-version: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| # NOTE: Test jobs are split by platform for the same reason as build jobs (see | |
| # build-linux-64). Keep these job definitions textually identical except for: | |
| # - host-platform value | |
| # - build job under needs: | |
| # - uses: (test-wheel-linux.yml vs test-wheel-windows.yml) | |
| test-linux-64: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| host-platform: | |
| - linux-64 | |
| name: Test ${{ matrix.host-platform }} | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.doc-only) }} | |
| permissions: | |
| contents: read # This is required for actions/checkout | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| - detect-changes | |
| - build-linux-64 | |
| secrets: inherit | |
| uses: ./.github/workflows/test-wheel-linux.yml | |
| with: | |
| build-type: pull-request | |
| host-platform: ${{ matrix.host-platform }} | |
| build-ctk-ver: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| nruns: ${{ (github.event_name == 'schedule' && 100) || 1}} | |
| skip-bindings-test: ${{ !fromJSON(needs.detect-changes.outputs.test_bindings) }} | |
| # See test-linux-64 for why test jobs are split by platform. | |
| test-linux-aarch64: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| host-platform: | |
| - linux-aarch64 | |
| name: Test ${{ matrix.host-platform }} | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.doc-only) }} | |
| permissions: | |
| contents: read # This is required for actions/checkout | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| - detect-changes | |
| - build-linux-aarch64 | |
| secrets: inherit | |
| uses: ./.github/workflows/test-wheel-linux.yml | |
| with: | |
| build-type: pull-request | |
| host-platform: ${{ matrix.host-platform }} | |
| build-ctk-ver: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| nruns: ${{ (github.event_name == 'schedule' && 100) || 1}} | |
| skip-bindings-test: ${{ !fromJSON(needs.detect-changes.outputs.test_bindings) }} | |
| # See test-linux-64 for why test jobs are split by platform. | |
| test-windows: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| host-platform: | |
| - win-64 | |
| name: Test ${{ matrix.host-platform }} | |
| if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.doc-only) }} | |
| permissions: | |
| contents: read # This is required for actions/checkout | |
| needs: | |
| - ci-vars | |
| - should-skip | |
| - detect-changes | |
| - build-windows | |
| secrets: inherit | |
| uses: ./.github/workflows/test-wheel-windows.yml | |
| with: | |
| build-type: pull-request | |
| host-platform: ${{ matrix.host-platform }} | |
| build-ctk-ver: ${{ needs.ci-vars.outputs.CUDA_BUILD_VER }} | |
| nruns: ${{ (github.event_name == 'schedule' && 100) || 1}} | |
| skip-bindings-test: ${{ !fromJSON(needs.detect-changes.outputs.test_bindings) }} | |
| doc: | |
| name: Docs | |
| if: ${{ github.repository_owner == 'nvidia' }} | |
| # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages | |
| permissions: | |
| id-token: write | |
| contents: write | |
| pull-requests: write | |
| needs: | |
| - ci-vars | |
| - build-linux-64 | |
| secrets: inherit | |
| uses: ./.github/workflows/build-docs.yml | |
| with: | |
| is-release: ${{ github.ref_type == 'tag' }} | |
| checks: | |
| name: Check job status | |
| if: always() | |
| runs-on: ubuntu-latest | |
| needs: | |
| - should-skip | |
| - detect-changes | |
| - test-sdist-linux | |
| - test-sdist-windows | |
| - test-linux-64 | |
| - test-linux-aarch64 | |
| - test-windows | |
| - doc | |
| steps: | |
| - name: Exit | |
| run: | | |
| # if any dependencies were cancelled or failed, that's a failure | |
| # | |
| # see https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#always | |
| # and https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks | |
| # for why this cannot be encoded in the job-level `if:` field | |
| # | |
| # TL; DR: `$REASONS` | |
| # | |
| # The intersection of skipped-as-success and required status checks | |
| # creates a scenario where if you DON'T `always()` run this job, the | |
| # status check UI will block merging and if you DO `always()` run and | |
| # a dependency is _cancelled_ (due to a critical failure, which is | |
| # somehow not considered a failure ¯\_(ツ)_/¯) then the critically | |
| # failing job(s) will timeout causing a cancellation here and the | |
| # build to succeed which we don't want (originally this was just | |
| # 'exit 0') | |
| # | |
| # Note: When [doc-only] is in PR title, test jobs are intentionally | |
| # skipped and should not cause failure. | |
| # | |
| # detect-changes gates whether heavy test matrices run at all; if it | |
| # does not succeed, downstream test jobs are skipped rather than | |
| # failed, which would otherwise go unnoticed here. Require its | |
| # success explicitly so a broken gating step cannot masquerade as a | |
| # green CI run. | |
| doc_only=${{ needs.should-skip.outputs.doc-only }} | |
| if ${{ needs.detect-changes.result != 'success' }}; then | |
| exit 1 | |
| fi | |
| if ${{ needs.doc.result == 'cancelled' || needs.doc.result == 'failure' }}; then | |
| exit 1 | |
| fi | |
| if ${{ needs.test-sdist-linux.result == 'cancelled' || | |
| needs.test-sdist-linux.result == 'failure' || | |
| needs.test-sdist-windows.result == 'cancelled' || | |
| needs.test-sdist-windows.result == 'failure' }}; then | |
| exit 1 | |
| fi | |
| if [[ "${doc_only}" != "true" ]]; then | |
| if ${{ needs.test-linux-64.result == 'cancelled' || | |
| needs.test-linux-64.result == 'failure' || | |
| needs.test-linux-aarch64.result == 'cancelled' || | |
| needs.test-linux-aarch64.result == 'failure' || | |
| needs.test-windows.result == 'cancelled' || | |
| needs.test-windows.result == 'failure' }}; then | |
| exit 1 | |
| fi | |
| fi | |
| exit 0 |