Skip to content
Open
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
161 changes: 161 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,151 @@ jobs:
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:
BASE_REF: ${{ 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
Expand Down Expand Up @@ -151,6 +296,7 @@ jobs:
needs:
- ci-vars
- should-skip
- detect-changes
- build-linux-64
secrets: inherit
uses: ./.github/workflows/test-wheel-linux.yml
Expand All @@ -159,6 +305,7 @@ jobs:
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:
Expand All @@ -174,6 +321,7 @@ jobs:
needs:
- ci-vars
- should-skip
- detect-changes
- build-linux-aarch64
secrets: inherit
uses: ./.github/workflows/test-wheel-linux.yml
Expand All @@ -182,6 +330,7 @@ jobs:
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:
Expand All @@ -197,6 +346,7 @@ jobs:
needs:
- ci-vars
- should-skip
- detect-changes
- build-windows
secrets: inherit
uses: ./.github/workflows/test-wheel-windows.yml
Expand All @@ -205,6 +355,7 @@ jobs:
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
Expand All @@ -228,6 +379,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- should-skip
- detect-changes
- test-linux-64
- test-linux-aarch64
- test-windows
Expand All @@ -254,7 +406,16 @@ jobs:
#
# 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
Expand Down
14 changes: 11 additions & 3 deletions .github/workflows/test-wheel-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ on:
nruns:
type: number
default: 1
# When true, cuda.bindings tests (and the Cython tests that depend on
# them) are skipped even when CTK majors match. Callers set this based
# on the output of the detect-changes job in ci.yml so PRs that only
# touch unrelated modules avoid the expensive bindings test suite.
skip-bindings-test:
type: boolean
default: false

defaults:
run:
Expand Down Expand Up @@ -113,6 +120,7 @@ jobs:
LOCAL_CTK: ${{ matrix.LOCAL_CTK }}
PY_VER: ${{ matrix.PY_VER }}
SHA: ${{ github.sha }}
SKIP_BINDINGS_TEST_OVERRIDE: ${{ inputs.skip-bindings-test && '1' || '0' }}
run: ./ci/tools/env-vars test

- name: Download cuda-pathfinder build artifacts
Expand All @@ -122,21 +130,21 @@ jobs:
path: ./cuda_pathfinder

- name: Download cuda-python build artifacts
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '0'}}
if: ${{ env.USE_BACKPORT_BINDINGS == '0' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: cuda-python-wheel
path: .

- name: Download cuda.bindings build artifacts
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '0'}}
if: ${{ env.USE_BACKPORT_BINDINGS == '0' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ env.CUDA_BINDINGS_ARTIFACT_NAME }}
path: ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}

- name: Download cuda-python & cuda.bindings build artifacts from the prior branch
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '1'}}
if: ${{ env.USE_BACKPORT_BINDINGS == '1' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand Down
14 changes: 11 additions & 3 deletions .github/workflows/test-wheel-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ on:
nruns:
type: number
default: 1
# When true, cuda.bindings tests (and the Cython tests that depend on
# them) are skipped even when CTK majors match. Callers set this based
# on the output of the detect-changes job in ci.yml so PRs that only
# touch unrelated modules avoid the expensive bindings test suite.
skip-bindings-test:
type: boolean
default: false

jobs:
compute-matrix:
Expand Down Expand Up @@ -107,6 +114,7 @@ jobs:
LOCAL_CTK: ${{ matrix.LOCAL_CTK }}
PY_VER: ${{ matrix.PY_VER }}
SHA: ${{ github.sha }}
SKIP_BINDINGS_TEST_OVERRIDE: ${{ inputs.skip-bindings-test && '1' || '0' }}
shell: bash --noprofile --norc -xeuo pipefail {0}
run: ./ci/tools/env-vars test

Expand All @@ -117,21 +125,21 @@ jobs:
path: ./cuda_pathfinder

- name: Download cuda-python build artifacts
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '0'}}
if: ${{ env.USE_BACKPORT_BINDINGS == '0' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: cuda-python-wheel
path: .

- name: Download cuda.bindings build artifacts
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '0'}}
if: ${{ env.USE_BACKPORT_BINDINGS == '0' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ env.CUDA_BINDINGS_ARTIFACT_NAME }}
path: ${{ env.CUDA_BINDINGS_ARTIFACTS_DIR }}

- name: Download cuda-python & cuda.bindings build artifacts from the prior branch
if: ${{ env.SKIP_CUDA_BINDINGS_TEST == '1'}}
if: ${{ env.USE_BACKPORT_BINDINGS == '1' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand Down
19 changes: 18 additions & 1 deletion ci/tools/env-vars
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,27 @@ elif [[ "${1}" == "test" ]]; then
BUILD_CUDA_MAJOR="$(cut -d '.' -f 1 <<< ${BUILD_CUDA_VER})"
TEST_CUDA_MAJOR="$(cut -d '.' -f 1 <<< ${CUDA_VER})"
CUDA_BINDINGS_ARTIFACT_BASENAME="cuda-bindings-python${PYTHON_VERSION_FORMATTED}-cuda${BUILD_CUDA_VER}-${HOST_PLATFORM}"
# USE_BACKPORT_BINDINGS flags the CTK-major-mismatch case where the
# current-run bindings wheel was built for a different CTK major than the
# one under test, so we must pull the bindings wheel from the backport
# branch instead. This is independent of whether bindings tests run.
# SKIP_CUDA_BINDINGS_TEST is the test-time gate: it is set when the CTK
# majors differ OR when the caller tells us to skip for path-filter
# reasons via SKIP_BINDINGS_TEST_OVERRIDE.
if [[ ${BUILD_CUDA_MAJOR} != ${TEST_CUDA_MAJOR} ]]; then
USE_BACKPORT_BINDINGS=1
SKIP_CUDA_BINDINGS_TEST=1
SKIP_CYTHON_TEST=1
else
SKIP_CUDA_BINDINGS_TEST=0
USE_BACKPORT_BINDINGS=0
# Path-filter override only skips bindings tests, NOT cython tests
# for other modules (e.g. cuda.core). Cython skip is driven solely
# by the build/test CTK minor-version mismatch.
if [[ "${SKIP_BINDINGS_TEST_OVERRIDE:-0}" == "1" ]]; then
SKIP_CUDA_BINDINGS_TEST=1
else
SKIP_CUDA_BINDINGS_TEST=0
fi
BUILD_CUDA_MINOR="$(cut -d '.' -f 2 <<< ${BUILD_CUDA_VER})"
TEST_CUDA_MINOR="$(cut -d '.' -f 2 <<< ${CUDA_VER})"
if [[ ${BUILD_CUDA_MINOR} != ${TEST_CUDA_MINOR} ]]; then
Expand All @@ -80,6 +96,7 @@ elif [[ "${1}" == "test" ]]; then
echo "SKIP_CUDA_BINDINGS_TEST=${SKIP_CUDA_BINDINGS_TEST}"
echo "SKIP_CYTHON_TEST=${SKIP_CYTHON_TEST}"
echo "TEST_CUDA_MAJOR=${TEST_CUDA_MAJOR}"
echo "USE_BACKPORT_BINDINGS=${USE_BACKPORT_BINDINGS}"
} >> $GITHUB_ENV
fi

Expand Down
Loading