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 = (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().configureEach { dependsOn(uniffiBindgen) } tasks.matching { it.name == "preBuild" }.configureEach { dependsOn(cargoBuildAndroid, uniffiBindgen) }