fix(ci): validate all jniLibs ABIs and exclude armeabi-v7a from debug builds#173
Conversation
… builds Two CI failures on master since 2026-07-02: 1. `INSTALL_FAILED_NO_MATCHING_ABIS` in E2E tests: the actions/cache v3→v4 upgrade in PR ActivityWatch#160 invalidated the jniLibs cache, forcing a fresh Rust debug build. Building all 4 ABIs in debug mode (~160MB+ per .so) exhausts runner disk space, leaving the armeabi-v7a .so with a corrupt ELF section header table (e_shoff=0x9d93a8c past EOF). The old x86_64-only check passes silently, the corrupt .so is uploaded, and the E2E build fails when llvm-strip tries to process it. Fixes: - `.github/workflows/build.yml`: replace the x86_64-only existence check with a Python-based ELF validity check for all 4 ABIs. This catches corruption at build-rust time with a clear diagnostic instead of a cryptic INSTALL_FAILED_NO_MATCHING_ABIS at E2E time. - `mobile/build.gradle`: add `abiFilters 'arm64-v8a', 'x86_64'` to the debug build type. Debug APKs used for E2E testing on x86_64 emulators don't need armeabi-v7a or x86 libs. Filtering them out prevents a corrupt ARM7 .so from blocking the E2E install even if the CI check is bypassed in future. Note: the `age: no identity matched any of the recipients` failure in the `build-aab` job is a separate signing-infrastructure issue not addressed here.
Greptile SummaryThis PR improves CI reliability after master broke on 2026-07-02 by replacing a single-file existence check with a Python ELF validator that inspects all 4 ABIs, and by limiting debug APK packaging to
Confidence Score: 5/5Safe to merge — the ELF validator is stdlib-only Python with correct header parsing, and the abiFilters follows standard Android Gradle Plugin conventions without touching release builds. Both changes are narrowly scoped: the Python script adds validation that was clearly missing, and the Gradle change is a standard per-build-type ABI filter. The ELF header parsing logic is correct for both 32-bit and 64-bit classes, the short-circuit accumulation pattern evaluates every ABI, and the truncation guard comes after the magic check. The abiFilters is release-build-safe. The one design tension — the check script still validates armeabi-v7a even though the debug APK no longer needs it — is a future-proofing gap rather than a current defect. No files require special attention, though the alignment between ABIS in check-jnilibs.py and the debug abiFilters in mobile/build.gradle is worth revisiting if disk-space-driven arm7 corruption recurs. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant MR as make aw-server-rust
participant ELF as check-jnilibs.py
participant ART as upload-artifact
participant E2E as test-e2e job
participant GRADLE as Gradle assembleDebug
Note over MR: build-rust job (all 4 ABIs)
MR->>ELF: jniLibs produced
alt All 4 ABIs valid ELF
ELF->>ART: upload artifact
ART->>E2E: download artifact
E2E->>GRADLE: assemble debug APK
Note over GRADLE: abiFilters excludes armeabi-v7a and x86
GRADLE-->>E2E: debug APK with arm64-v8a + x86_64 only
else Any ABI corrupt or missing
ELF--xART: FAIL with clear CORRUPT diagnostic
Note over E2E: No artifact uploaded
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant MR as make aw-server-rust
participant ELF as check-jnilibs.py
participant ART as upload-artifact
participant E2E as test-e2e job
participant GRADLE as Gradle assembleDebug
Note over MR: build-rust job (all 4 ABIs)
MR->>ELF: jniLibs produced
alt All 4 ABIs valid ELF
ELF->>ART: upload artifact
ART->>E2E: download artifact
E2E->>GRADLE: assemble debug APK
Note over GRADLE: abiFilters excludes armeabi-v7a and x86
GRADLE-->>E2E: debug APK with arm64-v8a + x86_64 only
else Any ABI corrupt or missing
ELF--xART: FAIL with clear CORRUPT diagnostic
Note over E2E: No artifact uploaded
end
Reviews (2): Last reviewed commit: "fix(ci): reuse jniLibs ELF validation" | Re-trigger Greptile |
| if header[:4] != b'\x7fELF': | ||
| print(f'NOT_ELF {path}', file=sys.stderr) | ||
| ok = False | ||
| continue | ||
| e_class = header[4] # 1=32-bit, 2=64-bit |
There was a problem hiding this comment.
The script reads 64 bytes but never checks that
len(header) is actually 64 before calling struct.unpack. If a file begins with \x7fELF but is truncated before offset 36 (ELFCLASS32) or offset 48 (ELFCLASS64), the slice header[32:36] or header[40:48] is shorter than required, and struct.unpack raises struct.error with an unhandled exception. CI still fails, but the error is a Python traceback rather than the intended CORRUPT diagnostic.
| if header[:4] != b'\x7fELF': | |
| print(f'NOT_ELF {path}', file=sys.stderr) | |
| ok = False | |
| continue | |
| e_class = header[4] # 1=32-bit, 2=64-bit | |
| if len(header) < 64: | |
| print(f'CORRUPT {path}: file too small for ELF header ' | |
| f'({len(header)} bytes)', file=sys.stderr) | |
| ok = False | |
| continue | |
| if header[:4] != b'\x7fELF': | |
| print(f'NOT_ELF {path}', file=sys.stderr) | |
| ok = False | |
| continue | |
| e_class = header[4] # 1=32-bit, 2=64-bit |
|
@greptileai review |
Problem
Master CI has been failing since 2026-07-02 with two distinct issues:
1.
INSTALL_FAILED_NO_MATCHING_ABISin E2E testsRoot cause chain:
actions/cache v3→v4. GitHub Actions cache v4 uses a different backend, so all old v3 caches were invalidated.armeabi-v7a/libaw_server.somid-write (corrupt ELF:e_shoff=0x9d93a8cpast EOF).x86_64/libaw_server.soexists — so the corrupt arm7 file passed silently and was uploaded as the artifact.mergeDebugNativeLibstries tollvm-stripthe corrupt arm7.so, and fails with:2.
age: no identity matched any of the recipients(not addressed here)The
KEY_FASTLANE_APIsigning secret no longer matches the encrypted.agefile. This is a separate signing-infrastructure issue requiring secret rotation — not addressed in this PR.Fixes
build.yml— Comprehensive jniLibs validity checkReplaces the x86_64-existence-only check with a Python script that validates all 4 ABIs for:
\x7fELF)This catches corruption at build-rust time with a clear diagnostic instead of a cryptic
INSTALL_FAILED_NO_MATCHING_ABISdeep in the E2E logs.mobile/build.gradle— Exclude legacy ABIs from debug buildsAdds
abiFilters 'arm64-v8a', 'x86_64'to thedebugbuild type.x86_64. It never needs armeabi-v7a or x86 libs..sofrom ever blocking the E2E install, even if the CI check is bypassed in future.abiFilterson release).Testing
The ELF validity check is straightforward Python using only stdlib (
os,struct). The abiFilters change follows the standard Android Gradle Plugin pattern for per-variant ABI filtering.The
agesigning failure means the fullbuild-aab/build-apkjobs will still fail until the signing secret is rotated, but thetest-e2ejob should pass once the jniLibs cache is populated for the v4 backend (or after one successful fresh build where arm7 isn't corrupted).