-
Notifications
You must be signed in to change notification settings - Fork 0
350 lines (302 loc) · 12.5 KB
/
Copy pathrust.yml
File metadata and controls
350 lines (302 loc) · 12.5 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
339
340
341
342
343
344
345
346
347
348
349
350
name: Rust CI
# Path scoping lives in the `changes` job below, NOT in `on:`. A workflow that
# never starts because of a path filter never creates its check runs, and a
# required check that is never created sits "Expected — Waiting for status" on
# the PR forever: `mergeStateStatus` stays BLOCKED with nothing to click. That
# is exactly what happened to every cloud-only PR while these fourteen contexts
# were required and `paths-ignore: cloud/**` kept them from ever reporting.
#
# A job skipped by an `if:` condition is different: it *does* create a check
# run, concludes "skipped", and branch protection counts that as satisfied. So
# the workflow now always starts, decides once whether the Rust tree was
# touched, and lets every job skip itself when it wasn't.
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
# Cancel superseded runs on the same ref so hung/queued Windows jobs from
# already-pushed SHAs cannot starve the tip (observed 60+ min hangs with no
# job timeout during the macOS PR merge burst).
concurrency:
group: rust-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
# The `changes` job reads the PR's file list through the API rather than
# cloning full history to diff it.
pull-requests: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
# Decides once whether this change touches the Rust tree. Every job below
# gates on it, so the fourteen required contexts always report — as real
# results on a Rust change, as "skipped" (which branch protection accepts)
# on a cloud-only or docs-only one.
changes:
name: Detect changed paths
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
rust: ${{ steps.filter.outputs.rust }}
steps:
- id: filter
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
# On push (post-merge) we do not try to be clever: run everything.
# Only pull requests need accurate skipping, and only they are gated
# by branch protection.
if [ "${{ github.event_name }}" != "pull_request" ]; then
echo "rust=true" >> "$GITHUB_OUTPUT"
exit 0
fi
files=$(gh api \
"repos/${{ github.repository }}/pulls/${{ github.event.number }}/files" \
--paginate --jq '.[].filename')
# These are the paths Rust CI has no stake in — the same list that
# used to live in `paths-ignore`. If every changed file matches one
# of them, the Rust jobs skip. An empty or unreadable file list
# falls through to "true", so the failure mode is running CI we did
# not need rather than merging without it.
IGNORED='^(cloud/|docs/|public/)|^\.github/workflows/cloud\.yml$|\.md$'
if printf '%s\n' "$files" | grep -qvE "$IGNORED"; then
echo "rust=true" >> "$GITHUB_OUTPUT"
else
echo "rust=false" >> "$GITHUB_OUTPUT"
fi
check:
name: Check (${{ matrix.os }})
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
# ubuntu compiles via the headless backend (the fastest signal and the
# only job that exercises platform/headless.rs); it needs the GTK/webkit
# system libraries before cargo runs.
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libxdo-dev
- name: Check Rust crate
run: cargo check --manifest-path src-tauri/Cargo.toml --all-targets
test:
name: Test (${{ matrix.os }})
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ${{ matrix.os }}
# Windows cold `cargo test` normally finishes well under this; without a
# cap, a stuck runner holds the queue indefinitely.
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libxdo-dev
- name: Run unit tests
run: cargo test --manifest-path src-tauri/Cargo.toml
clippy:
name: Clippy (${{ matrix.os }})
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libxdo-dev
- name: Run clippy
run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings
fmt:
name: Rustfmt
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Check formatting
run: cargo fmt --manifest-path src-tauri/Cargo.toml -- --check
tauri-compile:
name: Tauri compile smoke test (${{ matrix.os }})
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
# cargo tauri build runs beforeBuildCommand (npm run build -> Vite),
# so the frontend toolchain must be installed first.
- uses: actions/setup-node@v7
with:
node-version: 20
cache: npm
- name: Install frontend dependencies
run: npm ci
- name: Install Tauri CLI
run: cargo install tauri-cli --version "^2.0" --locked
- name: Compile desktop app without packaging installers
run: cargo tauri build --no-bundle
experimental:
name: Experimental (${{ matrix.job }})
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
job: [check, test, clippy]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Install Linux dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libxdo-dev g++ libstdc++-13-dev
- name: Check (experimental)
if: matrix.job == 'check'
run: cargo check --manifest-path src-tauri/Cargo.toml --all-targets --features experimental
- name: Run unit tests (experimental)
if: matrix.job == 'test'
env:
CXX: g++
CC: gcc
run: cargo test --manifest-path src-tauri/Cargo.toml --features experimental
- name: Run clippy (experimental)
if: matrix.job == 'clippy'
run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets --features experimental -- -D warnings
version-consistency:
name: Version consistency
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# Source manifests must agree with each other; marketing files must agree
# with each other on the *published* release, which may trail source.
# Forcing README/site to carry the source version made CI demand we
# advertise unreleased builds (the false claim #281 removed).
- name: Check version alignment
run: |
SRC=$(grep '^version' src-tauri/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "Source version: $SRC"
for f in src-tauri/Cargo.toml src-tauri/tauri.conf.json; do
grep -q "$SRC" "$f" || { echo "Source version $SRC missing from $f"; exit 1; }
done
# public/index.html is the cloud-pivoted marketing site; it no longer
# advertises a specific desktop release (the legacy download CTA was
# deliberately dropped, see #396 and the site copy rewrite that
# followed). README.md still names the published desktop tag as
# historical reference. Only cross-check the two when the site
# actually mentions a version — its absence is not drift to catch,
# it's the current, intended state of a page that stopped talking
# about desktop releases at all.
REL=$(grep -oE 'v2\.[0-9]+\.[0-9]+' public/index.html | head -1)
echo "Advertised release: ${REL:-<none>}"
if [ -n "$REL" ]; then
grep -q "$REL" README.md || { echo "Advertised release $REL missing from README.md"; exit 1; }
fi
frontend-contract:
name: Frontend contract tests
needs: changes
if: ${{ !cancelled() && (needs.changes.result == 'failure' || needs.changes.outputs.rust == 'true') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: '20'
- name: Run compression-review contract tests
run: node --test src/compression-review.test.mjs
# The single required context for this workflow.
#
# Branch protection cannot depend on the matrix jobs directly. A matrix job
# skipped by an `if:` condition reports its check run under the *unexpanded*
# name — literally "Check (${{ matrix.os }})" — because the matrix never
# expands for a job that does not run. So requiring "Check (ubuntu-latest)"
# leaves it waiting forever on a cloud-only PR, which is the same deadlock
# this workflow's `changes` job was introduced to fix, one level down.
#
# A non-matrix aggregator has a static name in every case. It reports failure
# if any gated job failed, and success when they all skipped.
rust-ci:
name: Rust CI
needs:
- check
- test
- clippy
- fmt
- tauri-compile
- version-consistency
- frontend-contract
# `always()` so this still reports when its dependencies skipped — the
# entire point. Without it, a skipped dependency skips the aggregator too
# and the required context never appears.
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# GitHub expressions take single-quoted string literals only. An earlier
# revision used join(needs.*.result, ",") with double quotes, which is a
# workflow-file syntax error — the whole run fails to start, no jobs are
# created, and the PR shows no Rust checks at all rather than a failure
# pointing at this line.
- name: Fail if any gated Rust job failed
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "::error::a gated Rust CI job failed or was cancelled"
exit 1
- name: Report
run: echo "all gated Rust jobs passed or skipped"