1 name: CI
2
3 on:
4 # Branches only — a tag push must NOT fire CI. Release tags point at a commit
5 # that main already validated, so re-running CI on the tag adds 13 macos jobs
6 # that compete with the release workflows for the shared macOS runner pool.
7 push:
8 branches:
9 - "**"
10 pull_request:
11 workflow_dispatch:
12
13 concurrency:
14 group: ci-${{ github.ref }}
15 cancel-in-progress: true
16
17 permissions:
18 contents: read
19
20 jobs:
21 # ── Toolchain pin ─────────────────────────────────────────────────────────
22 # rust-toolchain.toml is the single source of truth for the Rust version, but
23 # dtolnay/rust-toolchain takes `toolchain` as a required input and will not
24 # read that file. Left implicit, the action installs plain `stable` and adds
25 # the requested components/targets to *it*, while bare `cargo` in this repo
26 # resolves to the pinned channel instead — a different toolchain that has
27 # none of them ("cargo-fmt is not installed for 1.94.0", "can't find crate
28 # for `core`"). Resolve the pin once here and feed it to every Rust job so
29 # the two halves agree, without restating the version in this file.
30 toolchain:
31 name: Toolchain
32 runs-on: ubuntu-latest
33 outputs:
34 channel: ${{ steps.read.outputs.channel }}
35 steps:
36 - uses: actions/checkout@v6
37 - id: read
38 run: |
39 channel="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
40 if [ -z "$channel" ]; then
41 echo "::error::Failed to read channel from rust-toolchain.toml"
42 exit 1
43 fi
44 echo "channel=${channel}" >> "$GITHUB_OUTPUT"
45
46 # ── Release metadata ─────────────────────────────────────────────────────
47 # Same gate the release workflows use, minus the tag check. Running it on
48 # ordinary pushes means version drift surfaces while you are still on a
49 # branch, instead of on release day.
50 preflight:
51 uses: ./.github/workflows/release-preflight.yml
52
53 # ── Dart / Flutter SDK validation ────────────────────────────────────────
54 dart-sdk:
55 name: Dart SDK (Flutter)
56 runs-on: ubuntu-latest
57 steps:
58 - uses: actions/checkout@v6
59 - uses: subosito/flutter-action@v2
60 with:
61 flutter-version-file: sdk/dart/.flutter-version
62 channel: stable
63 cache: true
64 - name: Install Dart dependencies
65 working-directory: sdk/dart
66 run: flutter pub get
67 - name: Analyze Dart package
68 working-directory: sdk/dart
69 run: flutter analyze --no-fatal-infos
70 - name: Analyze score-sensitive generated Dart files
71 working-directory: sdk/dart
72 run: |
73 flutter analyze lib/src/frb_generated.dart/api.dart
74 flutter analyze lib/src/frb_generated.dart/frb_generated.dart
75 flutter analyze lib/src/frb_generated.dart/frb_generated.io.dart
76 - name: Verify Apple SwiftPM manifests exist
77 working-directory: sdk/dart
78 run: |
79 test -f ios/onde_inference/Package.swift
80 test -f ios/onde_inference/Sources/onde_inference/OndeInferencePlugin.swift
81 test -f macos/onde_inference/Package.swift
82 test -f macos/onde_inference/Sources/onde_inference/OndeInferencePlugin.swift
83 - name: Run Dart package tests
84 working-directory: sdk/dart
85 run: flutter test
86 - name: Restore generated files modified by flutter pub get
87 working-directory: sdk/dart
88 run: git checkout -- . 2>/dev/null || true
89 - name: Dry-run Dart publish
90 working-directory: sdk/dart
91 run: flutter pub publish --dry-run
92
93 # ── Formatting ────────────────────────────────────────────────────────────
94 fmt:
95 name: Format
96 runs-on: macos-26
97 needs: toolchain
98 steps:
99 - uses: actions/checkout@v6
100 - uses: dtolnay/rust-toolchain@stable
101 with:
102 toolchain: ${{ needs.toolchain.outputs.channel }}
103 components: rustfmt
104 - run: cargo fmt --all -- --check
105
106 # ── Lint ──────────────────────────────────────────────────────────────────
107 clippy:
108 name: Clippy (macOS)
109 runs-on: macos-26
110 needs: toolchain
111 env:
112 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
113 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
114 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
115 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
116 HF_TOKEN: ${{ secrets.HF_TOKEN }}
117 steps:
118 - uses: actions/checkout@v6
119 - uses: dtolnay/rust-toolchain@stable
120 with:
121 toolchain: ${{ needs.toolchain.outputs.channel }}
122 components: clippy
123 - uses: Swatinem/rust-cache@v2
124 with:
125 cache-bin: false
126 - run: cargo clippy --all-targets -- -D warnings
127
128 # ── Tests (macOS — the only platform where tests run natively) ────────────
129 test:
130 name: Test (macOS)
131 runs-on: macos-26
132 needs: toolchain
133 env:
134 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
135 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
136 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
137 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
138 HF_TOKEN: ${{ secrets.HF_TOKEN }}
139 steps:
140 - uses: actions/checkout@v6
141 - uses: dtolnay/rust-toolchain@stable
142 with:
143 toolchain: ${{ needs.toolchain.outputs.channel }}
144 - uses: Swatinem/rust-cache@v2
145 with:
146 cache-bin: false
147 - run: cargo test --lib
148
149 # ── Cross-compile checks for Darwin platforms (highest priority) ─────────
150 check-macos-arm64:
151 name: Check (macOS arm64)
152 runs-on: macos-26
153 needs: toolchain
154 env:
155 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
156 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
157 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
158 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
159 HF_TOKEN: ${{ secrets.HF_TOKEN }}
160 steps:
161 - uses: actions/checkout@v6
162 - uses: dtolnay/rust-toolchain@stable
163 with:
164 toolchain: ${{ needs.toolchain.outputs.channel }}
165 targets: aarch64-apple-darwin
166 - uses: Swatinem/rust-cache@v2
167 with:
168 cache-bin: false
169 key: macos-arm64
170 - run: cargo check --target aarch64-apple-darwin
171
172 check-macos-intel:
173 name: Check (macOS x86_64)
174 runs-on: macos-26
175 needs: toolchain
176 env:
177 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
178 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
179 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
180 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
181 HF_TOKEN: ${{ secrets.HF_TOKEN }}
182 steps:
183 - uses: actions/checkout@v6
184 - uses: dtolnay/rust-toolchain@stable
185 with:
186 toolchain: ${{ needs.toolchain.outputs.channel }}
187 targets: x86_64-apple-darwin
188 - uses: Swatinem/rust-cache@v2
189 with:
190 cache-bin: false
191 key: macos-intel
192 - run: cargo check --target x86_64-apple-darwin
193
194 check-ios:
195 name: Check (iOS device)
196 runs-on: macos-26
197 needs: toolchain
198 env:
199 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
200 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
201 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
202 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
203 HF_TOKEN: ${{ secrets.HF_TOKEN }}
204 steps:
205 - uses: actions/checkout@v6
206 - uses: dtolnay/rust-toolchain@stable
207 with:
208 toolchain: ${{ needs.toolchain.outputs.channel }}
209 targets: aarch64-apple-ios
210 - uses: Swatinem/rust-cache@v2
211 with:
212 cache-bin: false
213 key: ios
214 - run: cargo check --target aarch64-apple-ios
215
216 check-ios-sim:
217 name: Check (iOS simulator)
218 runs-on: macos-26
219 needs: toolchain
220 env:
221 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
222 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
223 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
224 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
225 HF_TOKEN: ${{ secrets.HF_TOKEN }}
226 steps:
227 - uses: actions/checkout@v6
228 - uses: dtolnay/rust-toolchain@stable
229 with:
230 toolchain: ${{ needs.toolchain.outputs.channel }}
231 targets: aarch64-apple-ios-sim
232 - uses: Swatinem/rust-cache@v2
233 with:
234 cache-bin: false
235 key: ios-sim
236 - run: cargo check --target aarch64-apple-ios-sim
237
238 check-tvos:
239 name: Check (tvOS device)
240 runs-on: macos-26
241 env:
242 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
243 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
244 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
245 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
246 HF_TOKEN: ${{ secrets.HF_TOKEN }}
247 steps:
248 - uses: actions/checkout@v6
249 - uses: dtolnay/rust-toolchain@nightly
250 with:
251 components: rust-src
252 - uses: Swatinem/rust-cache@v2
253 with:
254 cache-bin: false
255 key: tvos
256 - run: cargo +nightly check -Z build-std --target aarch64-apple-tvos
257
258 check-tvos-sim:
259 name: Check (tvOS simulator)
260 runs-on: macos-26
261 env:
262 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
263 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
264 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
265 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
266 HF_TOKEN: ${{ secrets.HF_TOKEN }}
267 steps:
268 - uses: actions/checkout@v6
269 - uses: dtolnay/rust-toolchain@nightly
270 with:
271 components: rust-src
272 - uses: Swatinem/rust-cache@v2
273 with:
274 cache-bin: false
275 key: tvos-sim
276 - run: cargo +nightly check -Z build-std --target aarch64-apple-tvos-sim
277
278 check-visionos:
279 name: Check (visionOS device)
280 runs-on: macos-26
281 env:
282 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
283 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
284 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
285 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
286 HF_TOKEN: ${{ secrets.HF_TOKEN }}
287 steps:
288 - uses: actions/checkout@v6
289 - uses: dtolnay/rust-toolchain@nightly
290 with:
291 components: rust-src
292 - uses: Swatinem/rust-cache@v2
293 with:
294 cache-bin: false
295 key: visionos
296 - run: cargo +nightly check -Z build-std --target aarch64-apple-visionos
297
298 check-visionos-sim:
299 name: Check (visionOS simulator)
300 runs-on: macos-26
301 env:
302 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
303 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
304 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
305 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
306 HF_TOKEN: ${{ secrets.HF_TOKEN }}
307 steps:
308 - uses: actions/checkout@v6
309 - uses: dtolnay/rust-toolchain@nightly
310 with:
311 components: rust-src
312 - uses: Swatinem/rust-cache@v2
313 with:
314 cache-bin: false
315 key: visionos-sim
316 - run: cargo +nightly check -Z build-std --target aarch64-apple-visionos-sim
317
318 check-watchos:
319 name: Check (watchOS device)
320 runs-on: macos-26
321 env:
322 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
323 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
324 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
325 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
326 HF_TOKEN: ${{ secrets.HF_TOKEN }}
327 steps:
328 - uses: actions/checkout@v6
329 - uses: dtolnay/rust-toolchain@nightly
330 with:
331 components: rust-src
332 - uses: Swatinem/rust-cache@v2
333 with:
334 cache-bin: false
335 key: watchos
336 - run: cargo +nightly check -Z build-std --target aarch64-apple-watchos
337
338 check-watchos-sim:
339 name: Check (watchOS simulator)
340 runs-on: macos-26
341 env:
342 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
343 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
344 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
345 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
346 HF_TOKEN: ${{ secrets.HF_TOKEN }}
347 steps:
348 - uses: actions/checkout@v6
349 - uses: dtolnay/rust-toolchain@nightly
350 with:
351 components: rust-src
352 - uses: Swatinem/rust-cache@v2
353 with:
354 cache-bin: false
355 key: watchos-sim
356 - run: cargo +nightly check -Z build-std --target aarch64-apple-watchos-sim
357
358 # ── Cross-compile checks for Unix-family platforms ────────────────────────
359 check-linux:
360 name: Check (Linux x86_64)
361 runs-on: ubuntu-latest
362 needs: toolchain
363 env:
364 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
365 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
366 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
367 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
368 HF_TOKEN: ${{ secrets.HF_TOKEN }}
369 steps:
370 - uses: actions/checkout@v6
371 - uses: dtolnay/rust-toolchain@stable
372 with:
373 toolchain: ${{ needs.toolchain.outputs.channel }}
374 targets: x86_64-unknown-linux-gnu
375 - uses: Swatinem/rust-cache@v2
376 with:
377 cache-bin: false
378 key: linux-x86_64
379 - run: cargo check --target x86_64-unknown-linux-gnu
380
381 check-android:
382 name: Check (Android arm64)
383 runs-on: ubuntu-latest
384 needs: toolchain
385 env:
386 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
387 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
388 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
389 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
390 HF_TOKEN: ${{ secrets.HF_TOKEN }}
391 CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER: aarch64-linux-android34-clang
392 CC_aarch64_linux_android: aarch64-linux-android34-clang
393 AR_aarch64_linux_android: llvm-ar
394 steps:
395 - uses: actions/checkout@v6
396 - uses: dtolnay/rust-toolchain@stable
397 with:
398 toolchain: ${{ needs.toolchain.outputs.channel }}
399 targets: aarch64-linux-android
400 - uses: android-actions/setup-android@v4
401 - name: Configure Android NDK toolchain
402 shell: bash
403 run: |
404 echo "${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin" >> "$GITHUB_PATH"
405 - uses: Swatinem/rust-cache@v2
406 with:
407 cache-bin: false
408 key: android-arm64
409 - run: cargo check --target aarch64-linux-android
410 # The Dart SDK's FFI crate is what actually ships in the Android AAR, so
411 # check it too — a core change can compile while breaking the bridge.
412 - name: Check Dart FFI crate
413 run: cargo check --target aarch64-linux-android --manifest-path sdk/dart/rust/Cargo.toml
414
415 # ── Cross-compile checks for Windows (lowest priority) ────────────────────
416 check-windows:
417 name: Check (Windows x86_64)
418 runs-on: windows-latest
419 needs: toolchain
420 env:
421 GRESIQ_API_KEY_DEV: ${{ secrets.GRESIQ_API_KEY_DEV }}
422 GRESIQ_API_SECRET_DEV: ${{ secrets.GRESIQ_API_SECRET_DEV }}
423 GRESIQ_API_KEY_PRODUCTION: ${{ secrets.GRESIQ_API_KEY_PRODUCTION }}
424 GRESIQ_API_SECRET_PRODUCTION: ${{ secrets.GRESIQ_API_SECRET_PRODUCTION }}
425 HF_TOKEN: ${{ secrets.HF_TOKEN }}
426 steps:
427 - uses: actions/checkout@v6
428 - uses: dtolnay/rust-toolchain@stable
429 with:
430 toolchain: ${{ needs.toolchain.outputs.channel }}
431 targets: x86_64-pc-windows-msvc
432 - uses: Swatinem/rust-cache@v2
433 with:
434 cache-bin: false
435 key: windows-x86_64
436 - run: cargo check --target x86_64-pc-windows-msvc