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
permissions:
contents: read
name: Rust CI
on:
push:
branches:
pull_request:
branches:
workflow_call:
env:
CARGO_TERM_COLOR: always
PROTOC_VERSION: '3.25.3'
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
# The target dir is restored fresh each run (Swatinem cache), so incremental
# state is always cold and only bloats artifacts; worse, sccache refuses to
# cache incremental compilations, so leaving it on excludes the workspace
# crates from caching entirely.
CARGO_INCREMENTAL: "0"
jobs:
format:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check Bartender image pins
run: scripts/ci/sync-bartender-image.sh --check
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-16
components: rustfmt
- run: cargo fmt --all -- --check
clippy:
name: Clippy Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-16
components: clippy
# The voip example (built under --all-targets) links cpal, which needs ALSA headers.
- name: Install ALSA dev headers
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Install protoc
uses: taiki-e/install-action@v2
with:
tool: protoc@${{ env.PROTOC_VERSION }}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
# Cache the target dir too: sccache can't cache proc-macros, build scripts
# or bins (linker-output crate types), and those recompile every run.
# Swatinem restores them; sccache still fills in after a Cargo.lock change.
- name: Cache Rust build (registry + target)
uses: Swatinem/rust-cache@v2
with:
cache-targets: "true"
- name: Run Clippy
run: cargo clippy --all-targets -- -D warnings
test:
name: Build & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Reclaim unused Android SDK space
run: |
df -h /
sudo rm -rf --one-file-system /usr/local/lib/android
df -h /
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-16
# The voip example (built under --all-targets) links cpal, which needs ALSA headers.
- name: Install ALSA dev headers
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
# cargo-nextest ships pre-built binaries (nexte.st/docs/installation/pre-built-binaries),
# which is what install-action fetches — no compile step on the runner.
- name: Install protoc and cargo-nextest
uses: taiki-e/install-action@v2
with:
tool: protoc@${{ env.PROTOC_VERSION }},cargo-nextest
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
# Cache the target dir too: sccache can't cache proc-macros, build scripts
# or bins (linker-output crate types), and those recompile every run.
# Swatinem restores them; sccache still fills in after a Cargo.lock change.
- name: Cache Rust build (registry + target)
uses: Swatinem/rust-cache@v2
with:
cache-targets: "true"
- name: Build project
run: cargo build --workspace --exclude e2e-tests --all-targets --verbose
- name: Run tests
run: cargo nextest run --profile ci --workspace --exclude e2e-tests
# nextest cannot run doctests (nextest-rs/nextest#16) and silently reports
# none, so they need their own libtest step — the workspace has ~200 doc
# examples whose compilation is asserted nowhere else.
- name: Run doctests
run: cargo test --workspace --exclude e2e-tests --doc --verbose
# waproto serde tests are feature-gated and produce zero tests under the
# default feature set, so exercise each representation explicitly. No
# doctest counterpart: the features only swap derives on generated code,
# whose doc comments the step above already builds.
- name: Test waproto serde (enum repr)
run: cargo nextest run --profile ci -p waproto --features serde-enum-repr
- name: Test waproto serde (snake_case)
run: cargo nextest run --profile ci -p waproto --features serde-snake-case
test-all-features:
name: Build & Lint (all features)
runs-on: ubuntu-latest
# Runs on every PR (no voip path filter): the voip-gated code is scattered across the client,
# handler, send and stanza layers, so enumerating its files in a path filter kept leaking. The
# webrtc/opus tree compiles once and is cached (sccache), so the steady-state cost is the
# workspace recompile with voip on, not the deps. The heavy relay e2e tests stay out of `--lib`.
steps:
- uses: actions/checkout@v6
- name: Reclaim unused Android SDK space
run: |
df -h /
sudo rm -rf --one-file-system /usr/local/lib/android
df -h /
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-16
# The voip example (built under --all-features --all-targets) links cpal,
# which needs ALSA on Linux.
- name: Install ALSA dev headers
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Install protoc and cargo-nextest
uses: taiki-e/install-action@v2
with:
tool: protoc@${{ env.PROTOC_VERSION }},cargo-nextest
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
# Cache the target dir too: sccache can't cache proc-macros, build scripts
# or bins (linker-output crate types), and those recompile every run.
# Swatinem restores them; sccache still fills in after a Cargo.lock change.
- name: Cache Rust build (registry + target)
uses: Swatinem/rust-cache@v2
with:
cache-targets: "true"
- name: Build (all features)
run: cargo build --workspace --exclude e2e-tests --all-features --all-targets --verbose
- name: Clippy (all features)
run: cargo clippy --workspace --exclude e2e-tests --all-features --all-targets -- -D warnings
# No `cargo test --all-features`: it enables the `danger-skip-*-verify` flags, which disable
# security verification (the cert-chain negative test is even cfg'd out), so the suite would
# change behavior rather than catch rot; build + clippy cover that. The voip suite runs under
# the explicit `voip` feature set instead, never `--all-features`.
- name: Test (voip)
run: |
cargo nextest run --profile ci -p wacore --features voip --lib
cargo nextest run --profile ci -p whatsapp-rust --features "voip tokio-native tokio-transport" --lib
# legacy-session-interop is off by default, so every other test job
# compiles its module away and never runs a single one of its tests.
- name: Test (legacy-session-interop)
run: cargo nextest run --profile ci -p wacore-libsignal --features legacy-session-interop
rustdoc:
name: Rustdoc
runs-on: ubuntu-latest
env:
# Broken intra-doc links and malformed doc attributes are only surfaced by
# rustdoc, never by clippy or rustc, so they need their own gate.
RUSTDOCFLAGS: "-D warnings"
steps:
- uses: actions/checkout@v6
- name: Reclaim unused Android SDK space
run: |
df -h /
sudo rm -rf --one-file-system /usr/local/lib/android
df -h /
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-16
# The voip-cli member links cpal, which needs ALSA headers.
- name: Install ALSA dev headers
run: sudo apt-get update && sudo apt-get install -y libasound2-dev
- name: Install protoc
uses: taiki-e/install-action@v2
with:
tool: protoc@${{ env.PROTOC_VERSION }}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
# Cache the target dir too: sccache can't cache proc-macros, build scripts
# or bins (linker-output crate types), and those recompile every run.
# Swatinem restores them; sccache still fills in after a Cargo.lock change.
- name: Cache Rust build (registry + target)
uses: Swatinem/rust-cache@v2
with:
cache-targets: "true"
# --all-features so the voip-gated docs are covered too. Default features
# alone would leave their links unchecked, and docs.rs builds with features
# on (hence the `cfg(docsrs)` in the workspace check-cfg).
- name: Build docs
run: cargo doc --workspace --no-deps --all-features
feature-matrix:
name: Feature Matrix (each feature)
runs-on: ubuntu-latest
# Individual features are only ever exercised in the combinations the other
# jobs happen to build (default / --all-features), so a feature whose gates
# are wrong compiles fine there and breaks for a downstream consumer that
# picks it alone. Restricted to the published library crates; bin/test
# members carry no feature surface worth enumerating.
steps:
- uses: actions/checkout@v6
- name: Reclaim unused Android SDK space
run: |
df -h /
sudo rm -rf --one-file-system /usr/local/lib/android
df -h /
# `simd` needs nightly, so the matrix runs on the pinned toolchain.
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-16
- name: Install protoc
uses: taiki-e/install-action@v2
with:
tool: protoc@${{ env.PROTOC_VERSION }}
- name: Install cargo-hack
uses: taiki-e/install-action@v2
with:
tool: cargo-hack
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
# Cache the target dir too: sccache can't cache proc-macros, build scripts
# or bins (linker-output crate types), and those recompile every run.
# Swatinem restores them; sccache still fills in after a Cargo.lock change.
- name: Cache Rust build (registry + target)
uses: Swatinem/rust-cache@v2
with:
cache-targets: "true"
# --no-dev-deps: dev-dependencies enable features on the crate under test
# (wacore/test-util, for one), which would mask exactly the gaps this
# job looks for.
- name: Check each feature
run: >
cargo hack check --each-feature --no-dev-deps
-p whatsapp-rust -p wacore -p wacore-binary -p wacore-appstate
-p wacore-libsignal -p wacore-noise -p waproto
test-stable:
name: Test Stable (no-simd)
runs-on: ubuntu-latest
# .cargo/config.toml sets nightly-only rustflags (-Zshare-generics) for the
# x86_64-linux target; a set-but-empty RUSTFLAGS takes precedence over
# config rustflags, keeping this stable-toolchain job buildable.
env:
RUSTFLAGS: ""
steps:
- uses: actions/checkout@v6
# Pinned rather than `@stable`: this version is the workspace MSRV
# (`rust-version` in the root Cargo.toml). To raise the floor, bump both
# together — a floating `stable` would silently let the declared MSRV rot.
- uses: dtolnay/rust-toolchain@1.94.1
# The pre-built nextest binary is toolchain-independent — it drives this
# MSRV cargo the same way it drives the nightly one.
- name: Install protoc and cargo-nextest
uses: taiki-e/install-action@v2
with:
tool: protoc@${{ env.PROTOC_VERSION }},cargo-nextest
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
# Cache the target dir too: sccache can't cache proc-macros, build scripts
# or bins (linker-output crate types), and those recompile every run.
# Swatinem restores them; sccache still fills in after a Cargo.lock change.
- name: Cache Rust build (registry + target)
uses: Swatinem/rust-cache@v2
with:
cache-targets: "true"
- name: Build wacore-binary (stable, no SIMD)
run: cargo build -p wacore-binary --no-default-features --verbose
- name: Test wacore-binary (stable, no SIMD)
run: cargo nextest run --profile ci -p wacore-binary --no-default-features --lib
- name: Build wacore-appstate (stable, no SIMD)
run: cargo build -p wacore-appstate --no-default-features --verbose
- name: Test wacore-appstate (stable, no SIMD)
run: cargo nextest run --profile ci -p wacore-appstate --no-default-features --lib
- name: Build wacore (stable, no SIMD)
run: cargo build -p wacore --no-default-features --verbose
- name: Test wacore (stable, no SIMD)
run: cargo nextest run --profile ci -p wacore --no-default-features --lib
# `rust-version` is published metadata for every member, but only the
# three crates above are exercised at that toolchain. This compiles the
# rest of the publishable set so the declared floor is a checked promise
# rather than an assertion. Check-only: the tests that matter here
# already ran above, and this is about the toolchain accepting the code.
- name: Check the remaining published crates at MSRV
run: >
cargo check --no-default-features --verbose
-p whatsapp-rust -p wacore-derive -p wacore-libsignal -p wacore-noise -p waproto
-p whatsapp-rust-ureq-http-client -p whatsapp-rust-chat-store
-p whatsapp-rust-sqlite-storage -p whatsapp-rust-tokio-transport