Part 27 — Rust-Driven Android Native Build & Packaging Automation Architecture
Reusable P2P Communication Platform
Status: Architecture specification
Part: 27 of 27 additional production-hardening parts
Primary language: Rust
Android build hosts: Rust xtask + Cargo + Android NDK + Gradle/AGP as packaging/signing backend
Primary goals: one-command reproducible Android builds, automatic Rust .so generation for all required ABIs, deterministic staging into Android packaging, Dioxus integration, version consistency, signing safety, Play Store AAB/APK production, CI automation, symbols/SBOM/checksums, failure-proof release gates, and elimination of manual jniLibs workflows
1. Purpose
The previous Android architecture assumes a native Rust communication/media core.
A weak build workflow often looks like:
manually run cargo-ndk
copy libsomething.so into jniLibs
run Gradle
hope versions match
This is acceptable during prototyping but not for production.
Manual native-library staging creates risks:
wrong ABI
stale .so
debug library in release build
missing architecture
version mismatch
forgotten rebuild
wrong NDK
wrong Cargo features
unsigned or incorrectly signed artifact
non-reproducible CI
The production architecture must turn Android building into a deterministic pipeline.
The core rule is:
Rust
xtaskowns build intent and orchestration; Cargo builds native Rust, the Android NDK supplies target toolchains, and Gradle/AGP packages/signs Android artifacts.
Gradle is still required because Android's application packaging/signing ecosystem is Gradle/AGP-centric, but Gradle should not become the source of truth for Rust build policy.
2. Desired Developer Experience
Development:
cargo xtask android run
Release candidate:
cargo xtask android build --release
Play Store:
cargo xtask android bundle --release
Signed APK:
cargo xtask android apk --release
Everything required should happen automatically:
toolchain validation
target setup
Rust build
native library collection
JNI staging
Gradle packaging
artifact validation
3. Architectural Position
Developer / CI
│
▼
cargo xtask
│
├── validate environment
├── select Android ABIs
├── invoke Cargo/NDK
├── stage .so artifacts
├── generate build metadata
├── invoke Gradle
├── verify APK/AAB
└── collect release artifacts
│
▼
Android Gradle Project
│
▼
APK / AAB
4. Responsibilities
xtask owns
build profile
Rust feature selection
target ABI matrix
toolchain discovery
native build invocation
artifact staging
build metadata
release checks
Gradle invocation
artifact collection
verification
Cargo owns
Rust compilation
dependency resolution
Rust features
Cargo.lock
Android NDK owns
Android C ABI toolchain
linker
sysroot
platform native libraries
Gradle / AGP owns
Android resources
AndroidManifest
DEX/Kotlin/Java
APK/AAB packaging
signing integration
Play Store bundle format
5. Why Not Replace Gradle Entirely
Attempting to replace Gradle for official Android packaging would add enormous maintenance burden around:
AAB
manifest merging
resources
R8
APK signing
bundle metadata
Play Store conventions
Android SDK changes
That offers little architectural benefit.
The Rust-centric solution is:
Rust controls Gradle
rather than:
Rust replaces Android packaging infrastructure
6. Workspace Layout
Recommended:
/
├── Cargo.toml
├── Cargo.lock
├── rust-toolchain.toml
├── xtask/
│ ├── Cargo.toml
│ └── src/
│ ├── main.rs
│ ├── android/
│ │ ├── mod.rs
│ │ ├── build.rs
│ │ ├── toolchain.rs
│ │ ├── targets.rs
│ │ ├── stage.rs
│ │ ├── gradle.rs
│ │ ├── signing.rs
│ │ ├── metadata.rs
│ │ ├── verify.rs
│ │ ├── symbols.rs
│ │ └── release.rs
│ └── util/
├── apps/
│ └── android/
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ ├── gradle.properties
│ └── app/
│ ├── build.gradle.kts
│ ├── src/main/
│ │ ├── AndroidManifest.xml
│ │ ├── java/ or kotlin/
│ │ ├── res/
│ │ └── jniLibs/
│ └── proguard-rules.pro
└── crates/
└── ...
7. xtask Pattern
Use a workspace-local Rust binary:
cargo xtask ...
xtask is not shipped to users.
It exists for:
build
test
package
release
CI
developer tooling
8. cargo xtask
Root Cargo configuration can expose alias:
[alias]
xtask = "run --package xtask --"
Then:
cargo xtask android build
9. Android Command Tree
Suggested CLI:
cargo xtask android doctor
cargo xtask android build
cargo xtask android run
cargo xtask android test
cargo xtask android apk
cargo xtask android bundle
cargo xtask android symbols
cargo xtask android clean
cargo xtask android release
10. doctor
Before builds, validate:
Java
Android SDK
Android NDK
Gradle wrapper
adb
Rust toolchain
Rust Android targets
signing config if release
11. Fail Early
Bad:
compile for 15 minutes
then discover NDK missing
Good:
doctor detects missing dependency immediately
12. Toolchain Manifest
Pin required versions in one machine-readable file.
Example:
(
android: (
min_sdk: 26,
target_sdk: 36,
compile_sdk: 36,
ndk: "28.0.x",
),
)
Exact versions should follow current product requirements at implementation time.
13. Version Source of Truth
Avoid defining versions independently in:
Cargo
Gradle
CI
docs
release script
Use one source of truth and generate/validate downstream configuration.
14. Build Metadata Crate
Optional:
crates/build-metadata/
contains:
product version
protocol version
ABI version
git commit
build channel
15. App Version
Maintain:
semver-like app version
Android versionName
Android versionCode
through one release metadata path.
16. Version Code
Android requires monotonically increasing integer.
xtask should validate:
versionCode > previous release
in release pipeline where previous metadata available.
17. Supported Android ABIs
Recommended initial release:
arm64-v8a
x86_64
Possible additional:
armeabi-v7a
only if real user/device requirements justify it.
18. Rust Target Mapping
Example:
arm64-v8a
→ aarch64-linux-android
x86_64
→ x86_64-linux-android
armeabi-v7a
→ armv7-linux-androideabi
19. ABI Mapping Type
#![allow(unused)] fn main() { pub enum AndroidAbi { Arm64V8a, X86_64, ArmeabiV7a, } }
20. Central Target Mapping
One Rust function defines:
Android ABI
Rust target
jniLibs directory
NDK linker target
Do not duplicate mappings in shell scripts.
21. Native Library
Example output:
libcomm_android.so
or product-specific name.
22. Library Crate
Android native entry crate:
crates/comm-android-ffi/
Cargo:
[lib]
crate-type = ["cdylib"]
23. One Android Native Entry Library
Prefer one primary JNI/native shared library.
Benefits:
simpler loading
fewer packaging issues
one ABI boundary
Internal Rust crates link statically into it.
24. Multiple .so Libraries
Only when needed for:
third-party native dependency
special codec library
platform separation
Track explicitly.
25. Build Flow
For each ABI:
select target
↓
configure NDK compiler/linker
↓
cargo build
↓
locate resulting .so
↓
strip/copy symbols according to profile
↓
stage into jniLibs/<abi>/
26. No Manual Copy
xtask must own:
jniLibs staging
Developer should never manually move .so.
27. Staging Directory
Use generated directory:
target/android-staging/
Then either:
copy/sync to app/src/main/jniLibs
or configure Gradle to consume the generated directory directly.
28. Prefer Generated Native Directory
Better:
Gradle jniLibs.srcDirs += target/android-staging/jniLibs
This avoids modifying source tree during build.
29. Source Tree Cleanliness
Do not commit compiled:
.so
into Git.
30. Gradle Native Input
Generated:
target/android-staging/jniLibs/arm64-v8a/libcomm_android.so
target/android-staging/jniLibs/x86_64/libcomm_android.so
31. Stale Library Protection
Before native build:
clear staging for selected build profile
After build:
verify every expected ABI exists
32. Build Fingerprint
For each .so, record:
Rust target
Cargo profile
features
commit
digest
33. Native Artifact Manifest
(
profile: "release",
git: "...",
artifacts: [
(
abi: "arm64-v8a",
sha256: "...",
),
],
)
34. Gradle Preflight Check
Gradle packaging should verify expected native manifest exists.
This prevents standalone Gradle release from accidentally packaging stale libraries.
35. Gradle Guard Task
Create task:
verifyRustNativeArtifacts
that fails if:
missing manifest
wrong profile
missing ABI
version mismatch
36. Gradle Dependency
Ensure:
preBuild
depends on:
verifyRustNativeArtifacts
37. Optional Gradle → xtask Trigger
Could make Gradle invoke Rust automatically.
But recommended source of truth is:
xtask → Cargo → Gradle
rather than recursive:
Gradle → xtask → Gradle
38. Avoid Build Cycles
Choose one top-level orchestrator:
xtask
39. Development Escape Hatch
A developer may run Gradle directly for UI-only work.
Then Gradle should:
reuse last valid debug native artifact
or fail with clear message.
40. Automatic Debug Native Build
Option:
./gradlew assembleDebug
can call a small Rust build preparation script/task.
But keep release build strictly through xtask.
41. Cargo-NDK
cargo-ndk can be used as implementation detail if it remains useful and maintained.
Architecture should not depend semantically on it.
42. Direct Cargo + NDK
xtask can alternatively configure:
CC
AR
CARGO_TARGET_*_LINKER
and invoke Cargo directly.
43. Build Tool Abstraction
#![allow(unused)] fn main() { pub trait AndroidNativeBuilder { fn build(&self, request: NativeBuildRequest) -> Result<NativeArtifacts, BuildError>; } }
Implementations:
CargoNdkBuilder
DirectNdkBuilder
44. Default
Use the simplest robust approach.
If cargo-ndk works reliably:
use it
No need to reimplement its functionality just for Rust purity.
45. "Rust-Driven" Meaning
The orchestration logic is Rust.
It can invoke:
cargo
cargo-ndk
gradlew
adb
bundletool
as external tools.
46. No Shell-Script Core
Avoid large:
build_android.sh
release_android.sh
containing business logic.
Tiny wrapper scripts are acceptable.
47. Process Invocation
Use Rust:
#![allow(unused)] fn main() { std::process::Command }
or ergonomic command crate.
48. Command Logging
Print:
tool
arguments sanitized
target
duration
result
Never print signing secrets.
49. Structured Build Errors
#![allow(unused)] fn main() { pub enum AndroidBuildError { MissingSdk, MissingNdk, UnsupportedNdk, MissingRustTarget, CargoFailed, MissingNativeArtifact, GradleFailed, SigningMisconfigured, ArtifactVerificationFailed, } }
50. Build Profiles
Support:
#![allow(unused)] fn main() { pub enum AndroidBuildProfile { Debug, Dev, Profile, Release, } }
51. Debug
fast compile
debug symbols
logging
no optimization or low optimization
52. Dev
Useful compromise:
some optimization
debug assertions
developer diagnostics
53. Profile
Performance testing:
release optimization
symbols retained
profiling hooks
54. Release
full optimization
production features
release signing
strict checks
55. Rust Cargo Profiles
Map Android profiles to:
Cargo profile
Gradle build type
feature set
56. Release Feature Freeze
Explicitly define release features.
Example:
android
dioxus
iroh
files
dtn
calls
android-hw-video
audio-dsp
Do not rely on accidental default feature expansion.
57. Feature Manifest
(
release_features: [
"android",
"iroh",
"calls",
],
)
58. Debug-Only Features
Examples:
network inspector
unsafe developer plugin mode
verbose tracing
must not accidentally ship in release.
59. Release Guard
xtask checks forbidden features.
60. NDK Discovery
Look in:
ANDROID_NDK_HOME
ANDROID_SDK_ROOT/ndk/<version>
according to policy.
61. Do Not Guess Arbitrarily
If multiple NDKs installed:
select pinned version
or fail with clear instructions.
62. SDK Discovery
Validate:
platform
build-tools
platform-tools
required versions.
63. Java Discovery
Gradle/AGP needs compatible JDK.
doctor validates.
64. Rust Target Installation
doctor can report:
rustup target add aarch64-linux-android
Optionally xtask setup installs automatically.
65. Setup Command
cargo xtask android setup
Can:
check SDK
check NDK
install Rust targets
create local generated config
Do not silently install large Android SDK components without explicit user action.
66. Local Config
Machine-specific paths belong in:
local.properties
or environment.
Do not commit them.
67. Generated Gradle Properties
xtask may generate:
target/android-staging/build.properties
with:
native staging path
build commit
feature hash
68. Dioxus Integration
The Android app may be Dioxus-based.
xtask should treat Dioxus build output as part of app build graph.
69. Dioxus CLI
If Dioxus tooling is required:
xtask invokes it
or directly builds appropriate Rust crate.
Keep one top-level command.
70. Rust Native Core vs Dioxus App
Possible architecture:
Dioxus application Rust
+
Android platform shell
+
native communication/media core
Avoid compiling duplicate copies of core into multiple shared libraries.
71. Native Symbol Ownership
Ensure JNI symbols exist only in intended library.
72. JNI Registration
Prefer:
JNI_OnLoad
+
explicit native method registration
or stable generated bindings.
Avoid fragile name-based JNI where possible.
73. Native Library Load
Android shell loads:
System.loadLibrary("comm_android")
This tiny Kotlin line is fine.
74. Load Validation
Expose:
native build ID
ABI version
SDK version
and compare with Android app build metadata at startup.
75. Native/App Version Handshake
Kotlin/Rust bridge:
expected native ABI = X
actual native ABI = Y
Mismatch:
fail clearly
76. Why Version Handshake
Protects against:
stale jniLibs
partial install
development mismatch
77. Build ID
Generate:
git commit
dirty flag
timestamp optional
feature digest
78. Reproducibility and Timestamp
For reproducible builds, avoid embedding current wall-clock timestamp unless explicitly outside reproducible binary.
Use:
SOURCE_DATE_EPOCH
where appropriate.
79. Dirty Tree Policy
Release build should fail if Git tree dirty by default.
Override only explicitly.
80. Release Tag
Release command can require:
version tag
matches metadata.
81. Cargo.lock
Release requires committed:
Cargo.lock
82. Gradle Dependency Locking
Enable dependency verification/locking where practical.
83. NDK Version Pinning
CI and developers use same NDK major/minor.
84. Gradle Wrapper
Commit:
gradlew
gradle-wrapper.properties
Use wrapper, not system Gradle.
85. AGP Pinning
Pin Android Gradle Plugin version.
Upgrade deliberately.
86. Kotlin Version Pinning
If Kotlin is used, pin version.
87. Build Reproducibility
Pin:
Rust toolchain
Cargo.lock
NDK
SDK level
Gradle wrapper
AGP
Kotlin
Java major
88. Build Environment Manifest
xtask doctor --json can emit machine-readable tool versions.
89. CI Build Container
For Linux CI, use controlled image containing:
JDK
Android SDK
NDK
Rust
or install deterministically.
90. Cache Strategy
Cache:
Cargo registry
Cargo git
Rust target
Gradle cache
NDK downloads
Do not cache:
final release staging blindly
without fingerprint validation.
91. ABI Parallelism
ABIs can build in parallel if machine memory allows.
92. Resource-Aware Build Parallelism
xtask can cap:
ABI jobs
Cargo jobs
for CI.
93. Artifact Staging Atomicity
Build into temporary directory:
target/android-staging.tmp
Then rename to active staging after success.
94. Partial Build Protection
If x86_64 build fails after arm64 succeeds:
do not leave mixed release staging
95. Native Digest Verification
Before Gradle:
hash every .so
and record manifest.
96. ELF Validation
On Linux host, inspect:
architecture
SONAME
needed libraries
using suitable tools.
97. Android API Compatibility
Ensure native library does not depend on symbols above configured minSdk unexpectedly.
98. Native Dependency Audit
Check DT_NEEDED.
Unexpected:
libfoo.so
not packaged:
fail build
99. Strip Strategy
Release:
strip runtime symbols
but preserve separate debug symbols.
100. Symbols
Collect:
unstripped .so
symbol files
mapping files
for crash diagnosis.
101. Native Debug Symbols
Store outside public APK/AAB release artifact where appropriate.
102. Play Console Symbols
Generate/upload compatible native debug symbol archive as part of release workflow if needed.
103. R8 / ProGuard
If Kotlin/Java surface is small:
R8 still enabled for release
Keep JNI-required classes/methods from being removed when necessary.
104. JNI Keep Rules
Generate/maintain explicit rules.
105. Resource Shrinking
Can enable for release if safe.
106. APK vs AAB
Official Play Store:
AAB
Direct site download:
APK
107. Build Commands
cargo xtask android bundle --release
cargo xtask android apk --release
108. Universal APK
For website distribution, choose:
universal APK
or separate ABI APKs.
109. Separate ABI APKs
Smaller downloads but more release files.
110. Recommended Direct Distribution
Initially:
one universal signed APK
for simplicity.
Later optionally:
per-ABI APKs
111. Play Bundle
AAB allows Play to generate device-specific splits.
112. Signing Architecture
Never hard-code:
keystore password
key password
in source.
113. Signing Inputs
Read from:
environment
CI secret store
local secure configuration
114. Local Release Signing
Possible:
ANDROID_KEYSTORE_PATH
ANDROID_KEYSTORE_PASSWORD
ANDROID_KEY_ALIAS
ANDROID_KEY_PASSWORD
Environment variable names can be product-specific.
115. Do Not Print Secrets
Command logs must redact:
passwords
tokens
key paths if sensitive
116. Play App Signing
Prefer Play App Signing for Play distribution if product chooses it.
Local/CI uses upload key.
117. Direct APK Signing
Website APK needs product signing key.
Protect carefully.
118. Signing Separation
Potential:
Play upload key
direct-distribution signing key
depending strategy.
119. Reproducibility vs Signing
Signing may make byte-for-byte reproducibility harder.
Keep:
unsigned deterministic artifact
+
signed distribution artifact
where useful.
120. Signing Validation
After build:
verify APK signature
using official Android tooling.
121. AAB Validation
Use:
bundletool
or official tooling for structural validation.
122. Package Name
Validate release application ID.
Prevent accidental:
.dev
.debug
package release.
123. Release Manifest Validation
Check:
INTERNET
BLUETOOTH
camera
microphone
foreground service types
against actual features.
124. Permission Audit
Release pipeline should produce permission report.
Unexpected dangerous permission:
fail or require explicit approval
125. Android Exported Components
Validate:
activities
services
receivers
providers
for unnecessary exported access.
126. Network Security Config
Validate production config.
Do not ship debug trust-all certificates.
127. Debuggable Flag
Release APK must be:
debuggable=false
128. Backup Rules
Validate Android backup behavior for:
identity keys
sensitive state
129. Native Key Storage
Part 19/identity secure-store config must align with release.
130. Build Channel
#![allow(unused)] fn main() { pub enum ReleaseChannel { Dev, Alpha, Beta, Stable, } }
131. Channel Metadata
Embed:
channel
version
commit
for diagnostics.
132. Package Suffix
Dev/beta may use:
applicationIdSuffix
allowing side-by-side install.
133. Release Candidate
cargo xtask android release --channel beta
134. Test Before Package
Release pipeline must run:
cargo fmt --check
cargo clippy
unit tests
protocol tests
Android native tests where available
before signing.
135. Release Gate Ordering
lint
↓
unit
↓
Rust build
↓
native artifact verify
↓
Gradle tests
↓
package
↓
package verify
↓
sign
↓
final verify
136. Android Unit Tests
Run:
Gradle unit tests
for Kotlin/platform bridge.
137. Instrumentation Tests
On emulator/device:
JNI load
native ABI handshake
basic runtime start
permissions
138. Codec Smoke Tests
For media release:
hardware codec capability
audio native stream
at least on hardware lab/nightly.
139. Emulator Limits
Emulator is useful for:
JNI
basic lifecycle
x86_64
not final hardware media validation.
140. Device Farm
Optional future CI:
real Android devices
for release candidate validation.
141. run
cargo xtask android run
Flow:
build selected ABI
assemble debug
adb install
adb launch
142. Device ABI Detection
If one device connected:
adb shell getprop ro.product.cpu.abi
Then build only that ABI for faster dev.
143. Emulator Detection
x86_64 emulator:
build x86_64 only
144. Multi-Device Run
Allow:
--device SERIAL
145. Logcat
Optional:
cargo xtask android run --logcat
filters Rust/app logs.
146. Rust Backtrace
Debug builds can enable native backtraces.
147. Native Crash Symbols
xtask can symbolize tombstone/backtrace using stored symbols.
148. android symbols
Command:
cargo xtask android symbols <crash-file>
maps native addresses where possible.
149. Release Artifact Directory
dist/android/<version>/
Contents:
app-release.aab
app-release.apk
checksums.txt
build-manifest.json
native-symbols.zip
sbom.*
licenses.*
150. Artifact Naming
Include:
product
version
channel
architecture if split
151. Checksums
Generate:
SHA-256
for website release.
152. SBOM
Generate software bill of materials for:
Rust dependencies
Gradle/Kotlin dependencies
native dependencies
153. License Report
Aggregate:
Cargo license metadata
Android dependencies
for release compliance.
154. Provenance
Build manifest should include:
Git commit
Rust version
NDK version
Gradle version
AGP version
JDK version
features
ABIs
artifact hashes
155. Build Manifest
Example:
{
"version": "1.0.0",
"commit": "...",
"abis": ["arm64-v8a", "x86_64"],
"rust": "...",
"ndk": "...",
"artifacts": {}
}
JSON is appropriate for external CI/release tooling.
156. Rebuild Comparison
Two builds can compare manifests.
157. Release Upload
xtask may prepare artifacts.
Actual upload can be separate.
158. Website Release
Could provide:
APK
checksum
signature metadata
release notes
159. Play Store Upload
If later automated:
use official Play publishing API/tool
behind explicit release command.
Do not embed credentials into repository.
160. Upload Separation
Safer initial architecture:
build/sign
then
manual Play Console upload
until automated deployment is mature.
161. CI Job Split
Example:
android-check
android-native-arm64
android-native-x86_64
android-package
android-release
162. PR CI
Build:
debug x86_64
arm64 compile check
to save time.
163. Main Branch CI
Build all release ABIs.
164. Release CI
Full:
tests
AAB
APK
symbols
SBOM
signatures
165. Matrix Builds
Each ABI can be separate CI job.
Final packaging job downloads verified .so artifacts.
166. Cross-Job Artifact Integrity
Package job verifies digest manifest from native jobs.
167. Avoid Mixing Commits
Every artifact embeds same:
Git SHA
Packaging fails if native artifacts came from another commit.
168. Feature Hash
Native artifact includes/exports:
feature configuration hash
Optional strong protection.
169. Build Cache Poisoning
Do not trust cache merely because filename matches.
Cargo handles dependency fingerprints; release staging additionally checks build metadata.
170. Clean Build Release
Official release should support:
--clean
or run from clean isolated CI checkout.
171. Hermetic Direction
Long-term:
containerized/pinned toolchain
for reproducible Android releases.
172. Nix/Devcontainer Optional
Can use:
Nix
container
mise/asdf
for developer setup, but not required architecture.
Rust doctor remains canonical validator.
173. Windows/macOS Build Hosts
Android build can run from:
Linux
Windows
macOS
if toolchains supported.
Official CI can standardize on Linux.
174. Path Handling
xtask must use Rust PathBuf.
No Unix-only path string assumptions.
175. Gradle Wrapper Invocation
Linux/macOS:
./gradlew
Windows:
gradlew.bat
Abstract in Rust.
176. Environment Sanitization
Pass only required environment into release subprocess where practical.
177. Secrets Isolation
Signing secrets only provided to signing/package phase.
Not to ordinary compile/test jobs.
178. CI Secret Boundary
Unsigned release build can run in untrusted build job.
Signing runs in protected environment.
179. Two-Stage Release
Stage A:
build unsigned verified artifact
Stage B:
sign/package final artifact
180. Native Code Signing
Android signs APK/AAB package as a whole.
Individual .so usually rely on package integrity.
Still hash them in build manifest.
181. JNI ABI Compatibility
Part 19 ABI version is validated at build/startup.
182. Kotlin Stub Generation
If JNI wrappers are generated:
xtask generate bindings
before compile.
183. Binding Drift
CI checks generated Kotlin/JNI header/binding files are up to date.
184. Cbindgen/UniFFI
If used for other bindings:
xtask orchestrates generation
but Android JNI may remain custom.
185. Generated Files Policy
Choose:
committed generated API
or
build-generated
and enforce consistently.
186. Recommended JNI Generation
Keep generated bindings reproducible and diffable.
For release:
regenerate
compare
fail if unexpected dirty diff
187. Android Resources
xtask does not replace Android resource processing.
Gradle handles:
XML
icons
strings
manifest merge
188. Dioxus Assets
If Dioxus assets require generation/bundling:
xtask prepares them before Gradle
189. Asset Fingerprinting
Use generated asset manifest.
190. Native Asset Separation
Do not hide .so inside generic assets.
Use jniLibs.
191. Split Install Testing
For AAB, use bundletool to generate/install local device APK set.
192. Bundle Test
AAB
↓
bundletool
↓
.apks
↓
install
CI/nightly can validate.
193. Universal APK from AAB
Can generate for testing, but direct website APK can also come from Gradle APK task.
194. MinSdk Enforcement
Rust native code must respect minSdk.
195. API-Level Compile Flags
NDK target includes API level:
aarch64-linux-android26
example.
xtask derives from one minSdk source.
196. ABI-Specific API Level
Avoid accidental inconsistent API levels across targets.
197. 64-Bit Policy
Play Store requires appropriate 64-bit support for native apps.
Arm64 must be first-class.
198. 32-Bit Support Decision
Do not carry armeabi-v7a forever by default.
It increases:
build time
test matrix
download size
maintenance
Enable only if user base requires.
199. Native Library Size
Track per ABI.
Release guard can warn on major regression.
200. APK/AAB Size Budget
Track:
total
native
assets
DEX
201. Size Regression Report
Example:
+12 MB native due to new codec
202. AV1 Software Codec Impact
Software AV1 can significantly increase binary size.
Feature/config should make size visible.
203. Hardware Codec Dependencies
Android hardware codec path generally uses system APIs and does not require shipping codec libraries.
This helps size.
204. Release Architecture Choice
If software AV1 library is huge:
consider dynamic feature only if Android architecture permits and complexity justified
but start simple.
205. Debug Symbols Size
Do not mistake stripped APK size with symbol archive size.
206. Build Timing
xtask reports durations:
Rust per ABI
Gradle
packaging
tests
207. Performance Optimization
Use incremental builds for dev.
Use clean reproducible environment for release.
208. Build Cancellation
If one ABI fails:
cancel remaining packaging
209. Error Output
Preserve:
Cargo stderr
Gradle stderr
with clear phase header.
210. No Hidden Retry
Build failures should not be automatically retried repeatedly unless known transient network download phase.
211. Offline Build
After dependencies/toolchains cached, support:
cargo --offline
Gradle offline
for reproducibility/air-gapped use.
212. Vendoring
Optional enterprise:
cargo vendor
Gradle dependency mirror
213. Supply Chain
Release pipeline should audit:
Cargo.lock
Gradle lockfiles
checksums
SBOM
214. Cargo Audit
Security/dependency scanning can be part of release gate.
Tool choice may evolve.
215. Gradle Dependency Verification
Enable checksums/signatures where supported.
216. Forbidden Licenses
Optional project policy can fail release based on dependency license.
217. Signing Key Rotation
Release tooling must allow new keystore/upload key configuration without code changes.
218. Key Backup
Signing keys require secure backup/operational process.
Outside source repository.
219. Debug Signing
Use standard debug key only for dev.
Never website-distribute debug-signed build.
220. Release Environment Marker
At runtime diagnostics:
release_signed
debug
dev
221. Build Integrity Check at Startup
Optional:
Rust JNI can compare:
app build version
native build version
and emit fatal error on mismatch.
222. Crash Symbol Server
Future self-hosted system can store:
version → symbols
for crash reports.
223. Privacy
Build tooling contains no user data.
Crash symbols are code metadata only.
224. Android Build Testkit
Create:
xtask tests
for:
ABI mappings
version generation
staging
manifest parsing
artifact validation
225. Unit Test Toolchain Discovery
Mock filesystem/environment.
Do not require real NDK for every xtask unit test.
226. Integration Test
CI runs real:
NDK build
Gradle package
227. Stale Artifact Test
Put old .so in staging.
Run build.
Expected:
removed/replaced
228. Missing ABI Test
Delete x86_64 library before package.
Expected:
packaging blocked
229. Wrong Profile Test
Stage debug library.
Attempt release package.
Expected:
blocked
230. Wrong Commit Test
Native manifest Git SHA differs.
Expected:
blocked
231. ABI Handshake Test
Package app with intentionally mismatched native ABI version.
Expected:
startup fails clearly
232. Signing Test
Release pipeline without signing secrets:
fail before final signing
or produce explicit unsigned artifact only if requested.
233. Permission Regression Test
Unexpected Android permission appears.
Release guard detects.
234. Debuggable Test
Inspect APK.
Assert release is not debuggable.
235. Native Dependency Test
Inspect .so.
Assert only approved Android/system libraries are required.
236. Symbol Test
Ensure symbol archive corresponds to exact native digest.
237. Reproducibility Test
Build unsigned artifact twice in controlled environment.
Compare:
manifest
native hashes
and byte-level outputs where practical.
238. Release Command
Example:
cargo xtask android release \
--version 1.4.0 \
--channel stable \
--apk \
--aab
239. Release Flow
validate clean tree
↓
validate versions
↓
doctor
↓
lint/test
↓
build all ABIs
↓
verify native
↓
Gradle release
↓
verify package
↓
sign
↓
verify signature
↓
symbols
↓
SBOM/licenses
↓
checksums
↓
dist/
240. Release Confirmation
Before signing stable release, xtask can require:
--confirm-release
in local mode.
CI can use noninteractive protected job.
241. Build State Machine
#![allow(unused)] fn main() { pub enum AndroidReleaseStage { Validate, RustBuild, NativeVerify, AndroidBuild, PackageVerify, Signing, ReleaseMetadata, Complete, } }
242. State Logging
Print:
[3/8] NativeVerify
for clear build UX.
243. Dry Run
cargo xtask android release --dry-run
Shows:
targets
features
versions
signing mode
commands
without signing/upload.
244. Configuration Precedence
Recommended:
CLI
>
explicit environment
>
project config
>
defaults
Document clearly.
245. Config File
Example:
build/android.ron
246. Example Config
(
min_sdk: 26,
abis: [
"arm64-v8a",
"x86_64",
],
native_lib: "comm_android",
release_features: [
"android",
"calls",
"dtn",
],
)
247. No Secrets in RON
Do not store:
keystore password
Play API token
in project config.
248. Build Plugin Architecture
If other projects reuse the communication platform, package build helpers as:
comm-android-build
crate.
249. Reusable Builder Crate
Provides:
ABI mapping
artifact manifest
Gradle handoff
JNI verification
External products can reuse it.
250. Product-Specific xtask
Each product can wrap:
comm-android-build
with its own:
package name
features
signing
assets
251. Why This Matters for Reusability
The P2P platform is intended to be reused by other applications.
A reusable Android SDK should not require users to memorize manual NDK commands.
252. Consumer Build Integration
External product could:
#![allow(unused)] fn main() { AndroidBuild::new() .native_crate("comm-android-ffi") .abis([...]) .gradle_project("android/") .build()?; }
253. C ABI SDK Distribution Alternative
Part 19 can also distribute prebuilt Android:
AAR
so non-Rust consumers do not compile Rust at all.
254. Source Integration vs Prebuilt SDK
Two modes:
Source build:
xtask compiles Rust
SDK build:
Gradle consumes official AAR/native libs
255. First-Party App
Use source build for maximum control.
256. Third-Party SDK Consumer
Prefer:
AAR
where appropriate.
257. AAR Build
xtask can produce:
comm-sdk.aar
containing:
JNI bridge
native .so per ABI
minimal Kotlin API
258. AAR Verification
Inspect:
classes.jar
jni/
manifest
and ABI metadata.
259. Maven Publication
Future SDK distribution:
Maven repository
for Android consumers.
260. Maven Publishing
Gradle handles publishing artifact format.
xtask orchestrates version/validation.
261. SDK Release Separation
App release and SDK release can be separate commands.
cargo xtask android release-app
cargo xtask android release-sdk
262. Binary Compatibility
Part 19 C ABI version is embedded in AAR metadata.
263. SDK Consumer Safety
At runtime, wrapper validates ABI.
264. Documentation Generation
Release can generate:
BUILD_INFO.md
from manifest for audit.
265. Build Troubleshooting
doctor should produce actionable output:
NDK 27 found, project requires 28.x
not generic failure.
266. No Automatic Global Mutation
Do not silently modify:
user shell config
global Java
system SDK
267. Local Tool Bootstrap
If project chooses, provide:
tools/
download cache.
But user consent is needed for large downloads.
268. CI Parity
Developer and CI should invoke same:
cargo xtask android ...
not maintain separate CI shell implementation.
269. GitHub/GitLab/etc.
CI config should be thin:
setup cache
setup secrets
cargo xtask android release
270. Forgeyard Integration
If the project later uses its own CI/CD platform, Forgeyard should call the same xtask.
The build contract remains tool-neutral.
271. Release Notes
xtask may generate template from Git history, but release notes remain product-level concern.
272. Artifact Upload Is Separate Capability
Do not couple building tightly to:
GitHub Releases
Play Store
website
Adapters can be added.
273. Build Security Invariants
- Release
.sofiles are never manually staged. - All selected ABIs must be present before packaging.
- Native artifacts must match release profile.
- Native artifacts and Android wrapper must come from same build/commit.
- Release feature set is explicit.
- Android/FFI ABI versions are checked.
- Signing secrets never enter source control.
- Release package is verified after signing.
- Debuggable/developer features cannot silently ship.
- Gradle cannot package stale release native artifacts without detection.
- Build tool versions are pinned/validated.
- final artifacts include checksums/build provenance.
- CI and local development use the same orchestrator.
- failed partial ABI builds never become active staging.
- build caches cannot override artifact fingerprint validation.
274. Initial Production Scope
Implement first:
xtask Android CLI
doctor
arm64-v8a
x86_64
cargo-ndk/direct NDK build adapter
automatic staging
native artifact manifest
Gradle verification task
debug/release profiles
APK
AAB
runtime native/app ABI handshake
release signing
checksums
symbols
Then:
SBOM
license report
AAR SDK
device-run helper
bundletool verification
CI matrix
permission audit
Defer initially:
automatic Play Store publishing
complex remote build service
all possible Android ABIs
custom replacement for Gradle/AGP
275. Implementation Phases
Phase 1 — xtask
CLI
config
doctor
tool discovery
Phase 2 — Native Build
ABI matrix
Cargo/NDK
artifact collection
Phase 3 — Staging
generated jniLibs
artifact manifest
stale protection
Phase 4 — Gradle Orchestration
assemble
bundle
verification task
Phase 5 — Runtime Compatibility
build ID
ABI version handshake
Phase 6 — Release
signing
APK
AAB
checksums
symbols
Phase 7 — CI
matrix
cache
artifact digest
protected signing
Phase 8 — SDK
AAR
Maven
third-party consumers
Phase 9 — Hardening
stale artifact tests
wrong profile tests
permission regression
reproducibility
supply-chain checks
276. Definition of Done
Part 27 is complete when:
- a developer can build Android with one
cargo xtaskcommand - Rust native libraries are automatically built for all selected ABIs
- no manual
jniLibscopy is required - generated native staging is outside source control
- Gradle refuses stale/missing/wrong-profile native libraries
- arm64-v8a is a first-class release ABI
- x86_64 emulator/developer builds work
- the exact NDK/toolchain version is validated
- Rust target and Android ABI mapping is centralized
- debug/dev/profile/release modes map consistently across Cargo and Gradle
- release features are explicit and validated
- app/native ABI versions handshake at runtime
- AAB is produced for Play Store distribution
- signed APK is produced for direct website distribution
- release signing secrets remain outside repository/logs
- package signatures are verified after build
- symbols are archived for native crash diagnosis
- release artifacts receive checksums and provenance metadata
- CI and local builds execute the same Rust orchestrator
- partial or mixed-commit native artifacts cannot be packaged
- automated tests cover stale artifacts, missing ABIs, wrong profiles, JNI mismatch, and release package validation
277. Relationship to Earlier Parts
Part 27 operationalizes:
19 — C ABI / FFI
20 — Embedded/build portability principles
25 — Android Hardware Surface / Zero-Copy Video
26 — Rust-First Audio DSP / Native Audio
It also packages the Android implementation of:
Iroh P2P networking
Dioxus UI
DTN
Bluetooth/Wi-Fi communication
hardware video codecs
Opus audio
emergency functionality
plugin/extension runtime where enabled
278. Final Architecture
DEVELOPER / CI
│
▼
cargo xtask
│
┌────────────────┼─────────────────┐
│ │ │
Doctor Rust/NDK Build Release Policy
│ │ │
│ ┌────────┼────────┐ │
│ │ │ │
│ arm64-v8a x86_64 │
│ │ │ │
│ └────────┬────────┘ │
│ │ │
│ Native Artifact │
│ Manifest │
│ │ │
└────────────────┼─────────────────┘
│
Generated jniLibs
│
▼
Gradle / AGP
┌─────────┴─────────┐
│ │
APK AAB
│ │
Website release Play Store
The intended developer experience becomes:
cargo xtask android run
for daily work and:
cargo xtask android release --apk --aab
for official release.
No step should require:
"remember to run cargo-ndk first"
"copy this .so into jniLibs"
"make sure it is the release one"
Those are build-system responsibilities.
279. Final Principle
The Android build system should be Rust-driven without pretending Android packaging is not an Android concern.
The correct division is:
Rust xtask:
owns intent
owns orchestration
owns validation
owns native compilation
owns release policy
Cargo + NDK:
compile Rust for Android
Gradle / AGP:
package and sign Android applications
CI:
invokes the same xtask
This gives the project a predictable one-command Android pipeline while preserving compatibility with the official Android ecosystem.
The result is not merely:
"the app can be built"
but:
the exact native code,
for the exact ABIs,
with the exact features,
from the exact source revision,
is automatically packaged,
verified,
signed,
and made ready for Play Store and direct distribution.
That is the production role of Part 27.