Skip to content

Commit a515d4c

Browse files
ci: gate area jobs on a changes filter + single ci aggregator
A cheap `changes` job computes which areas a PR touches; the frontend/rust/ desktop jobs gate on its outputs (needs + job-level if), so an untouched area's job no longer spins up a runner just to skip its steps (notably the Windows one). Since a skipped job doesn't satisfy a required status check, add a `ci` aggregator (always() + needs all) that fails only on a real job failure — point branch protection at that single context instead of the individual jobs.
1 parent 5703726 commit a515d4c

1 file changed

Lines changed: 66 additions & 90 deletions

File tree

.github/workflows/ci.yml

Lines changed: 66 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
name: CI
22

33
on:
4-
# Run on every PR so the required checks always report a status (a required
5-
# check that never runs blocks merges forever). Path scoping is applied inside
6-
# each job via its "Detect changes" step, not with top-level path filters.
74
pull_request:
85
push:
96
branches: [main]
107

118
jobs:
12-
# ── Frontend: bun workspace (lockfile at the repo root), biome, build, tests ──
13-
frontend:
9+
# ── One cheap job computes which areas a PR touches; the heavy jobs gate on its
10+
# outputs (`needs` + job-level `if`), so an irrelevant area's job never starts
11+
# (no runner spun up — notably the Windows one). A skipped job doesn't satisfy a
12+
# required status check, so the single required check is the `ci` aggregator at
13+
# the bottom, not the individual jobs. ──
14+
changes:
1415
runs-on: ubuntu-latest
16+
outputs:
17+
frontend: ${{ steps.detect.outputs.frontend }}
18+
rust: ${{ steps.detect.outputs.rust }}
19+
desktop: ${{ steps.detect.outputs.desktop }}
1520
steps:
1621
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
1722
with:
1823
fetch-depth: 0
1924

2025
- name: Detect changes
21-
id: changes
26+
id: detect
2227
run: |
2328
if [ "${{ github.event_name }}" = "pull_request" ]; then
2429
base="${{ github.event.pull_request.base.sha }}"
@@ -27,47 +32,62 @@ jobs:
2732
base="${{ github.event.before }}"
2833
head="${{ github.sha }}"
2934
fi
35+
# No usable base (first push, force-push, shallow clone) → run everything.
3036
if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
31-
echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
32-
fi
33-
if git diff --name-only "$base" "$head" | grep -Eq '^(frontend/|module/|scripts/|package\.json|bun\.lock|biome\.json)'; then
34-
echo "relevant=true" >> "$GITHUB_OUTPUT"
35-
else
36-
echo "relevant=false" >> "$GITHUB_OUTPUT"
37+
{
38+
echo "frontend=true"
39+
echo "rust=true"
40+
echo "desktop=true"
41+
} >> "$GITHUB_OUTPUT"
42+
exit 0
3743
fi
44+
files=$(git diff --name-only "$base" "$head")
45+
match() { printf '%s\n' "$files" | grep -Eq "$1"; }
46+
fe=false; rs=false; dk=false
47+
match '^(frontend/|module/|scripts/|package\.json|bun\.lock|biome\.json)' && fe=true
48+
match '^(crates/|src-tauri/|Cargo\.(toml|lock)|flake\.(nix|lock))' && rs=true
49+
match '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))' && dk=true
50+
{
51+
echo "frontend=$fe"
52+
echo "rust=$rs"
53+
echo "desktop=$dk"
54+
} >> "$GITHUB_OUTPUT"
55+
56+
# ── Frontend: bun workspace (lockfile at the repo root), biome, build, tests ──
57+
frontend:
58+
needs: changes
59+
if: needs.changes.outputs.frontend == 'true'
60+
runs-on: ubuntu-latest
61+
steps:
62+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
63+
with:
64+
fetch-depth: 0
3865

3966
- name: Setup Bun
40-
if: steps.changes.outputs.relevant == 'true'
4167
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
4268
with:
4369
bun-version: latest
4470

4571
- name: Install dependencies
46-
if: steps.changes.outputs.relevant == 'true'
4772
# The bun workspace is rooted at the repo; install from the root so the
4873
# frontend member and its deps resolve against the single bun.lock.
4974
run: bun install --frozen-lockfile
5075

5176
- name: Biome check (format + lint)
52-
if: steps.changes.outputs.relevant == 'true'
5377
run: bunx biome check
5478

5579
- name: Build (tsc + vite)
56-
if: steps.changes.outputs.relevant == 'true'
5780
working-directory: frontend
5881
run: bun run build
5982

6083
- name: Unit tests
61-
if: steps.changes.outputs.relevant == 'true'
6284
working-directory: frontend
6385
run: bunx vitest run
6486

6587
- name: i18n dictionaries
66-
if: steps.changes.outputs.relevant == 'true'
6788
run: bun run frontend/scripts/check-i18n.ts
6889

6990
- name: ShellCheck
70-
if: steps.changes.outputs.relevant == 'true'
7191
run: |
7292
# The module is now a thin launcher over the Rust daemon: service.sh /
7393
# action.sh / uninstall.sh run under Android sh; customize.sh runs under
@@ -80,44 +100,24 @@ jobs:
80100
# ── Rust workspace: fmt, clippy, tests, and generated-bindings drift, all in
81101
# the flake devshell (carries the rust toolchain + webkit for src-tauri). ──
82102
rust:
103+
needs: changes
104+
if: needs.changes.outputs.rust == 'true'
83105
runs-on: ubuntu-latest
84106
steps:
85107
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
86108
with:
87109
fetch-depth: 0
88110

89-
- name: Detect changes
90-
id: changes
91-
run: |
92-
if [ "${{ github.event_name }}" = "pull_request" ]; then
93-
base="${{ github.event.pull_request.base.sha }}"
94-
head="${{ github.event.pull_request.head.sha }}"
95-
else
96-
base="${{ github.event.before }}"
97-
head="${{ github.sha }}"
98-
fi
99-
if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
100-
echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
101-
fi
102-
if git diff --name-only "$base" "$head" | grep -Eq '^(crates/|src-tauri/|Cargo\.(toml|lock)|flake\.(nix|lock))'; then
103-
echo "relevant=true" >> "$GITHUB_OUTPUT"
104-
else
105-
echo "relevant=false" >> "$GITHUB_OUTPUT"
106-
fi
107-
108111
- name: Install Nix
109-
if: steps.changes.outputs.relevant == 'true'
110112
uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14
111113
with:
112114
extra-conf: |
113115
accept-flake-config = true
114116
115117
- name: Nix store cache
116-
if: steps.changes.outputs.relevant == 'true'
117118
uses: DeterminateSystems/magic-nix-cache-action@87b14cf437d03d37989d87f0fa5ce4f5dc1a330b # v8
118119

119120
- name: fmt + clippy + test + codegen drift
120-
if: steps.changes.outputs.relevant == 'true'
121121
run: |
122122
nix develop --command bash -euo pipefail -c '
123123
cargo fmt --all --check
@@ -135,60 +135,38 @@ jobs:
135135
# platform/linker breakage without the slow installer. macOS is still deferred
136136
# (no Platform port yet). ──
137137
desktop-linux:
138+
needs: changes
139+
if: needs.changes.outputs.desktop == 'true'
138140
runs-on: ubuntu-latest
139141
steps:
140142
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
141143
with:
142144
fetch-depth: 0
143145

144-
- name: Detect changes
145-
id: changes
146-
run: |
147-
if [ "${{ github.event_name }}" = "pull_request" ]; then
148-
base="${{ github.event.pull_request.base.sha }}"
149-
head="${{ github.event.pull_request.head.sha }}"
150-
else
151-
base="${{ github.event.before }}"
152-
head="${{ github.sha }}"
153-
fi
154-
if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
155-
echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
156-
fi
157-
if git diff --name-only "$base" "$head" | grep -Eq '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))'; then
158-
echo "relevant=true" >> "$GITHUB_OUTPUT"
159-
else
160-
echo "relevant=false" >> "$GITHUB_OUTPUT"
161-
fi
162-
163146
- name: Setup Rust
164-
if: steps.changes.outputs.relevant == 'true'
165147
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
166148
with:
167149
toolchain: stable
168150
targets: x86_64-unknown-linux-gnu
169151

170152
- name: Linux desktop deps
171-
if: steps.changes.outputs.relevant == 'true'
172153
run: |
173154
sudo apt-get update
174155
sudo apt-get install -y \
175156
libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev \
176157
libayatana-appindicator3-dev patchelf
177158
178159
- name: Setup Bun
179-
if: steps.changes.outputs.relevant == 'true'
180160
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
181161
with:
182162
bun-version: latest
183163

184164
- name: Build the UI (generate_context embeds frontendDist)
185-
if: steps.changes.outputs.relevant == 'true'
186165
run: |
187166
bun install --frozen-lockfile
188167
( cd frontend && bun run build )
189168
190169
- name: Compile the Tauri app (no bundle)
191-
if: steps.changes.outputs.relevant == 'true'
192170
run: cargo build -p kasumi-desktop
193171

194172
# ── Windows desktop compile smoke. Mirrors desktop-linux: the `rust` job runs on
@@ -198,54 +176,32 @@ jobs:
198176
# portable Rust shelling out to ip/iptables — so the `rust` workspace job already
199177
# compiles it on every PR. Only the rarer NDK cross-link is left to nightly.) ──
200178
desktop-windows:
179+
needs: changes
180+
if: needs.changes.outputs.desktop == 'true'
201181
runs-on: windows-latest
202182
steps:
203183
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
204184
with:
205185
fetch-depth: 0
206186

207-
- name: Detect changes
208-
id: changes
209-
shell: bash
210-
run: |
211-
if [ "${{ github.event_name }}" = "pull_request" ]; then
212-
base="${{ github.event.pull_request.base.sha }}"
213-
head="${{ github.event.pull_request.head.sha }}"
214-
else
215-
base="${{ github.event.before }}"
216-
head="${{ github.sha }}"
217-
fi
218-
if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
219-
echo "relevant=true" >> "$GITHUB_OUTPUT"; exit 0
220-
fi
221-
if git diff --name-only "$base" "$head" | grep -Eq '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))'; then
222-
echo "relevant=true" >> "$GITHUB_OUTPUT"
223-
else
224-
echo "relevant=false" >> "$GITHUB_OUTPUT"
225-
fi
226-
227187
- name: Setup Rust
228-
if: steps.changes.outputs.relevant == 'true'
229188
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
230189
with:
231190
toolchain: stable
232191
targets: x86_64-pc-windows-msvc
233192

234193
- name: Setup Bun
235-
if: steps.changes.outputs.relevant == 'true'
236194
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
237195
with:
238196
bun-version: latest
239197

240198
- name: Build the UI (generate_context embeds frontendDist)
241-
if: steps.changes.outputs.relevant == 'true'
242199
shell: bash
243200
run: |
244201
bun install --frozen-lockfile
245202
( cd frontend && bun run build )
246203
247204
- name: Stub the wintun bundle resource (compile-smoke doesn't bundle)
248-
if: steps.changes.outputs.relevant == 'true'
249205
shell: bash
250206
run: |
251207
# tauri-build validates declared bundle.resources at compile time. The
@@ -255,5 +211,25 @@ jobs:
255211
: > src-tauri/binaries/wintun.dll
256212
257213
- name: Compile the Tauri app (no bundle)
258-
if: steps.changes.outputs.relevant == 'true'
259214
run: cargo build -p kasumi-desktop
215+
216+
# ── The single required status check. It always runs and aggregates the gated
217+
# jobs above: a skipped area (its files weren't touched) is fine; only a real
218+
# failure or cancellation fails CI. Point branch protection at THIS context so
219+
# path-skipped jobs never block a merge. ──
220+
ci:
221+
needs: [changes, frontend, rust, desktop-linux, desktop-windows]
222+
if: always()
223+
runs-on: ubuntu-latest
224+
steps:
225+
- name: Verify no required job failed
226+
run: |
227+
results="${{ join(needs.*.result, ' ') }}"
228+
echo "job results: $results"
229+
for r in $results; do
230+
if [ "$r" = "failure" ] || [ "$r" = "cancelled" ]; then
231+
echo "a CI job did not pass"
232+
exit 1
233+
fi
234+
done
235+
echo "all CI jobs passed or were skipped"

0 commit comments

Comments
 (0)