# 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! 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 * * *" workflow_dispatch: {} jobs: should-skip: if: ${{ github.repository_owner == 'nvidia' }} runs-on: ubuntu-latest permissions: contents: read pull-requests: read outputs: skip: ${{ steps.get-should-skip.outputs.skip }} doc-only: ${{ steps.get-should-skip.outputs.doc_only }} base-ref: ${{ steps.get-should-skip.outputs.base_ref }} steps: - name: Compute whether to skip builds and tests id: get-should-skip env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: | set -euxo pipefail if ${{ startsWith(github.ref_name, 'pull-request/') }}; then pr_number="$(grep -Po '(\d+)$' <<< '${{ github.ref_name }}')" pr="$(gh pr view "${pr_number}" --json baseRefName,title)" pr_title="$(jq -r '.title' <<< "${pr}")" base_ref="$(jq -r '.baseRefName' <<< "${pr}")" 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 base_ref="" fi echo "skip=${skip}" >> "$GITHUB_OUTPUT" echo "doc_only=${doc_only}" >> "$GITHUB_OUTPUT" echo "base_ref=${base_ref}" >> "$GITHUB_OUTPUT" # Detect which packages were touched by the PR so downstream build and test # jobs can avoid rebuilding/retesting packages unaffected by the change. # See issue #299. # # Dependency graph (verified in pyproject.toml files): # cuda_pathfinder -> (no internal deps) # cuda.bindings (both versions) -> cuda_pathfinder # cuda_core -> cuda_pathfinder, cuda_bindings # cuda_python -> cuda_pathfinder, cuda_bindings, cuda_core (meta package) # # A change to cuda_pathfinder (or shared infra) forces a rebuild of every # downstream module. A bindings-only change rebuilds its bindings and # metapackage, then tests them with the unchanged Core wheel from the baseline. # Core source changes rebuild both CUDA variants. # A change to cuda_core alone skips rebuilding/retesting cuda_bindings and # cuda_pathfinder, but still retests the downstream cuda-python metapackage. # Shared build/orchestration changes run the full pipeline; test-only CI # infrastructure runs every test suite without rebuilding package wheels. # On pushes to main, schedule, workflow_dispatch, or non-bindings release # tags we unconditionally run everything because there is no meaningful # "changed paths" baseline for those events. Bare bindings tags select the # corresponding registered cuda.bindings package root and cuda-python build. detect-changes: if: ${{ github.repository_owner == 'nvidia' }} runs-on: ubuntu-latest needs: should-skip permissions: actions: read contents: read outputs: bindings-config: ${{ steps.registry.outputs.bindings-config }} current-cuda-version: ${{ steps.registry.outputs.current-cuda-version }} python-supported: ${{ steps.registry.outputs.python-supported }} windows-arm64-supported: ${{ steps.registry.outputs.windows-arm64-supported }} windows-arm64-single-cuda-major: ${{ steps.registry.outputs.windows-arm64-single-cuda-major }} workplan: ${{ steps.workplan.outputs.workplan }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Only PR mirrors need full ancestry to find their merge base. fetch-depth: ${{ startsWith(github.ref_name, 'pull-request/') && '0' || '1' }} filter: blob:none - name: Set up Python for CI tools uses: ./.github/actions/setup-hardened-python with: python-version: "3.12" - name: Install CI tool test dependencies run: python3 -m pip install -e './ci[test]' - name: Read bindings configuration and select supported build platforms id: registry run: | set -euo pipefail bindings_config=$(python3 -m ci.tools.bindings_config) current_cuda_version=$(jq -er \ '.package_roots[] | select(.release_status == "current") | .toolkit_version' \ <<< "$bindings_config") if [[ "$current_cuda_version" =~ ^([0-9]+)\.([0-9]+)(\.|$) ]]; then current_cuda_major="${BASH_REMATCH[1]}" current_cuda_minor="${BASH_REMATCH[2]}" else echo "Invalid current CUDA version: $current_cuda_version" >&2 exit 1 fi # Windows ARM64 is available starting with CUDA 13.4. if (( current_cuda_major > 13 || (current_cuda_major == 13 && current_cuda_minor >= 4) )); then windows_arm64_supported=true else windows_arm64_supported=false fi # CUDA 13 introduced Windows ARM64 mid-release-line, so there is no # CUDA 12 Windows ARM64 toolkit. Once current advances to CUDA 14, # CUDA 13 can be built as the maintenance major on this platform; # keep this equality so CUDA 14 does not exclude that supported line. if [[ "$current_cuda_major" == "13" ]]; then windows_arm64_single_cuda_major=true else windows_arm64_single_cuda_major=false fi { echo "python-supported=$(yq -o json -I 0 '.python.supported' ci/versions.yml)" echo "bindings-config=$bindings_config" echo "current-cuda-version=$current_cuda_version" echo "windows-arm64-supported=$windows_arm64_supported" echo "windows-arm64-single-cuda-major=$windows_arm64_single_cuda_major" } >> "$GITHUB_OUTPUT" - name: Resolve PR merge base id: merge-base if: ${{ startsWith(github.ref_name, 'pull-request/') }} env: BASE_REF: ${{ needs.should-skip.outputs.base-ref }} run: | set -euo pipefail if [[ -z "${BASE_REF}" ]]; then echo "Could not resolve PR base branch" >&2 exit 1 fi base=$(git merge-base HEAD "origin/${BASE_REF}") echo "sha=${base}" >> "$GITHUB_OUTPUT" - name: Resolve reusable base artifacts id: baseline if: ${{ startsWith(github.ref_name, 'pull-request/') }} env: BASE_REF: ${{ needs.should-skip.outputs.base-ref }} BINDINGS_CONFIG: ${{ steps.registry.outputs.bindings-config }} CURRENT_CUDA_VERSION: ${{ steps.registry.outputs.current-cuda-version }} MERGE_BASE: ${{ steps.merge-base.outputs.sha }} GH_TOKEN: ${{ github.token }} WINDOWS_ARM64_SUPPORTED: ${{ steps.registry.outputs.windows-arm64-supported }} WINDOWS_ARM64_SINGLE_CUDA_MAJOR: ${{ steps.registry.outputs.windows-arm64-single-cuda-major }} run: | set -uo pipefail unavailable() { echo "No complete reusable artifact set was found; this run will build and test everything." >> "$GITHUB_STEP_SUMMARY" exit 0 } if [[ -z "${BASE_REF}" ]]; then unavailable fi merge_base="${MERGE_BASE}" if [[ -z "${merge_base}" ]]; then unavailable fi if ! runs=$(gh run list \ --repo "${{ github.repository }}" \ --branch "${BASE_REF}" \ --commit "${merge_base}" \ --event push \ --workflow ci.yml \ --status success \ --limit 1 \ --json databaseId,headSha); then unavailable fi # Reuse only artifacts produced from the exact commit used as the # PR diff base. Using the latest base-branch run is unsafe for a PR # that was opened before newer changes landed on that branch. run_id=$(jq -r '.[0].databaseId // empty' <<< "$runs") run_sha=$(jq -r '.[0].headSha // empty' <<< "$runs") if [[ -z "${run_id}" || "${run_sha}" != "${merge_base}" ]]; then unavailable fi if ! artifact_names=$(gh api \ "repos/${{ github.repository }}/actions/runs/${run_id}/artifacts?per_page=100" \ --paginate \ --jq '.artifacts[] | select(.expired == false) | .name'); then unavailable fi has_artifact() { grep -Fxq "$1" <<< "$artifact_names" } missing=() has_artifact cuda-pathfinder-wheel || missing+=(cuda-pathfinder-wheel) if ! cuda_versions_json=$(jq -ce '[.package_roots[].toolkit_version]' <<< "$BINDINGS_CONFIG"); then unavailable fi mapfile -t cuda_versions < <(jq -r '.[]' <<< "$cuda_versions_json") for version in "${cuda_versions[@]}"; do name="cuda-python-wheel-cuda${version}" has_artifact "$name" || missing+=("$name") done if ! python_versions=$(yq -r '.python.supported[]' ci/versions.yml); then unavailable fi if ! platforms=$(yq -r '.platforms[]' ci/test-matrix.yml); then unavailable fi if [[ "$WINDOWS_ARM64_SUPPORTED" == "true" ]]; then platforms+=$'\nwin-arm64' fi if [[ -z "${python_versions}" || -z "${platforms}" ]]; then unavailable fi while IFS= read -r python_version; do python=${python_version//./} while IFS= read -r platform; do for version in "${cuda_versions[@]}"; do if [[ "$platform" == "win-arm64" && "$WINDOWS_ARM64_SINGLE_CUDA_MAJOR" == "true" && "$version" != "$CURRENT_CUDA_VERSION" ]]; then continue fi binding="cuda-bindings-python${python}-cuda${version}-${platform}-${merge_base}" has_artifact "$binding" || missing+=("$binding") done core="cuda-core-python${python}-${platform}-${merge_base}" has_artifact "$core" || missing+=("$core") done <<< "${platforms}" done <<< "${python_versions}" if (( ${#missing[@]} != 0 )); then printf 'Missing reusable artifact: %s\n' "${missing[@]}" >&2 unavailable fi echo "run_id=${run_id}" >> "$GITHUB_OUTPUT" { echo echo "Reusable artifacts: run \`${run_id}\` at \`${merge_base}\` on \`${BASE_REF}\`." } >> "$GITHUB_STEP_SUMMARY" - name: Validate bindings package metadata and test CI tools run: | python3 -m ci.tools.bindings_config --check-package-metadata python3 -m pytest -q --noconftest ci/tools/tests - name: Compute CI workplan id: workplan env: MERGE_BASE: ${{ steps.merge-base.outputs.sha }} BASELINE_RUN_ID: ${{ steps.baseline.outputs.run_id }} RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }} run: | set -euo pipefail planner_args=( --merge-base "$MERGE_BASE" --baseline-run-id "$BASELINE_RUN_ID" ) if [[ -n "$RELEASE_TAG" ]]; then planner_args+=(--release-tag "$RELEASE_TAG") fi python3 -m ci.tools.compute_ci_plan "${planner_args[@]}" \ --github-output "$GITHUB_OUTPUT" \ --github-step-summary "$GITHUB_STEP_SUMMARY" api-check-core-vs-release: name: API check (cuda_core vs. latest release) if: >- ${{ !fromJSON(needs.should-skip.outputs.skip) && fromJSON(needs.detect-changes.outputs.workplan).jobs.core_api_checks }} runs-on: ubuntu-latest needs: - should-skip - detect-changes permissions: contents: read steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 filter: blob:none - name: Find latest release tag id: latest-tag shell: bash --noprofile --norc -euo pipefail {0} env: GH_TOKEN: ${{ github.token }} run: | # --paginate fetches all pages; jq outputs one name per line per page; # sed prints the first (newest) match while consuming all pages, so # gh can complete without SIGPIPE. Fails if no cuda-core-v* tag is found. tag="$(gh api "repos/$GITHUB_REPOSITORY/tags" --paginate \ --jq '.[] | select(.name | startswith("cuda-core-v")) | .name' \ | sed -n '1p')" if [[ -z "${tag}" ]]; then echo "::error::No cuda-core-v* tag found in the repository." >&2 exit 1 fi echo "tag=${tag}" >> "$GITHUB_OUTPUT" - name: Fetch release tag shell: bash --noprofile --norc -euo pipefail {0} run: | git fetch --depth=1 --filter=blob:none origin \ "refs/tags/${{ steps.latest-tag.outputs.tag }}:refs/tags/${{ steps.latest-tag.outputs.tag }}" - name: Check cuda_core public API id: griffe uses: ./.github/actions/griffe-api-check with: package-name: cuda.core package-dir: cuda_core merge-base: ${{ steps.latest-tag.outputs.tag }} api-check-core-vs-base: name: API check (cuda_core vs. merge base) if: >- ${{ startsWith(github.ref_name, 'pull-request/') && !fromJSON(needs.should-skip.outputs.skip) && fromJSON(needs.detect-changes.outputs.workplan).jobs.core_api_checks }} runs-on: ubuntu-latest needs: - should-skip - detect-changes permissions: contents: read steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 1 filter: blob:none - name: Fetch merge base commit shell: bash --noprofile --norc -euo pipefail {0} run: | git fetch --depth=1 --filter=blob:none origin \ "${{ fromJSON(needs.detect-changes.outputs.workplan).merge_base }}" - name: Check cuda_core public API id: griffe uses: ./.github/actions/griffe-api-check with: package-name: cuda.core package-dir: cuda_core merge-base: ${{ fromJSON(needs.detect-changes.outputs.workplan).merge_base }} # NOTE: Build jobs are intentionally split by platform rather than using a single # matrix. This lets each test job consume its platform-specific artifacts as # soon as they are ready. ARM64 and Windows tests also wait for linux-64, # which produces the universal pathfinder and cuda-python wheels. 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: - should-skip - detect-changes strategy: fail-fast: false matrix: host-platform: - linux-64 name: Build ${{ fromJSON(needs.detect-changes.outputs.workplan).labels.build }} (${{ matrix.host-platform }}) if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) }} permissions: actions: read contents: read secrets: inherit uses: ./.github/workflows/build-wheel.yml with: host-platform: ${{ matrix.host-platform }} bindings-config: ${{ needs.detect-changes.outputs.bindings-config }} workplan: ${{ needs.detect-changes.outputs.workplan }} python-versions: ${{ needs.detect-changes.outputs.python-supported }} # See build-linux-64 for why build jobs are split by platform. build-linux-aarch64: needs: - should-skip - detect-changes strategy: fail-fast: false matrix: host-platform: - linux-aarch64 name: Build ${{ fromJSON(needs.detect-changes.outputs.workplan).labels.build_platform }} (${{ matrix.host-platform }}) if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.linux }} permissions: actions: read contents: read secrets: inherit uses: ./.github/workflows/build-wheel.yml with: host-platform: ${{ matrix.host-platform }} bindings-config: ${{ needs.detect-changes.outputs.bindings-config }} workplan: ${{ needs.detect-changes.outputs.workplan }} python-versions: ${{ needs.detect-changes.outputs.python-supported }} # See build-linux-64 for why build jobs are split by platform. build-windows: needs: - should-skip - detect-changes strategy: fail-fast: false matrix: host-platform: - win-64 name: Build ${{ fromJSON(needs.detect-changes.outputs.workplan).labels.build_platform }} (${{ matrix.host-platform }}) if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.windows }} permissions: actions: read contents: read secrets: inherit uses: ./.github/workflows/build-wheel.yml with: host-platform: ${{ matrix.host-platform }} bindings-config: ${{ needs.detect-changes.outputs.bindings-config }} workplan: ${{ needs.detect-changes.outputs.workplan }} python-versions: ${{ needs.detect-changes.outputs.python-supported }} # Windows ARM64 is available starting with CUDA 13.4. Build only the current # CUDA major because no prior-major toolkit or wheel artifacts exist. build-windows-arm64: needs: - should-skip - detect-changes name: Build ${{ fromJSON(needs.detect-changes.outputs.workplan).labels.build_current }} (win-arm64) if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) && needs.detect-changes.outputs.windows-arm64-supported == 'true' && fromJSON(needs.detect-changes.outputs.workplan).jobs.windows_arm64_build && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.windows }} permissions: actions: read contents: read secrets: inherit uses: ./.github/workflows/build-wheel.yml with: host-platform: win-arm64 bindings-config: ${{ needs.detect-changes.outputs.bindings-config }} workplan: ${{ needs.detect-changes.outputs.workplan }} single-cuda-major: ${{ needs.detect-changes.outputs.windows-arm64-single-cuda-major == 'true' }} python-versions: ${{ needs.detect-changes.outputs.python-supported }} # NOTE: test-sdist jobs are split by platform (mirroring build-* and test-wheel-*) # so platform-specific sources (e.g. cuda_bindings_13/*_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: strategy: fail-fast: false matrix: package: ${{ fromJSON(needs.detect-changes.outputs.bindings-config).package_roots }} needs: - should-skip - detect-changes - build-linux-64 name: Sdist ${{ fromJSON(needs.detect-changes.outputs.workplan).labels.sdist[matrix.package.package_root] }} (linux-64) if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) && fromJSON(needs.detect-changes.outputs.workplan).jobs.sdist_tests }} permissions: actions: read contents: read secrets: inherit uses: ./.github/workflows/test-sdist-linux.yml with: host-platform: linux-64 bindings-package: ${{ toJSON(matrix.package) }} workplan: ${{ needs.detect-changes.outputs.workplan }} # See test-sdist-linux for why sdist test jobs are split by platform. test-sdist-windows: strategy: fail-fast: false matrix: package: ${{ fromJSON(needs.detect-changes.outputs.bindings-config).package_roots }} needs: - should-skip - detect-changes - build-linux-64 - build-windows name: Sdist ${{ fromJSON(needs.detect-changes.outputs.workplan).labels.sdist[matrix.package.package_root] }} (win-64) if: ${{ github.repository_owner == 'nvidia' && !fromJSON(needs.should-skip.outputs.skip) && !fromJSON(needs.should-skip.outputs.doc-only) && fromJSON(needs.detect-changes.outputs.workplan).jobs.sdist_tests }} permissions: actions: read contents: read secrets: inherit uses: ./.github/workflows/test-sdist-windows.yml with: host-platform: win-64 bindings-package: ${{ toJSON(matrix.package) }} workplan: ${{ needs.detect-changes.outputs.workplan }} # 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) && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.linux }} permissions: actions: read contents: read # This is required for actions/checkout needs: - 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 }} bindings-config: ${{ needs.detect-changes.outputs.bindings-config }} nruns: ${{ (github.event_name == 'schedule' && 5) || 1}} workplan: ${{ needs.detect-changes.outputs.workplan }} # 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) && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.linux }} permissions: actions: read contents: read # This is required for actions/checkout needs: - should-skip - detect-changes - build-linux-64 - build-linux-aarch64 secrets: inherit uses: ./.github/workflows/test-wheel-linux.yml with: build-type: pull-request host-platform: ${{ matrix.host-platform }} bindings-config: ${{ needs.detect-changes.outputs.bindings-config }} nruns: ${{ (github.event_name == 'schedule' && 5) || 1}} workplan: ${{ needs.detect-changes.outputs.workplan }} # 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) && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.windows }} permissions: actions: read contents: read # This is required for actions/checkout needs: - should-skip - detect-changes - build-linux-64 - build-windows secrets: inherit uses: ./.github/workflows/test-wheel-windows.yml with: build-type: pull-request host-platform: ${{ matrix.host-platform }} bindings-config: ${{ needs.detect-changes.outputs.bindings-config }} nruns: ${{ (github.event_name == 'schedule' && 5) || 1}} workplan: ${{ needs.detect-changes.outputs.workplan }} 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: - build-linux-64 secrets: inherit uses: ./.github/workflows/build-docs.yml with: # Bare cuda.bindings tags intentionally produce only their matching # cuda.bindings and cuda-python artifacts. Keep tag docs on that focused # artifact set; component-specific release runs build the other docs. component: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') && 'cuda-bindings' || 'all' }} git-tag: ${{ github.ref_type == 'tag' && github.ref_name || '' }} run-id: ${{ github.run_id }} is-release: ${{ github.ref_type == 'tag' }} deploy-docs: ${{ github.ref_type != 'tag' }} checks: name: Check job status if: ${{ always() && github.repository_owner == 'nvidia' }} runs-on: ubuntu-latest needs: - should-skip - detect-changes - build-linux-64 - build-linux-aarch64 - build-windows - build-windows-arm64 - test-sdist-linux - test-sdist-windows - test-linux-64 - test-linux-aarch64 - test-windows - api-check-core-vs-release - api-check-core-vs-base - doc steps: - name: Exit env: NEEDS_JSON: ${{ toJSON(needs) }} run: | # GitHub treats `result == 'skipped'` as success for required # status checks (see CCCL gate comment + cccl#605). The previous # `cancelled || failure` predicate let upstream build failures # propagate as `skipped` on downstream test jobs and silently # pass this aggregator. Adopt CCCL's `check_result` pattern: # require an explicit `expected` status per dependency, where # anything else (including `skipped` from a failed upstream) # fails the gate. `if: always()` on the job still ensures this # step runs even when needs are skipped. if [[ "${{ needs.should-skip.outputs.skip }}" == "true" ]]; then echo "[no-ci] - skipping aggregator checks" exit 0 fi doc_only="${{ needs.should-skip.outputs.doc-only }}" linux_selected="${{ needs.detect-changes.outputs.workplan && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.linux || false }}" windows_selected="${{ needs.detect-changes.outputs.workplan && fromJSON(needs.detect-changes.outputs.workplan).jobs.platforms.windows || false }}" windows_arm64_supported="${{ needs.detect-changes.outputs.windows-arm64-supported }}" windows_arm64_selected="${{ needs.detect-changes.outputs.workplan && fromJSON(needs.detect-changes.outputs.workplan).jobs.windows_arm64_build || false }}" build_selected="${{ needs.detect-changes.outputs.workplan && fromJSON(needs.detect-changes.outputs.workplan).jobs.sdist_tests || false }}" run_core_api_check="${{ needs.detect-changes.outputs.workplan && fromJSON(needs.detect-changes.outputs.workplan).jobs.core_api_checks || false }}" is_pr="${{ startsWith(github.ref_name, 'pull-request/') }}" status="success" check_result() { local name=$1 expected=$2 result result=$(jq -r --arg name "$name" '.[$name].result // "missing"' <<< "$NEEDS_JSON") echo "Checking $name: result='$result' (expected '$expected')" if [[ "$result" != "$expected" ]]; then echo "::error::$name did not match expected result" status="failed" fi } # Control jobs, the universal Linux build, and docs always run. check_result "should-skip" "success" check_result "detect-changes" "success" check_result "build-linux-64" "success" check_result "doc" "success" # Optional platform builds and wheel tests share the platform plan. linux_expected="skipped" if [[ "$doc_only" != "true" && "$linux_selected" == "true" ]]; then linux_expected="success" fi windows_expected="skipped" if [[ "$doc_only" != "true" && "$windows_selected" == "true" ]]; then windows_expected="success" fi check_result "build-linux-aarch64" "$linux_expected" check_result "build-windows" "$windows_expected" windows_arm64_expected="skipped" if [[ "$windows_arm64_supported" == "true" && "$windows_arm64_selected" == "true" ]]; then windows_arm64_expected="$windows_expected" fi check_result "build-windows-arm64" "$windows_arm64_expected" # Sdist tests follow build selection; wheel tests follow the platform plan. expected="skipped" if [[ "$doc_only" != "true" && "$build_selected" == "true" ]]; then expected="success" fi check_result "test-sdist-linux" "$expected" check_result "test-sdist-windows" "$expected" check_result "test-linux-64" "$linux_expected" check_result "test-linux-aarch64" "$linux_expected" check_result "test-windows" "$windows_expected" # API compatibility checks run for cuda_core source changes and for # conservative full runs when reusable base artifacts are unavailable. expected="skipped" if [[ "$run_core_api_check" == "true" ]]; then expected="success"; fi check_result "api-check-core-vs-release" "$expected" # API compatibility checks against the merge base (api-check-core-vs-base) # are purely informational, since it is fine for API to drift during development # when it hasn't been released yet. [[ "$status" == "success" ]]