Establish Android CI, static analysis, tests, and reproducible native build gates #94

Closed
opened 2026-08-17 19:10:21 +00:00 by hugo · 1 comment
Owner

Goal

Make the Android application and every bundled Rust ABI continuously buildable, reviewable, and testable before feature work expands.

Scope

  • Add CI jobs for Gradle dependency verification, Kotlin compilation, unit tests, Android Lint, formatting/static analysis, Compose tests, and release AAB assembly.
  • Build and test the Rust Android bridge and selected ABIs with the pinned NDK; retain the repository-required Rust formatting, warnings, Clippy, and workspace tests.
  • Run ARM emulator instrumentation for the supported minimum/current SDK matrix where ARM system images are available; document unavailable emulator/API combinations and security/camera limitations without adding physical-device testing.
  • Verify UniFFI binding freshness/checksums, ABI contents, release stripping/debug-symbol archive, 16 KB page alignment, manifest permissions, backup rules, exported components, and absence of forbidden libraries/process execution.
  • Add deterministic fakes/fixtures for storage pages, clocks, errors, cancellation, biometric results, and process recreation. Never place production-like secrets in CI artifacts.
  • Add dependency/license/SBOM review and fail on known-disallowed dependency classes from #88.
  • Define baseline performance measurements for cold start/native load, large store browsing/search, TOTP warm load, and editor interactions; add Macrobenchmark/Baseline Profiles only after representative screens exist.
  • Keep signing and Play credentials out of general CI; distribution uses a separately protected release workflow.

Acceptance criteria

  • A clean CI run produces tested debug APKs and an unsigned/release-test AAB for every selected ABI.
  • Android and Rust warnings are treated as errors under documented, non-flaky gates.
  • Native packaging, page-size, binding mismatch, backup/exported-component, and architecture-boundary audits fail deterministically.
  • Test reports contain no decrypted entry, OTP URI, key armor, token, or passphrase.
  • Required local commands and the ARM emulator validation matrix and its limits are documented.

Depends on #88, the Gradle scaffold, and Rust/UniFFI packaging issues. Part of #87.

Android support update (2026-10-08)

User-directed scope: Android release and CI targets are arm64-v8a and armeabi-v7a. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage.

CI infrastructure decision (2026-10-08): use the existing Gitea Linux ARM64 runner only. No new macOS runner or other hardware is in scope. Validate the available ARM emulators locally; document runner limitations explicitly.

## Goal Make the Android application and every bundled Rust ABI continuously buildable, reviewable, and testable before feature work expands. ## Scope - Add CI jobs for Gradle dependency verification, Kotlin compilation, unit tests, Android Lint, formatting/static analysis, Compose tests, and release AAB assembly. - Build and test the Rust Android bridge and selected ABIs with the pinned NDK; retain the repository-required Rust formatting, warnings, Clippy, and workspace tests. - Run ARM emulator instrumentation for the supported minimum/current SDK matrix where ARM system images are available; document unavailable emulator/API combinations and security/camera limitations without adding physical-device testing. - Verify UniFFI binding freshness/checksums, ABI contents, release stripping/debug-symbol archive, 16 KB page alignment, manifest permissions, backup rules, exported components, and absence of forbidden libraries/process execution. - Add deterministic fakes/fixtures for storage pages, clocks, errors, cancellation, biometric results, and process recreation. Never place production-like secrets in CI artifacts. - Add dependency/license/SBOM review and fail on known-disallowed dependency classes from #88. - Define baseline performance measurements for cold start/native load, large store browsing/search, TOTP warm load, and editor interactions; add Macrobenchmark/Baseline Profiles only after representative screens exist. - Keep signing and Play credentials out of general CI; distribution uses a separately protected release workflow. ## Acceptance criteria - A clean CI run produces tested debug APKs and an unsigned/release-test AAB for every selected ABI. - Android and Rust warnings are treated as errors under documented, non-flaky gates. - Native packaging, page-size, binding mismatch, backup/exported-component, and architecture-boundary audits fail deterministically. - Test reports contain no decrypted entry, OTP URI, key armor, token, or passphrase. - Required local commands and the ARM emulator validation matrix and its limits are documented. Depends on #88, the Gradle scaffold, and Rust/UniFFI packaging issues. Part of #87. ## Android support update (2026-10-08) User-directed scope: Android release and CI targets are **arm64-v8a** and **armeabi-v7a**. x86_64 Android is excluded for now. Validation for every Android milestone issue uses emulator devices only; physical-device testing and physical-device checklists are excluded. Record the limits of available ARM emulator/API images without claiming physical-device coverage. CI infrastructure decision (2026-10-08): use the existing Gitea Linux ARM64 runner only. No new macOS runner or other hardware is in scope. Validate the available ARM emulators locally; document runner limitations explicitly.
hugo added this to the 06 - Implement an Android version of the software milestone 2026-08-17 19:10:21 +00:00
hugo added the enhancement label 2026-08-17 19:10:21 +00:00
Author
Owner

Implemented #94 in one commit: 66b1d4a, pushed to main. Verified Gitea run: 593 (Rust job 986 and Android job 987 both successful, including artifact upload and cleanup).

The Android workflow builds on the existing Linux ARM64 runner with pinned Rust 1.97.1, cargo-ndk 4.1.2, a checksummed community NDK r30, native ARM64 AAPT2 and the Java SDK manager. It produces arm64-v8a and armeabi-v7a packages without CPU emulation or an additional runner. An argument adapter preserves resource optimization with the pinned AAPT2.

The gates cover all five Kotlin modules: warnings as errors, JVM tests, KtLint, Android Lint, and instrumented test APK assembly. Native checks cover both ARM ABIs, UniFFI contracts and binding freshness, ELF and APK 16 KB alignment, library allowlists, stripped Rust/JNA libraries and matching build-ID symbols. Release APK and AAB assembly retain optimization and FULL native symbols. Rust audits verify packaged permissions, exports, backup/transfer exclusions and the actual packaged backup resource references and decoded XML. Dependency locks, artifact checksums, license policy and CycloneDX inventory cover both Android artifacts and the target-specific Rust dependency graph. Required app/core test reports must be present and passing; published summaries contain only hashed test IDs and status. No distribution credentials are required.

The job runs Cargo with one worker and compiles host tools before Gradle. CI uses separate single-use Gradle JVMs and in-process Kotlin compilation with a 1 GB heap; it executes every original aggregate verification gate after the individual phases. Diagnostic output reports aggregate disk/memory limits and counters after each JVM phase, including failure. Seven orchestration self-tests cover phase completeness, argument preservation, toolchain checksums, packaged backup references, symbol/checksum output and constrained cleanup. CI omits compiler debug metadata only for host/debug builds and disables incremental compilation; release debug information remains explicit and audited.

A timed-out earlier job left two unused Docker volumes, accounting for 11.9 GB of lost runner space. Removing only those volumes restored approximately 21 GB free. Subsequent failed-job cleanup and normal Docker teardown preserved that free-space baseline. Both jobs now run checkout-only cleanup after success or failure, with symlink/path validation; they do not prune shared caches or other projects. Disk preflight requires 16 GiB for the host job and 20 GiB for Android. Hard termination can still prevent cleanup from executing.

Validation: all four required Rust gates passed locally, including 449 workspace tests; native Linux ARM64 also passed the workspace gates. The full Android verification pipeline, package/manifest audits and artifact inventory passed locally on macOS and Linux ARM64. A fresh Linux build with a 4 GB container limit and no swap is recorded in the local verification log: all eleven Gradle phases and final package/manifest/SBOM/report audits completed with no new OOM kills. The measured earlier complete generated-files/toolchain footprint was 17.96 GiB, plus the runner's 738 MB prerequisite installation.

Independent verification of the downloaded Gitea artifact #347 passed: all 14 SHA-256 entries, 1,339 reviewed SBOM components, both sanitized JVM reports, both ARM native ABIs, debug/release APK and AAB packaging, matching FULL release symbols and decoded backup manifests. The downloaded CI debug app and its exact Rust/JNA native bytes passed ten JNI and two Compose tests on each of API 26/4 KB, API 37/4 KB and strict API 37/16 KB. Test APKs used the same source and a local debug identity to preserve existing emulator installations. Observed ADB controls, hierarchy dumps and screenshots confirmed Ready state, icons and Preferences navigation. The actual published AAB passed bundletool validation; its locally debug-signed optimized universal APK passed the Rust native audit, installed over existing data and launched on API 26 and strict API 37/16 KB. Release Preferences navigation was also validated on strict 16 KB. The repeat workflow triggered by the main push is separate; run 593 proves the exact delivered commit.

ARM emulator coverage uses persistent API 26/4 KB, API 37/4 KB and API 37/strict 16 KB devices. Each previously passed ten JNI and two Compose tests with the final smaller debug profile. ADB input and screenshots verified Ready fixture presentation, icons and Preferences navigation. Optimized Linux release APKs and a bundletool-produced universal APK installed over existing data and rendered correctly on API 26 and strict 16 KB. Launcher validation confirmed the iPhone-derived vault branding. No emulator or app data was reset.

Five process-cold measurements are documented with precise definitions in docs/android/ci.md: median startup/native-load times were 161/25.4 ms on API 26, 896/42.7 ms on API 37/4 KB and 730/62.0 ms on API 37/16 KB. These emulator observations are measurements, not flaky latency gates; #105 owns later Macrobenchmark work.

Acceptance-criteria review: complete; all acceptance criteria are implemented and verified under the approved ARM/emulator scope. The existing Linux setup has no native ARM Android Emulator package, so runtime verification uses the local ARM emulator matrix. ARMv7 is compiled and audited; the available 64-bit-only emulator images cannot execute it. Physical devices and x86 targets are excluded by the approved milestone scope. RUSTSEC-2026-0285 was resolved by updating rustls; the existing documented RSA exception remains unchanged.

Implemented #94 in one commit: [66b1d4a](https://git.rfc1437.de/hugo/IronStorage/commit/66b1d4a4c4155eeaed4a0541ecd2f9025da18d3d), pushed to main. Verified Gitea run: [593](https://git.rfc1437.de/hugo/IronStorage/actions/runs/593) (Rust job 986 and Android job 987 both successful, including artifact upload and cleanup). The Android workflow builds on the existing Linux ARM64 runner with pinned Rust 1.97.1, cargo-ndk 4.1.2, a checksummed community NDK r30, native ARM64 AAPT2 and the Java SDK manager. It produces arm64-v8a and armeabi-v7a packages without CPU emulation or an additional runner. An argument adapter preserves resource optimization with the pinned AAPT2. The gates cover all five Kotlin modules: warnings as errors, JVM tests, KtLint, Android Lint, and instrumented test APK assembly. Native checks cover both ARM ABIs, UniFFI contracts and binding freshness, ELF and APK 16 KB alignment, library allowlists, stripped Rust/JNA libraries and matching build-ID symbols. Release APK and AAB assembly retain optimization and FULL native symbols. Rust audits verify packaged permissions, exports, backup/transfer exclusions and the actual packaged backup resource references and decoded XML. Dependency locks, artifact checksums, license policy and CycloneDX inventory cover both Android artifacts and the target-specific Rust dependency graph. Required app/core test reports must be present and passing; published summaries contain only hashed test IDs and status. No distribution credentials are required. The job runs Cargo with one worker and compiles host tools before Gradle. CI uses separate single-use Gradle JVMs and in-process Kotlin compilation with a 1 GB heap; it executes every original aggregate verification gate after the individual phases. Diagnostic output reports aggregate disk/memory limits and counters after each JVM phase, including failure. Seven orchestration self-tests cover phase completeness, argument preservation, toolchain checksums, packaged backup references, symbol/checksum output and constrained cleanup. CI omits compiler debug metadata only for host/debug builds and disables incremental compilation; release debug information remains explicit and audited. A timed-out earlier job left two unused Docker volumes, accounting for 11.9 GB of lost runner space. Removing only those volumes restored approximately 21 GB free. Subsequent failed-job cleanup and normal Docker teardown preserved that free-space baseline. Both jobs now run checkout-only cleanup after success or failure, with symlink/path validation; they do not prune shared caches or other projects. Disk preflight requires 16 GiB for the host job and 20 GiB for Android. Hard termination can still prevent cleanup from executing. Validation: all four required Rust gates passed locally, including 449 workspace tests; native Linux ARM64 also passed the workspace gates. The full Android verification pipeline, package/manifest audits and artifact inventory passed locally on macOS and Linux ARM64. A fresh Linux build with a 4 GB container limit and no swap is recorded in the local verification log: all eleven Gradle phases and final package/manifest/SBOM/report audits completed with no new OOM kills. The measured earlier complete generated-files/toolchain footprint was 17.96 GiB, plus the runner's 738 MB prerequisite installation. Independent verification of the downloaded Gitea artifact #347 passed: all 14 SHA-256 entries, 1,339 reviewed SBOM components, both sanitized JVM reports, both ARM native ABIs, debug/release APK and AAB packaging, matching FULL release symbols and decoded backup manifests. The downloaded CI debug app and its exact Rust/JNA native bytes passed ten JNI and two Compose tests on each of API 26/4 KB, API 37/4 KB and strict API 37/16 KB. Test APKs used the same source and a local debug identity to preserve existing emulator installations. Observed ADB controls, hierarchy dumps and screenshots confirmed Ready state, icons and Preferences navigation. The actual published AAB passed bundletool validation; its locally debug-signed optimized universal APK passed the Rust native audit, installed over existing data and launched on API 26 and strict API 37/16 KB. Release Preferences navigation was also validated on strict 16 KB. The repeat workflow triggered by the main push is separate; run 593 proves the exact delivered commit. ARM emulator coverage uses persistent API 26/4 KB, API 37/4 KB and API 37/strict 16 KB devices. Each previously passed ten JNI and two Compose tests with the final smaller debug profile. ADB input and screenshots verified Ready fixture presentation, icons and Preferences navigation. Optimized Linux release APKs and a bundletool-produced universal APK installed over existing data and rendered correctly on API 26 and strict 16 KB. Launcher validation confirmed the iPhone-derived vault branding. No emulator or app data was reset. Five process-cold measurements are documented with precise definitions in docs/android/ci.md: median startup/native-load times were 161/25.4 ms on API 26, 896/42.7 ms on API 37/4 KB and 730/62.0 ms on API 37/16 KB. These emulator observations are measurements, not flaky latency gates; #105 owns later Macrobenchmark work. Acceptance-criteria review: complete; all acceptance criteria are implemented and verified under the approved ARM/emulator scope. The existing Linux setup has no native ARM Android Emulator package, so runtime verification uses the local ARM emulator matrix. ARMv7 is compiled and audited; the available 64-bit-only emulator images cannot execute it. Physical devices and x86 targets are excluded by the approved milestone scope. RUSTSEC-2026-0285 was resolved by updating rustls; the existing documented RSA exception remains unchanged.
hugo closed this issue 2026-10-09 06:51:39 +00:00
Sign in to join this conversation.