Package the Rust mobile core and generated Kotlin API for Android (#92)
Dependency security audit / rustsec (push) Failing after 24s
Dependency security audit / rustsec (push) Failing after 24s
This commit is contained in:
1 parent
e079b82b15
commit
fcb36a0684
42 files changed
+1586
-172
No files matched your search
+95
-15
@@ -1,13 +1,13 @@
|
||||
# Native Android build
|
||||
|
||||
Architecture and issue ownership: [ADR](../docs/android/architecture.md) and
|
||||
[parity matrix](../docs/android/parity.md). This scaffold implements #89. It is
|
||||
an installable native Compose foundation; onboarding, secure storage and native
|
||||
runtime packaging are implemented by their milestone issues.
|
||||
[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.
|
||||
|
||||
## Reproducible setup
|
||||
|
||||
Use a stable workspace Rust toolchain satisfying Cargo.toml, a JDK 25 installation
|
||||
Use Rust 1.97.1 (pinned in rust-toolchain.toml), cargo-ndk 4.1.2, a JDK 25 installation
|
||||
(Android Studio's JetBrains Runtime 25 is suitable), Android SDK Platform 37,
|
||||
build-tools 36.0.0 and NDK 30.0.16248370. The app emits JVM 17-compatible code
|
||||
and supports minSdk 26. Set JAVA_HOME and ANDROID_HOME/ANDROID_SDK_ROOT or create
|
||||
@@ -19,14 +19,14 @@ The committed Gradle 9.8.1 wrapper verifies the distribution SHA-256. Its JAR
|
||||
SHA-256 is 3b8a25775a69158b5ad2b1d17a80a88dc7b40a352a73eb27d06c612a7ce68e98,
|
||||
verified against the official Gradle version metadata. AGP 9.4.1 includes Kotlin
|
||||
support; the Kotlin JVM/Compose plugin pin upgrades its classpath consistently
|
||||
to 2.4.20. Do not additionally apply kotlin-android with built-in Kotlin enabled.
|
||||
to 2.4.21. Do not additionally apply kotlin-android with built-in Kotlin enabled.
|
||||
The version catalog is the single source for Android dependency/SDK/NDK pins.
|
||||
|
||||
From this directory:
|
||||
|
||||
```sh
|
||||
./gradlew :app:assembleDebug :app:assembleRelease :app:testDebugUnitTest :app:lintDebug ktlintCheck
|
||||
./gradlew :app:connectedDebugAndroidTest
|
||||
./gradlew auditNativePackages :core:testDebugUnitTest :app:testDebugUnitTest :app:lintDebug :app:lintRelease ktlintCheck
|
||||
./gradlew :app:connectedDebugAndroidTest :rust-bridge:connectedDebugAndroidTest
|
||||
```
|
||||
|
||||
Repeat the first command to verify configuration-cache reuse. Dependency
|
||||
@@ -50,12 +50,11 @@ source/import boundaries and subprocess paths before every Android build.
|
||||
These static checks accompany code review; they do not prove arbitrary Kotlin
|
||||
code cannot conceal a domain implementation.
|
||||
|
||||
Debug-only Loading/Empty/Ready/Locked/Error fixtures are generated from the
|
||||
storage-owned MobileShell::fixture by the Rust build tool. Debug controls select
|
||||
the fixture and native tabs; these controls and generated fake states are absent
|
||||
from release. Release displays the scaffold state and does not pretend to have
|
||||
opened/decrypted a repository. Future #92 replaces scaffold wiring with generated
|
||||
UniFFI calls; feature code continues to consume the facade.
|
||||
Debug controls invoke the storage-owned MobileShell::fixture through the native
|
||||
API for Loading/Empty/Ready/Locked/Error. A build-generated fixture inventory is
|
||||
used only as the test oracle. Release calls the same Rust Empty fixture without
|
||||
debug controls; repository initialization is owned by #90. Feature code consumes
|
||||
only the facade, including typed native recovery states.
|
||||
|
||||
## Identity, signing and update
|
||||
|
||||
@@ -64,8 +63,8 @@ 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 declared for later Rust packaging; the scaffold
|
||||
ships no native secret runtime or development network endpoint.
|
||||
and the selected three 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
|
||||
exclusions and cleartext denial are explicit. Feature-specific services/permissions
|
||||
@@ -87,3 +86,84 @@ app-data package as an update. After closing an issue, clean its project build
|
||||
outputs and Cargo target; preserve the emulator and shared SDK/toolchain caches.
|
||||
AGP's leaveApksInstalledAfterRun option is enabled so instrumented checks do not
|
||||
uninstall the app after running. Keep it enabled for this persistent test device.
|
||||
|
||||
## Rust packaging and integrity
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
The workspace's UniFFI 0.32.0 CLI generates Android-mode immutable Kotlin
|
||||
records from the arm64 library, using the global defaults in uniffi.toml.
|
||||
A version-checked preparation step redacts record toString, typed/internal
|
||||
exception diagnostics and unexpected callback stack traces, mechanically renames
|
||||
Rust error message fields to rustMessage in Kotlin,
|
||||
and translates every integrity guard to a typed failure. Checksums remain enabled
|
||||
and retain every native function and expected value. UniFFI checksums are u16:
|
||||
JNA's generated Int return is normalized to its lower 16 bits before comparison;
|
||||
AArch64 may leave unrelated upper bits in the return register. Contract versions
|
||||
are compared at their full width. A failed initializer remains failed until an
|
||||
app restart/update, rather than reporting a different error on each retry.
|
||||
|
||||
NativeRuntime performs OS library/ABI preflight, loads the library and initializes
|
||||
UniFFI before entering Rust. Missing/unsupported libraries, broken APKs, contract
|
||||
or checksum mismatch, and load failures yield typed, non-secret recovery states.
|
||||
It rejects main-thread calls. The core facade schedules blocking native calls on
|
||||
Dispatchers.IO; cancellation propagates and a superseded result cannot overwrite
|
||||
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
|
||||
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
|
||||
#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
|
||||
points. AGP strips release libraries and exports FULL native symbols to
|
||||
app/build/outputs/native-debug-symbols/release/native-debug-symbols.zip; retain
|
||||
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
|
||||
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.
|
||||
|
||||
Validation recorded on 2026-10-08 for #92:
|
||||
|
||||
| Environment | Verified |
|
||||
| --- | --- |
|
||||
| API 37 arm64 emulator, 4 KB | Generated API fixture/error/redaction tests; 25 UI states; release APK and AAB split launch; update-state preservation |
|
||||
| API 37 arm64 emulator, 16 KB, compatibility disabled | Same native/UI tests, release APK/AAB launch and update-state preservation |
|
||||
| Fresh source export | Three ABI debug builds with pinned tools, locked dependencies and strict artifact verification |
|
||||
| All three ABI release outputs | ELF alignment and identical UniFFI metadata; stripped APK libraries and separate FULL Rust symbols; nine bundletool ABI split APKs audited |
|
||||
| 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.
|
||||
Reference in new issue
Block a user