All checks were successful
ci / test (pull_request) Successful in 3m22s
Adds the Android product alongside the existing Linux TUI, in one repo. SDK — crates/onionwire-sdk is a UniFFI facade over the very same `onionwire` crate the TUI runs on. Identity, invite, friend upsert, send/receive, rotate and wipe all delegate; no protocol is reimplemented, so an Android peer and a Linux peer interoperate. It is its own Cargo workspace because Arti's TLS backends are non-additive: the TUI keeps native-tls (OpenSSL), Android needs rustls + static-sqlite (no OpenSSL, no system libsqlite3 in the NDK). android/ — Gradle project. :sdk produces the AAR (Kotlin bindings generated at build time + libonionwire_sdk.so via cargo-ndk), :app is a Kotlin/Compose/M3 messenger depending on :sdk only. minSdk 26, targetSdk/compileSdk 36, INTERNET-only, data in filesDir, backups excluded. Root Cargo.toml grows `native-tls` (default) and `rustls` features so exactly one Arti TLS backend is selected per build graph. The default build is unchanged: same backend, ratatui still a normal dependency, src/tui.rs untouched. Also: Store::self_fingerprint/set_petname + Node wrappers (additive only), scripts/build-android-local.sh, README sections, .gitignore for local SDK paths.
163 lines
5.8 KiB
Kotlin
163 lines
5.8 KiB
Kotlin
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|
|
|
plugins {
|
|
alias(libs.plugins.android.library)
|
|
alias(libs.plugins.kotlin.android)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Rust: build crates/onionwire-sdk for the requested Android ABIs and generate
|
|
// the UniFFI Kotlin bindings from the host cdylib.
|
|
//
|
|
// Nothing here touches the Linux TUI crate's feature graph: the SDK crate is
|
|
// its own Cargo workspace that selects Arti's rustls backend.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
val repoRoot: File = rootProject.projectDir.parentFile
|
|
val sdkCrateDir: File = repoRoot.resolve("crates/onionwire-sdk")
|
|
|
|
/** NDK version used for the Rust link step. Must match an installed NDK. */
|
|
val onionwireNdkVersion: String = (findProperty("onionwire.ndk") as String?)
|
|
?: "28.2.13676358"
|
|
|
|
/** Compile against this API level. Keep in sync with `minSdk`. */
|
|
val onionwireApiLevel: Int = ((findProperty("onionwire.api") as String?) ?: "26").toInt()
|
|
|
|
val androidSdkDir: File = sequenceOf(
|
|
System.getenv("ANDROID_HOME"),
|
|
System.getenv("ANDROID_SDK_ROOT"),
|
|
).filterNotNull().map(::File).firstOrNull()
|
|
?: rootProject.file("local.properties")
|
|
.takeIf { it.exists() }
|
|
?.readLines()
|
|
?.firstOrNull { it.startsWith("sdk.dir=") }
|
|
?.substringAfter('=')
|
|
?.trim()
|
|
?.let(::File)
|
|
?: error("ANDROID_HOME / ANDROID_SDK_ROOT unset and no sdk.dir in android/local.properties")
|
|
|
|
val ndkDir: File = androidSdkDir.resolve("ndk/$onionwireNdkVersion")
|
|
require(ndkDir.isDirectory) {
|
|
"NDK $onionwireNdkVersion not found at $ndkDir — run: sdkmanager --install \"ndk;$onionwireNdkVersion\""
|
|
}
|
|
|
|
val cargoExe: String = (findProperty("onionwire.cargo") as String?)
|
|
?: System.getenv("CARGO")
|
|
?: File(System.getProperty("user.home"), ".cargo/bin/cargo").takeIf { it.canExecute() }?.absolutePath
|
|
?: "cargo"
|
|
|
|
/** `-Ponionwire.abis=arm64-v8a,x86_64` to build more than one ABI. */
|
|
val onionwireAbis: List<String> = (findProperty("onionwire.abis") as String?)
|
|
?.split(',')?.map(String::trim)?.filter(String::isNotEmpty)
|
|
?: listOf("arm64-v8a")
|
|
|
|
val jniLibsDir = layout.buildDirectory.dir("rustJniLibs")
|
|
val uniffiKotlinDir = layout.buildDirectory.dir("generated/uniffi")
|
|
|
|
/** Host cdylib. UniFFI's `--library` mode dlopens it, so it must be host-arch. */
|
|
val cargoHostLib by tasks.registering(Exec::class) {
|
|
group = "onionwire"
|
|
description = "Builds libonionwire_sdk.so for the host (used only to emit bindings)."
|
|
workingDir = sdkCrateDir
|
|
environment("ANDROID_HOME", androidSdkDir.absolutePath)
|
|
commandLine(cargoExe, "build", "--release", "--lib")
|
|
}
|
|
|
|
val uniffiBindgen by tasks.registering(Exec::class) {
|
|
group = "onionwire"
|
|
description = "Generates the Kotlin UniFFI bindings into build/generated/uniffi."
|
|
dependsOn(cargoHostLib)
|
|
workingDir = sdkCrateDir
|
|
inputs.file(sdkCrateDir.resolve("src/lib.rs"))
|
|
outputs.dir(uniffiKotlinDir)
|
|
environment("ANDROID_HOME", androidSdkDir.absolutePath)
|
|
doFirst { uniffiKotlinDir.get().asFile.mkdirs() }
|
|
commandLine(
|
|
cargoExe, "run", "--release", "--bin", "uniffi-bindgen", "--",
|
|
"generate",
|
|
"--library", "target/release/libonionwire_sdk.so",
|
|
"--language", "kotlin",
|
|
"--no-format",
|
|
"--config", "uniffi.toml",
|
|
"--out-dir", uniffiKotlinDir.get().asFile.absolutePath,
|
|
)
|
|
}
|
|
|
|
val cargoBuildAndroid by tasks.registering(Exec::class) {
|
|
group = "onionwire"
|
|
description = "Cross-compiles crates/onionwire-sdk for ${onionwireAbis.joinToString()}."
|
|
workingDir = sdkCrateDir
|
|
inputs.dir(sdkCrateDir.resolve("src"))
|
|
inputs.file(sdkCrateDir.resolve("Cargo.toml"))
|
|
inputs.file(sdkCrateDir.resolve("Cargo.lock"))
|
|
inputs.dir(repoRoot.resolve("src"))
|
|
inputs.file(repoRoot.resolve("Cargo.toml"))
|
|
outputs.dir(jniLibsDir)
|
|
environment("ANDROID_HOME", androidSdkDir.absolutePath)
|
|
environment("ANDROID_NDK_HOME", ndkDir.absolutePath)
|
|
environment("NDK_HOME", ndkDir.absolutePath)
|
|
commandLine(
|
|
buildList {
|
|
add(cargoExe); add("ndk")
|
|
onionwireAbis.forEach { add("-t"); add(it) }
|
|
add("--platform"); add(onionwireApiLevel.toString())
|
|
add("-o"); add(jniLibsDir.get().asFile.absolutePath)
|
|
add("build"); add("--release"); add("--lib")
|
|
}
|
|
)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
android {
|
|
namespace = "com.siriusdevops.onionwire.sdk"
|
|
compileSdk = 36
|
|
ndkVersion = onionwireNdkVersion
|
|
|
|
defaultConfig {
|
|
minSdk = onionwireApiLevel
|
|
consumerProguardFiles("consumer-rules.pro")
|
|
ndk {
|
|
abiFilters += onionwireAbis
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
sourceSets["main"].jniLibs.srcDir(jniLibsDir)
|
|
sourceSets["main"].kotlin.srcDir(uniffiKotlinDir)
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = false
|
|
}
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_17)
|
|
}
|
|
}
|
|
|
|
dependencies {
|
|
// UniFFI's Kotlin backend talks to the Rust cdylib through JNA direct
|
|
// mapping, and the async bindings need coroutines. Both are part of the
|
|
// public API surface, so they are `api` — a consumer of the AAR needs them
|
|
// on its own compile classpath. JNA's `aar` variant is what carries
|
|
// libjnidispatch.so for each ABI.
|
|
api(variantOf(libs.jna) { artifactType("aar") })
|
|
api(libs.kotlinx.coroutines.core)
|
|
}
|
|
|
|
tasks.withType<KotlinCompile>().configureEach {
|
|
dependsOn(uniffiBindgen)
|
|
}
|
|
|
|
tasks.matching { it.name == "preBuild" }.configureEach {
|
|
dependsOn(cargoBuildAndroid, uniffiBindgen)
|
|
}
|