Skip to content
Merged
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
37 changes: 27 additions & 10 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ is a `workflow_call` reusable invoked from the umbrella.

## What runs when

| Job in `ci.yml` | Triggered by | Path filter source |
| Job in `ci.yml` | Triggered by | Routing rule |
| -------------------- | --------------------------------------------------- | ----------------------------------- |
| `preflight` | every PR / push to main / dispatch / PR label added | none (always runs) |
| `changes` | every PR / push to main / dispatch / PR label added | runs `dev/ci/compute-changes.py` |
Expand Down Expand Up @@ -90,9 +90,9 @@ Two rules keep those runs from corrupting the PR's status:
`preflight` on the label name used to let any unrelated label overwrite the
commit run's real `Preflight` verdict with `skipped`, see
[#5007](https://github.com/apache/datafusion-comet/issues/5007).
- Every heavy job excludes `labeled` events unless the label just added is the
one that gates it. Without that, applying a single label re-ran the entire
heavy pipeline at a commit that had already been tested.
- On a `labeled` event, `POLICY` reports false for every job the new label does
not gate. Without that, applying a single label re-ran the entire heavy
pipeline at a commit that had already been tested.

`run-spark-4.1-tests` gates nothing: `spark_4_1` already runs on every PR.

Expand Down Expand Up @@ -122,13 +122,30 @@ umbrella doesn't watch, or operate independently of the rest of CI:
| `spark_sql_test_reusable.yml` | `spark_3_4`, `spark_3_5`, `spark_4_0`, `spark_4_1` |
| `iceberg_spark_test_reusable.yml` | `iceberg_1_8`, `iceberg_1_9`, `iceberg_1_10`, `iceberg_1_11` |

## Modifying path filters
## Changing what runs when

Each long workflow's "what files trigger me" rules live in the `FILTERS`
dict at the top of `dev/ci/compute-changes.py`. The `changes` job in
`ci.yml` invokes that script and the gate `if:` on each long job consumes
`needs.changes.outputs.<name>`. When adding a new test suite or moving
sources, update the relevant filter entry there.
Every heavy job in `ci.yml` is gated on exactly one thing:

```yaml
if: needs.changes.outputs.spark_3_5 == 'true'
```

That single boolean folds together two separate decisions, both of which live
in `dev/ci/compute-changes.py`:

- **`FILTERS`** — which files the job covers. Pattern semantics match
dorny/picomatch (`**` spans path segments, `*` stays within one, a leading
`!` excludes).
- **`POLICY`** — which events may run it. `"pr"` for every pull request,
`"push"` for push to main, `"label:<name>"` for opt-in on a labelled pull
request. `"pr"` and `"label:"` are mutually exclusive. `workflow_dispatch`
always runs everything.

So adding a suite, moving sources, or changing when something runs is an edit
to one of those two tables, not to ten `${{ }}` expressions. Keeping the policy
in Python is also what makes it testable: GitHub expressions cannot be
exercised outside a real workflow run, whereas `POLICY_CASES` in
`dev/ci/check-ci-config.py` pins the expected job set for each event shape.

A file that a job reads but that no filter lists is silent: the job skips,
and the edit merges with only `preflight` having looked at it. The shared
Expand Down
134 changes: 36 additions & 98 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ jobs:
# `Preflight` verdict with `skipped` (issue #5007). Running it unconditionally
# costs about a minute of ubuntu-slim time per label event and keeps the
# reported verdict truthful. Skipping the redundant work is the heavy jobs'
# job, and they filter `labeled` events themselves below.
# job; POLICY in dev/ci/compute-changes.py drops every job a `labeled` event
# does not gate.
# ---------------------------------------------------------------------------
preflight:
name: Preflight
Expand Down Expand Up @@ -141,20 +142,23 @@ jobs:
id: compute
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.sha }}
EVENT_NAME: ${{ github.event_name }}
EVENT_ACTION: ${{ github.event.action }}
LABEL_NAME: ${{ github.event.label.name }}
PR_LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_AFTER: ${{ github.sha }}
run: |
set -euo pipefail
: > changed_files.txt
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
for key in build_linux build_macos benchmark docs spark_3_4 spark_3_5 spark_4_0 spark_4_1 iceberg_1_8 iceberg_1_9 iceberg_1_10 iceberg_1_11; do
echo "${key}=true" >> "$GITHUB_OUTPUT"
done
exit 0
fi
if [[ "$EVENT_NAME" == "pull_request" ]]; then
# No meaningful base to diff against; compute-changes.py forces
# every output true for this event so a manual run can exercise
# any gated job.
:
elif [[ "$EVENT_NAME" == "pull_request" ]]; then
git diff --name-only "$PR_BASE_SHA"..."$PR_HEAD_SHA" > changed_files.txt
else
# push to main; first push to a branch has all-zero before sha
Expand All @@ -169,70 +173,47 @@ jobs:
python3 dev/ci/compute-changes.py changed_files.txt >> "$GITHUB_OUTPUT"

# ---------------------------------------------------------------------------
# Heavy jobs: each is a thin caller of an existing reusable workflow. The
# `if:` expressions encode the same event/label/path criteria the
# standalone trigger workflows used to encode in their `on:` blocks.
# Heavy jobs: each is a thin caller of an existing reusable workflow, gated
# on the one `changes` output that covers it.
#
# On a `labeled` event only the job that the newly added label gates runs.
# Every other heavy job already ran on the opened/synchronize event at the
# same commit, so letting them through would duplicate an entire pipeline
# each time somebody applies a label.
# That single output already folds in everything these gates used to spell
# out inline: which files the job cares about, which events may run it, and
# which opt-in label it needs on a pull request. All of it lives in FILTERS
# and POLICY in dev/ci/compute-changes.py, where it is one table instead of
# ten near-identical `${{ }}` expressions, and where dev/ci/check-ci-config.py
# can actually test it.
# ---------------------------------------------------------------------------

pr_build_linux:
name: PR Build (Linux)
needs: changes
if: |
needs.changes.outputs.build_linux == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.action != 'labeled'))
if: needs.changes.outputs.build_linux == 'true'
uses: ./.github/workflows/pr_build_linux.yml

pr_build_macos:
name: PR Build (macOS)
needs: changes
if: |
needs.changes.outputs.build_macos == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.action != 'labeled'))
if: needs.changes.outputs.build_macos == 'true'
uses: ./.github/workflows/pr_build_macos.yml

pr_benchmark_check:
name: PR Benchmark Check
needs: changes
if: |
needs.changes.outputs.benchmark == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.action != 'labeled'))
if: needs.changes.outputs.benchmark == 'true'
uses: ./.github/workflows/pr_benchmark_check.yml

docs:
name: Deploy Comet site
needs: changes
# docs deploys to asf-site, so only run on push-to-main (or a manual dispatch).
if: |
needs.changes.outputs.docs == 'true' &&
(github.event_name == 'push' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.docs == 'true'
uses: ./.github/workflows/docs.yaml

spark_3_4:
name: Spark SQL Tests (Spark 3.4)
needs: changes
# Main-only by default; PRs need the `run-spark-3.4-tests` label.
if: |
needs.changes.outputs.spark_3_4 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-spark-3.4-tests') &&
(github.event.action != 'labeled' ||
github.event.label.name == 'run-spark-3.4-tests')))
if: needs.changes.outputs.spark_3_4 == 'true'
uses: ./.github/workflows/spark_sql_test_reusable.yml
with:
spark-short: '3.4'
Expand All @@ -242,12 +223,7 @@ jobs:
spark_3_5:
name: Spark SQL Tests (Spark 3.5)
needs: changes
if: |
needs.changes.outputs.spark_3_5 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.action != 'labeled'))
if: needs.changes.outputs.spark_3_5 == 'true'
uses: ./.github/workflows/spark_sql_test_reusable.yml
with:
spark-short: '3.5'
Expand All @@ -260,14 +236,7 @@ jobs:
# Main-only by default; PRs need the `run-spark-4.0-tests` label. Swapped
# with spark_4_1 on the `oom` branch to validate the memory caps against
# Spark 4.1 by default.
if: |
needs.changes.outputs.spark_4_0 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-spark-4.0-tests') &&
(github.event.action != 'labeled' ||
github.event.label.name == 'run-spark-4.0-tests')))
if: needs.changes.outputs.spark_4_0 == 'true'
uses: ./.github/workflows/spark_sql_test_reusable.yml
with:
spark-short: '4.0'
Expand All @@ -277,12 +246,7 @@ jobs:
spark_4_1:
name: Spark SQL Tests (Spark 4.1)
needs: changes
if: |
needs.changes.outputs.spark_4_1 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.action != 'labeled'))
if: needs.changes.outputs.spark_4_1 == 'true'
uses: ./.github/workflows/spark_sql_test_reusable.yml
with:
spark-short: '4.1'
Expand All @@ -293,14 +257,7 @@ jobs:
name: Iceberg Spark SQL Tests (Iceberg 1.8)
needs: changes
# Main-only by default; PRs need the `run-iceberg-tests` label.
if: |
needs.changes.outputs.iceberg_1_8 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-iceberg-tests') &&
(github.event.action != 'labeled' ||
github.event.label.name == 'run-iceberg-tests')))
if: needs.changes.outputs.iceberg_1_8 == 'true'
uses: ./.github/workflows/iceberg_spark_test_reusable.yml
with:
iceberg-short: '1.8'
Expand All @@ -313,14 +270,7 @@ jobs:
name: Iceberg Spark SQL Tests (Iceberg 1.9)
needs: changes
# Main-only by default; PRs need the `run-iceberg-tests` label.
if: |
needs.changes.outputs.iceberg_1_9 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-iceberg-tests') &&
(github.event.action != 'labeled' ||
github.event.label.name == 'run-iceberg-tests')))
if: needs.changes.outputs.iceberg_1_9 == 'true'
uses: ./.github/workflows/iceberg_spark_test_reusable.yml
with:
iceberg-short: '1.9'
Expand All @@ -334,14 +284,7 @@ jobs:
needs: changes
# Main-only by default; PRs need the `run-iceberg-tests` label. Iceberg 1.11
# (Spark 4.1) is the PR-gated Iceberg job; 1.10 covers the Spark 3.5 path.
if: |
needs.changes.outputs.iceberg_1_10 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'run-iceberg-tests') &&
(github.event.action != 'labeled' ||
github.event.label.name == 'run-iceberg-tests')))
if: needs.changes.outputs.iceberg_1_10 == 'true'
uses: ./.github/workflows/iceberg_spark_test_reusable.yml
with:
iceberg-short: '1.10'
Expand All @@ -354,12 +297,7 @@ jobs:
name: Iceberg Spark SQL Tests (Iceberg 1.11)
needs: changes
# Runs on every PR: Iceberg 1.11 is our only Spark 4.1 Iceberg coverage.
if: |
needs.changes.outputs.iceberg_1_11 == 'true' &&
(github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
github.event.action != 'labeled'))
if: needs.changes.outputs.iceberg_1_11 == 'true'
uses: ./.github/workflows/iceberg_spark_test_reusable.yml
with:
iceberg-short: '1.11'
Expand Down
Loading
Loading