Direct Android build
cargo ferry build android
The command builds only. It does not discover devices, start an emulator, install the APK, launch the application, or stream logs.
Pipeline
- Validate
ferry.tomland discover a compatible SDK platform, complete Build Tools, NDK LLVM prebuilt, and JDK. - Generate a deterministic
AndroidManifest.xml,res/tree, and private Java runtime bridge undertarget/ferry/. - Run Cargo once per configured ABI with a target-specific NDK Clang linker and
--message-format=json-render-diagnostics. - Parse Cargo
compiler-artifactmessages for thecdylib. Parsebuild-script-executedmessages and recursively collect dependency.dexfiles from thoseOUT_DIRs. - Compile the generated
FerryActivity, capability bridge, notification receiver, widget provider, and read-only share provider directly withjavacagainst the selectedandroid.jar. - Compile and link resources with
aapt2. - Merge compiled bridge classes and dependency bytecode with
d8. - Add stored
lib/<abi>/lib<name>.soandclasses*.dexentries to the resource APK. - Run
zipalign -P 16 -f 4before signing. - Sign with
apksigner, then verify the signature and runzipalign -c -P 16 4. - Independently inspect the ZIP: reject unsafe/duplicate names; require manifest, resources, icon, sequential DEX files, exact ABI libraries, and matching ELF class/machine headers. Enabled manifest components must have class definitions in merged DEX.
aapt2 dump badgingmust report the configured package andorg.rustferry.bridge.FerryActivitylauncher.
All external processes receive an executable plus an argument array. Paths with spaces or Unicode are not joined into a shell command. Each stage has a bounded runtime and a log below target/ferry/android/<profile>/logs/.
Output
The debug artifact is:
target/ferry/android/debug/<native-library-name>.apk
Intermediates and generated platform glue remain under target/ferry/; no Gradle project, Java/Kotlin source, or Android Studio project is written into user source.
Runtime bridge
FerryActivity is a generated subclass of Android’s NativeActivity, so the Rust entry point remains android_main(AndroidApp). The generated starter installs rustferry::android before Slint. Typed RustFerry calls cross one private JSON/JNI method; Java performs Android framework and main-thread work, while Rust owns typed results and application events.
Capability flags are baked into the bridge. rustferry::supports is true only when the corresponding configuration enables a concrete Android implementation. Persistent ordinary storage uses FileStorage below the application’s internal data directory. rustferry::android::with_context is an advanced synchronous escape hatch; raw JNI references must not outlive its callback.
Android emits foreground, background, resume, pause, low-memory, theme, window-size, network, deep-link, and notification-open events when the platform provides them. It deliberately does not translate Activity.onDestroy into Terminating: configuration changes also destroy activities, while process termination may deliver no callback. Persist important state eagerly.
Current Android widget rendering supports title/value/caption text plus one text, link, or button content node. Other widget content, image, or progress shapes return a backend error instead of reporting false success. File sharing accepts files inside application-owned files/cache directories through the generated read-only content provider. Android Live Activities use the configured ongoing-notification fallback.
ABIs
ferry.toml ABI | Rust target | APK directory |
|---|---|---|
arm64-v8a | aarch64-linux-android | lib/arm64-v8a/ |
x86_64 | x86_64-linux-android | lib/x86_64/ |
armeabi-v7a | armv7-linux-androideabi | lib/armeabi-v7a/ |
Each configured Rust target must already be installed. Cargo’s own target directory provides incremental Rust compilation. Generated resource and D8 intermediates use content fingerprints plus output-digest completion markers. Interrupted or modified intermediates are rebuilt instead of counted as cache hits, and builds sharing one profile output are serialized with a lock.
Dry run
cargo ferry build android --dry-run performs discovery and request validation, then returns an ordered plan without writing generated files, creating a keystore, or launching commands. Commands are represented as redacted argument arrays. The D8 step explicitly records that its inputs are deferred until Cargo JSON is parsed.
The custom-tool order follows Android’s documentation for AAPT2, zipalign, and apksigner.
Recorded artifact evidence
Current RustFerry evidence comes from Platform artifacts run 30719811812 at commit 8ed0192. The public CLI generated and built default Starter and Kitchen Sink projects for arm64. Both APKs passed ZIP integrity, v2/v3 signature, 16 KiB-aware alignment, package/launcher/API, classes.dex, compiled resources and icon, AArch64 ELF, android_main, and JNI callback checks. The Kitchen Sink APK additionally passed exact permission, deep-link, notification receiver, file provider, widget provider, and Live Activity fallback inspection.
Historical pre-rename evidence remains reproducible by its exact path: on 2026-08-01, generated_minimal_project_produces_verified_apk built target/android-e2e/pocket/android/debug/android_probe.apk with the installed SDK 35, Build Tools, NDK 29, Java 21, and aarch64-linux-android target. APK signature verification, 16 KiB-aware alignment, package com.example.androidprobe, singleTop launcher, ZIP integrity, DEX classes, the arm64-v8a ELF header, android_main, and the JNI callback passed inspection. Signed-binary XML inspection also proved the exact configured permission set, Activity/FileProvider/NotificationReceiver/WidgetProvider components, and scheme=probe;host=open.example;pathPrefix=/details deep-link filter. A repeated build reported cache hits for AAPT2 compile/link and D8. The pre-rename public CLI also completed new then build for fresh Starter and Kitchen Sink projects; exact legacy paths and scope are recorded in the historical status record.
No emulator or device behavior was observed in either the current or historical validation.