From d9020a8bb2e2ce6dc0a7c623a5f2715732b32e49 Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 3 Jul 2026 14:07:53 +0000 Subject: [PATCH 1/2] fix(ci): validate all jniLibs ABIs and exclude armeabi-v7a from debug builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #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. --- .github/workflows/build.yml | 34 ++++++++++++++++++++++++++++++++-- mobile/build.gradle | 7 +++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e1fb22ca..7d945b38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,9 +81,39 @@ jobs: run: | make aw-server-rust - - name: Check that jniLibs present + - name: Check that jniLibs present and valid ELF run: | - test -e mobile/src/main/jniLibs/x86_64/libaw_server.so + python3 - <<'EOF' + import os, struct, sys + abis = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'] + ok = True + for abi in abis: + path = f'mobile/src/main/jniLibs/{abi}/libaw_server.so' + if not os.path.exists(path): + print(f'MISSING {path}', file=sys.stderr) + ok = False + continue + fsize = os.path.getsize(path) + with open(path, 'rb') as f: + header = f.read(64) + 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 e_class == 1: + e_shoff = struct.unpack('= fsize: + print(f'CORRUPT {path}: section header past EOF ' + f'(e_shoff=0x{e_shoff:x}, size={fsize})', file=sys.stderr) + ok = False + continue + print(f'OK {abi}/libaw_server.so ({fsize:,} bytes)') + if not ok: + sys.exit(1) + EOF - name: Upload jniLibs artifact uses: actions/upload-artifact@v4 diff --git a/mobile/build.gradle b/mobile/build.gradle index 619fa488..d3d82822 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -38,6 +38,13 @@ android { applicationIdSuffix ".debug" jniDebuggable true renderscriptDebuggable true + // Limit debug APK to modern ABIs used by CI emulators and physical devices. + // armeabi-v7a debug Rust binaries are very large and can corrupt the CI + // artifact when disk space is tight; they aren't needed for x86_64 emulator + // runs or any arm64-v8a device released since ~2014. + ndk { + abiFilters 'arm64-v8a', 'x86_64' + } } } compileOptions { From 06caa04d3d78befbc826b8d9e9c4681139357116 Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 3 Jul 2026 14:46:06 +0000 Subject: [PATCH 2/2] fix(ci): reuse jniLibs ELF validation --- .github/workflows/build.yml | 40 +++--------------------- scripts/check-jnilibs.py | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 35 deletions(-) create mode 100755 scripts/check-jnilibs.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d945b38..c50ff6c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,37 +83,7 @@ jobs: - name: Check that jniLibs present and valid ELF run: | - python3 - <<'EOF' - import os, struct, sys - abis = ['arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'] - ok = True - for abi in abis: - path = f'mobile/src/main/jniLibs/{abi}/libaw_server.so' - if not os.path.exists(path): - print(f'MISSING {path}', file=sys.stderr) - ok = False - continue - fsize = os.path.getsize(path) - with open(path, 'rb') as f: - header = f.read(64) - 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 e_class == 1: - e_shoff = struct.unpack('= fsize: - print(f'CORRUPT {path}: section header past EOF ' - f'(e_shoff=0x{e_shoff:x}, size={fsize})', file=sys.stderr) - ok = False - continue - print(f'OK {abi}/libaw_server.so ({fsize:,} bytes)') - if not ok: - sys.exit(1) - EOF + python3 scripts/check-jnilibs.py - name: Upload jniLibs artifact uses: actions/upload-artifact@v4 @@ -235,9 +205,9 @@ jobs: key: ${{ env.cache-name }}-release-${{ env.RELEASE }}-ndk-${{ env.NDK_VERSION }}-${{ hashFiles('.git/modules/aw-server-rust/HEAD') }} fail-on-cache-miss: false - - name: Check that jniLibs present + - name: Check that jniLibs present and valid ELF run: | - test -e mobile/src/main/jniLibs/x86_64/libaw_server.so + python3 scripts/check-jnilibs.py - name: Verify versionName matches tag if: startsWith(github.ref, 'refs/tags/v') # only on runs triggered from tag @@ -319,9 +289,9 @@ jobs: name: jniLibs path: mobile/src/main/jniLibs/ - - name: Check that jniLibs present + - name: Check that jniLibs present and valid ELF run: | - test -e mobile/src/main/jniLibs/x86_64/libaw_server.so + python3 scripts/check-jnilibs.py # Test - name: Test diff --git a/scripts/check-jnilibs.py b/scripts/check-jnilibs.py new file mode 100755 index 00000000..3f349ddf --- /dev/null +++ b/scripts/check-jnilibs.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import os +import struct +import sys + + +ABIS = ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"] +LIB_NAME = "libaw_server.so" + + +def check_lib(abi): + path = os.path.join("mobile", "src", "main", "jniLibs", abi, LIB_NAME) + if not os.path.exists(path): + print(f"MISSING {path}", file=sys.stderr) + return False + + fsize = os.path.getsize(path) + with open(path, "rb") as f: + header = f.read(64) + + if header[:4] != b"\x7fELF": + print(f"NOT_ELF {path}", file=sys.stderr) + return False + + if len(header) < 64: + print( + f"CORRUPT {path}: file too small for ELF header " + f"({len(header)} bytes)", + file=sys.stderr, + ) + return False + + e_class = header[4] # 1=32-bit, 2=64-bit + if e_class == 1: + e_shoff = struct.unpack("= fsize: + print( + f"CORRUPT {path}: section header past EOF " + f"(e_shoff=0x{e_shoff:x}, size={fsize})", + file=sys.stderr, + ) + return False + + print(f"OK {abi}/{LIB_NAME} ({fsize:,} bytes)") + return True + + +def main(): + ok = True + for abi in ABIS: + ok = check_lib(abi) and ok + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main())