Establish native Linux ARM64 Android CI and verification gates (#94)
Android ARM verification / rust (push) Successful in 50m6s
Android ARM verification / android (push) Successful in 1h33m57s
Dependency security audit / rustsec (push) Successful in 21s

This commit is contained in:
hugo committed 2026-10-09 05:02:59 +02:00
1 parent 1abd75282e
commit 66b1d4a4c4
29 files changed
+1882 -56

No files matched your search

+18 -17
View File
@@ -1,9 +1,11 @@
# Native Android build
Architecture and issue ownership: [ADR](../docs/android/architecture.md) and
[parity matrix](../docs/android/parity.md). The native foundation implements #89 and #92. Onboarding and secure storage
are implemented by their milestone issues. Both variants now invoke Rust through
the generated UniFFI API.
[parity matrix](../docs/android/parity.md). The native foundation implements #89–#94, including private storage, Keystore
callbacks and build gates. Both variants invoke Rust through the generated
UniFFI API. Onboarding and the feature screens remain owned by their milestone
issues. The full [CI and emulator workflow](../docs/android/ci.md) documents the
ARM-only runner/toolchain policy and artifact audits.
## Reproducible setup
@@ -63,10 +65,11 @@ Version code 1 and name 1.0.1 are deterministic initial metadata. Debug signing
uses the standard local debug key. Release has no configured signing key and
produces an unsigned APK; #112 configures protected upload signing. Do not add
signing secrets or store passwords to the repository. Native symbol level FULL
and the selected three ABIs are packaged. Native runtime packaging is described
and the selected two ARM ABIs are packaged. Native runtime packaging is described
below; no development network endpoint is included.
The manifest has only the exported launcher activity. Backup/cloud/device-transfer
The manifest exports the launcher and the reviewed AndroidX profile receiver
protected by the privileged DUMP permission. Backup/cloud/device-transfer
exclusions and cleartext denial are explicit. Feature-specific services/permissions
are added with their owning issues. Resources use values directories for future
localization; vector/adaptive/monochrome icons and R8 consumer rules are present.
@@ -93,14 +96,14 @@ Install the pinned build tools once:
```sh
rustup toolchain install 1.97.1 --profile minimal
rustup target add --toolchain 1.97.1 aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
rustup target add --toolchain 1.97.1 aarch64-linux-android armv7-linux-androideabi
cargo install cargo-ndk --version 4.1.2 --locked
```
Gradle checks the actual Cargo/cargo-ndk/NDK revisions before building. Each
variant compiles crates/android with cargo-ndk into its own generated jniLibs,
then verifies ELF architecture, 16 KB LOAD alignment, and byte-identical UniFFI
metadata across all three ABIs. A packaging step selects only the Android library
metadata across both ARM ABIs. A packaging step selects only the Android library
and rejects accidental inclusion of the dependency Apple cdylib. The thin Android crate re-exports the existing
UniFFI component; it duplicates no storage rules or converters. #91 owns the
platform-neutral contract extraction. No handwritten JNI or unsafe Rust is added.
@@ -126,15 +129,14 @@ newer state. Blocking fixture calls finish in Rust when a coroutine is cancelled
no thread is forcibly stopped. Long storage operations must use their Rust-owned
cancellation methods when their feature is integrated.
No Rust-to-Kotlin platform callbacks are installed by #92. When #93 adds them,
use generated UniFFI/JNA callback adapters only: JNA attaches a calling native
#93 installs Rust-to-Kotlin Keystore callbacks through generated UniFFI/JNA
adapters: JNA attaches a calling native
thread to the JVM; retain the callback for the full Rust owner lifetime, release
it after unregistering, never assume the callback runs on the Android main thread,
and marshal UI/Activity work onto Main. Keep storage decisions in Rust and avoid
blocking the main thread or calling back into a held Rust lock.
JNA 5.19.1 is packaged as its Android AAR, filtered to arm64-v8a, armeabi-v7a and
x86_64. The release set may only be narrowed by changing the support decision in
JNA 5.19.1 is packaged as its Android AAR, filtered to arm64-v8a, armeabi-v7a only. The release set is arm64-v8a and armeabi-v7a under the user amendment to
#88, not by silently omitting an ABI. All source/lock/config files are task inputs;
outputs live only under build/. Debug and release compile independently, and
unchanged builds reuse their outputs. R8 keeps generated/JNA reflection entry
@@ -144,17 +146,16 @@ that file and outputs/mapping/release/mapping.txt with the matching release.
AAB base/lib entries let Play produce per-ABI splits.
The auditNativePackages gate inspects every native dependency in both APKs and
the AAB, checks all three ABI directories and both Rust/JNA libraries, and rejects
the AAB, checks both ARM ABI directories and both Rust/JNA libraries, and rejects
ELF segments below 16 KB alignment or APK native entries that are compressed or
not ZIP-aligned to 16 KB. Follow the official [16 KB runtime validation](https://developer.android.com/guide/practices/page-sizes):
use an actual 16 KB system image, check adb shell getconf PAGE_SIZE is 16384,
disable page-size compatibility mode, and run both instrumented test suites.
An ELF/ZIP audit is not a substitute for that runtime test. Milestone validation
is emulator-only per the user instruction of 2026-10-08; physical devices are
outside the test scope for every issue. Keep x86_64 emulator evidence separate
from the Apple Silicon arm64 emulator run.
outside the test scope for every issue. The user removed x86_64 Android from the current support scope on 2026-10-08.
Validation recorded on 2026-10-08 for #92:
Historical validation recorded on 2026-10-08 for #92 (before the ARM-only scope update):
| Environment | Verified |
| --- | --- |
@@ -165,5 +166,5 @@ Validation recorded on 2026-10-08 for #92:
| Physical devices | Excluded from milestone testing by user instruction |
| x86_64 runtime | System image installed; QEMU2 rejects x86_64 on this aarch64 Mac, including with acceleration disabled |
The outstanding x86_64 emulator check needs a compatible host; cross-compilation
and static package checks do not establish x86_64 runtime coverage.
The historical x86_64 runtime gap is no longer a required check under the current
ARM-only support decision.