import java.io.File import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) } android { namespace = "de.rfc1437.ironstorage.rustbridge" compileSdk = libs.versions.compileSdk.get().toInt() ndkVersion = libs.versions.ndk.get() defaultConfig { minSdk = libs.versions.minSdk.get().toInt() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } defaultConfig { consumerProguardFiles("consumer-rules.pro") } lint { abortOnError = true warningsAsErrors = true // The user scoped this milestone to ARM Android; x86 ChromeOS is excluded. disable += "ChromeOsAbiSupport" } } kotlin { jvmToolchain(25) compilerOptions { jvmTarget.set(JvmTarget.JVM_17) allWarningsAsErrors.set(true) } } dependencies { implementation(variantOf(libs.jna) { artifactType("aar") }) implementation(libs.annotation) testImplementation(libs.junit) androidTestImplementation(libs.android.test.runner) } val workspace = rootProject.layout.projectDirectory.dir("..").asFile val cargoExecutable = providers.environmentVariable("CARGO").orElse( providers.environmentVariable("PATH").map { path -> path.split(File.pathSeparatorChar).map { File(it, "cargo") } .firstOrNull { it.isFile && it.canExecute() }?.absolutePath ?: error("Cargo must be installed and in PATH") } ) val rustVersion = libs.versions.rust.get() val rustcExecutable = providers.exec { commandLine("rustup", "which", "--toolchain", rustVersion, "rustc") }.standardOutput.asText.map { it.trim() } val ndkDirectory = androidComponents.sdkComponents.ndkDirectory val sdkDirectory = androidComponents.sdkComponents.sdkDirectory val rustInputs = files( workspace.resolve("Cargo.toml"), workspace.resolve("Cargo.lock"), rootProject.file("rust-toolchain.toml"), fileTree(workspace.resolve("crates")) { include("**/*.rs", "**/Cargo.toml", "**/uniffi.toml") } ) val checkRustToolchain = tasks.register("checkRustToolchain") { workingDir = workspace commandLine( cargoExecutable.get(), "+$rustVersion", "run", "--locked", "--package", "ironstorage-android-build", "--", "check-toolchain", cargoExecutable.get(), rustVersion, ndkDirectory.get().asFile.absolutePath, libs.versions.ndk.get(), libs.versions.cargoNdk.get() ) } androidComponents.onVariants { variant -> val name = variant.name val title = name.replaceFirstChar { it.uppercaseChar() } val rawNative = layout.buildDirectory.dir("generated/native-raw/$name/jniLibs") val nativeOutput = layout.buildDirectory.dir("generated/native/$name/jniLibs") val rawBindings = layout.buildDirectory.dir("generated/bindings-raw/$name") val bindings = layout.buildDirectory.dir("generated/bindings/$name") val rustFlags = listOf( "-D warnings", "-C link-arg=-Wl,-z,max-page-size=16384", "-C link-arg=-Wl,-z,common-page-size=16384", "-C link-arg=-Wl,--build-id=sha1" ).joinToString(" ") val native = tasks.register("build${title}Rust") { dependsOn(checkRustToolchain) workingDir = workspace inputs.files(rustInputs) inputs.property("rustVersion", rustVersion) inputs.property("cargoNdk", libs.versions.cargoNdk.get()) inputs.property("ndkVersion", libs.versions.ndk.get()) inputs.property("minSdk", libs.versions.minSdk.get()) inputs.property("rustFlags", rustFlags) inputs.property("abis", listOf("arm64-v8a", "armeabi-v7a")) inputs.property("releaseDebug", "2") inputs.property( "devDebug", providers.environmentVariable("CARGO_PROFILE_DEV_DEBUG").orElse("cargo-default") ) inputs.property( "incremental", providers.environmentVariable("CARGO_INCREMENTAL").orElse("cargo-default") ) inputs.property( "cargoTargetDirectory", providers.environmentVariable("CARGO_TARGET_DIR").orElse("workspace-default") ) outputs.dir(rawNative) environment("RUSTUP_TOOLCHAIN", rustVersion) environment("RUSTC", rustcExecutable.get()) environment("ANDROID_NDK_HOME", ndkDirectory.get().asFile.absolutePath) environment("ANDROID_HOME", sdkDirectory.get().asFile.absolutePath) environment("RUSTFLAGS", rustFlags) environment("CARGO_PROFILE_RELEASE_DEBUG", "2") commandLine( listOf( cargoExecutable.get(), "+$rustVersion", "ndk", "--target", "arm64-v8a", "--target", "armeabi-v7a", "--platform", libs.versions.minSdk.get(), "--output-dir", rawNative.get().asFile.absolutePath, "build", "--locked", "--package", "ironstorage-android", "--lib" ) + if (variant.buildType == "release") listOf("--release") else emptyList() ) } val audit = tasks.register("package${title}Rust") { dependsOn(native) workingDir = workspace inputs.dir(rawNative) inputs.files( fileTree(workspace.resolve("tools/android-build")) { include("**/*.rs", "Cargo.toml") } ) outputs.dir(nativeOutput) commandLine( cargoExecutable.get(), "+$rustVersion", "run", "--locked", "--package", "ironstorage-android-build", "--", "package-native", rawNative.get().asFile.absolutePath, nativeOutput.get().asFile.absolutePath ) } val generate = tasks.register("generate${title}RustBindings") { dependsOn(audit) workingDir = workspace inputs.dir(nativeOutput) inputs.file(rootProject.file("uniffi.toml")) inputs.files( fileTree(workspace.resolve("tools/android-build")) { include("**/*.rs", "Cargo.toml") } ) outputs.dir(rawBindings) commandLine( cargoExecutable.get(), "+$rustVersion", "run", "--locked", "--package", "ironstorage-android-build", "--", "contract-bindings", nativeOutput.get().file("arm64-v8a/libironstorage_android.so").asFile.absolutePath, rootProject.file("uniffi.toml").absolutePath, rawBindings.get().asFile.absolutePath, "phone" ) } val prepare = tasks.register("prepare${title}RustBindings") { dependsOn(generate) workingDir = workspace inputs.dir(rawBindings) inputs.files( fileTree(workspace.resolve("tools/android-build")) { include("**/*.rs", "Cargo.toml") } ) outputs.dir(bindings) commandLine( cargoExecutable.get(), "+$rustVersion", "run", "--locked", "--package", "ironstorage-android-build", "--", "prepare-bindings", rawBindings.get().dir("kotlin").asFile.absolutePath, bindings.get().asFile.absolutePath ) } variant.sources.kotlin!!.addStaticSourceDirectory(bindings.get().asFile.absolutePath) variant.sources.jniLibs!!.addStaticSourceDirectory(nativeOutput.get().asFile.absolutePath) tasks.matching { it.name == "pre${title}Build" || it.name == "compile${title}Kotlin" }.configureEach { dependsOn(prepare) } } // Instrumentation suites share persistent emulators and must not compete for input focus. tasks.matching { it.name == "connectedDebugAndroidTest" }.configureEach { mustRunAfter(":app:connectedDebugAndroidTest") }