-
Notifications
You must be signed in to change notification settings - Fork 1
338 lines (302 loc) · 13.9 KB
/
Copy pathci.yml
File metadata and controls
338 lines (302 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
# ── One cheap job computes which areas a PR touches; the heavy jobs gate on its
# outputs (`needs` + job-level `if`), so an irrelevant area's job never starts
# (no runner spun up — notably the Windows one). A skipped job doesn't satisfy a
# required status check, so the single required check is the `ci` aggregator at
# the bottom, not the individual jobs. ──
changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.detect.outputs.frontend }}
rust: ${{ steps.detect.outputs.rust }}
desktop: ${{ steps.detect.outputs.desktop }}
nix: ${{ steps.detect.outputs.nix }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Detect changes
id: detect
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"
head="${{ github.event.pull_request.head.sha }}"
else
base="${{ github.event.before }}"
head="${{ github.sha }}"
fi
# No usable base (first push, force-push, shallow clone) → run everything.
if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then
{
echo "frontend=true"
echo "rust=true"
echo "desktop=true"
echo "nix=true"
} >> "$GITHUB_OUTPUT"
exit 0
fi
# Three-dot: diff from the merge-base, not base..head directly. When the
# branch is behind the base (main advanced after it forked — the common
# case), a two-dot diff would also list every file main changed since,
# falsely flagging areas the PR never touched. This mirrors GitHub's own
# PR diff semantics.
files=$(git diff --name-only "$base...$head")
match() { printf '%s\n' "$files" | grep -Eq "$1"; }
fe=false; rs=false; dk=false; nx=false
match '^(frontend/|package\.json|bun\.lock|biome\.json)' && fe=true
match '^(crates/|src-tauri/|Cargo\.(toml|lock)|flake\.(nix|lock))' && rs=true
match '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))' && dk=true
match '^(nix/|flake\.(nix|lock))' && nx=true
{
echo "frontend=$fe"
echo "rust=$rs"
echo "desktop=$dk"
echo "nix=$nx"
} >> "$GITHUB_OUTPUT"
# ── Frontend: bun workspace (lockfile at the repo root), build, tests, i18n.
# Formatting/lint (biome) is covered by the `treefmt` job, not here. ──
frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
- name: Install dependencies
# The bun workspace is rooted at the repo; install from the root so the
# frontend member and its deps resolve against the single bun.lock.
run: bun install --frozen-lockfile
- name: Build (tsc + vite)
working-directory: frontend
run: bun run build
- name: Unit tests
working-directory: frontend
run: bunx vitest run
- name: i18n dictionaries
run: bun run frontend/scripts/check-i18n.ts
# ── Rust workspace: clippy, tests, and generated-bindings drift, all in
# the flake devshell (carries the rust toolchain + webkit for src-tauri). ──
rust:
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14
with:
extra-conf: |
accept-flake-config = true
# ── Nix store cache (cachix). Pulls the devshell closure from our public
# cache as a substituter and pushes anything newly built at the end. Reads
# need no token (public cache), so fork PRs benefit too; pushes use the
# CACHIX_AUTH_TOKEN secret (absent on forks → read-only there). Replaces the
# retired magic-nix-cache, whose GHA backend was a no-op. ──
- name: Cachix
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: kasumi-proxy
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
# ── Cargo registry — dependency sources only, no compiled artifacts, so it's
# portable across toolchain bumps and keyed on Cargo.lock alone. ──
- name: Cache cargo registry
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: cargo-registry-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-registry-${{ runner.os }}-
# ── Workspace build artifacts (./target). The compile of clippy --all-targets +
# test + codegen (hundreds of crates: tauri, specta, webkit bindings) is the
# bulk of this job; an empty target means a full rebuild every run. But the
# build-script binaries under target are linked against the devshell's glibc,
# so a target restored from a different flake.lock points its ELF interpreter
# at a /nix/store loader that's no longer there and fails to exec ("No such
# file or directory"). So the key AND the restore fallback are both bound to
# flake.lock: a Cargo.lock-only bump still reuses target (only the changed
# crates recompile), but a flake.lock/toolchain bump starts clean instead of
# restoring a poisoned target. The devshell sets no CARGO_HOME /
# CARGO_TARGET_DIR, so cargo uses ~/.cargo and ./target on the host. ──
- name: Cache cargo build
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: target
key: cargo-target-${{ runner.os }}-${{ hashFiles('flake.lock') }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-target-${{ runner.os }}-${{ hashFiles('flake.lock') }}-
- name: Stub the libcronet bundle resource (clippy/codegen build the app)
run: |
# clippy/codegen compile kasumi-desktop, whose tauri.linux.conf.json
# declares the libcronet.so resource; tauri-build checks the path exists.
# The real lib is fetched only for the bundle — a placeholder suffices.
mkdir -p src-tauri/binaries
: > src-tauri/binaries/libcronet.so
- name: clippy + test + codegen drift
run: |
nix develop --command bash -euo pipefail -c '
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
# The generated frontend bindings/schemas/defaults must match the Rust
# source — regenerate and fail on any diff.
cargo run -p kasumi-desktop --bin codegen
git diff --exit-code -- frontend/src/generated
'
# ── Realise the desktop package derivation. The `rust` job only runs cargo in the
# devshell, never `nix build .#kasumi-desktop`, so installPhase / wrapper / icon
# staging were unchecked on PRs. Path-gated on the nix expressions. ──
nix-build:
needs: changes
if: needs.changes.outputs.nix == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14
with:
extra-conf: |
accept-flake-config = true
- name: Cachix
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: kasumi-proxy
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build the desktop package
run: nix build .#kasumi-desktop --accept-flake-config --print-build-logs
# ── The formatting gate for the whole tree — the single source of truth. This
# realises the flake's own treefmt check derivation (the exact `nix fmt` config,
# no duplicated formatter list), which runs rustfmt, biome (format + lint),
# shellcheck, and the nix formatters (alejandra/statix/deadnix) over every tracked
# file. Because it covers all of them, the per-area jobs no longer re-run
# `cargo fmt` / `biome` / `shellcheck`. Ungated on purpose: it runs on every PR so
# no area can slip an unformatted file past a path filter (a Rust-only or
# scripts-only change used to skip the comprehensive check entirely). ──
treefmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14
with:
extra-conf: |
accept-flake-config = true
- name: Cachix
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: kasumi-proxy
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: treefmt check (fails on any unformatted file)
run: nix build .#checks.x86_64-linux.treefmt --accept-flake-config --print-build-logs
# ── Desktop compile smoke (Linux + Windows) on the plain rustup toolchain — the
# path release/nightly actually bundle with (the nix `nix-build` job realises the
# flake package; this proves the non-nix one). Compile-only (no bundle): catches
# platform/linker breakage without the slow installer. macOS is still deferred
# (no Platform port yet). ──
desktop-linux:
needs: changes
if: needs.changes.outputs.desktop == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14
with:
extra-conf: |
accept-flake-config = true
- name: Cachix
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: kasumi-proxy
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Stub the libcronet bundle resource (compile-smoke doesn't bundle)
run: |
mkdir -p src-tauri/binaries
: > src-tauri/binaries/libcronet.so
- name: Compile the Tauri app (no bundle)
run: |
nix develop --command bash -euo pipefail -c '
bun install --frozen-lockfile
( cd frontend && bun run build )
cargo build -p kasumi-desktop
'
# ── Windows desktop compile smoke. Mirrors desktop-linux: the `rust` job runs on
# Linux and never compiles the #[cfg(windows)] Platform + Win32 code, so this is
# what catches a Windows-build break in the PR instead of the next nightly.
# (The Android daemon needs no such job: it has no cfg(target_os) gates — just
# portable Rust shelling out to ip/iptables — so the `rust` workspace job already
# compiles it on every PR. Only the rarer NDK cross-link is left to nightly.) ──
desktop-windows:
needs: changes
if: needs.changes.outputs.desktop == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- name: Setup Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
targets: x86_64-pc-windows-msvc
- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
- name: Build the UI (generate_context embeds frontendDist)
shell: bash
run: |
bun install --frozen-lockfile
( cd frontend && bun run build )
- name: Stub the bundle resources (compile-smoke doesn't bundle)
shell: bash
run: |
# tauri-build validates declared bundle.resources at compile time. The
# real wintun.dll / libcronet.dll / msys-2.0.dll are fetched only for the
# nightly/release bundle; placeholders satisfy the path check for this
# compile-only job.
mkdir -p src-tauri/binaries
: > src-tauri/binaries/wintun.dll
: > src-tauri/binaries/libcronet.dll
: > src-tauri/binaries/msys-2.0.dll
- name: Compile the Tauri app (no bundle)
run: cargo build -p kasumi-desktop
# ── The single required status check. It always runs and aggregates the gated
# jobs above: a skipped area (its files weren't touched) is fine; only a real
# failure or cancellation fails CI. Point branch protection at THIS context so
# path-skipped jobs never block a merge. ──
ci:
needs: [changes, frontend, rust, nix-build, treefmt, desktop-linux, desktop-windows]
if: always()
runs-on: ubuntu-latest
steps:
- name: Verify no required job failed
run: |
results="${{ join(needs.*.result, ' ') }}"
echo "job results: $results"
for r in $results; do
if [ "$r" = "failure" ] || [ "$r" = "cancelled" ]; then
echo "a CI job did not pass"
exit 1
fi
done
echo "all CI jobs passed or were skipped"