plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) } // Build isodrive native binary from submodule (only if binaries are missing) val buildIsodrive by tasks.registering { group = "native" description = "Build isodrive binary for all Android architectures" val isodriveDir = rootProject.file("isodrive") val outputDir = file("src/main/assets/bin") val architectures = listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86") // Check if all binaries exist val allBinariesExist = architectures.all { arch -> outputDir.resolve("$arch/isodrive").exists() } // Skip if all binaries already exist onlyIf { !allBinariesExist } doLast { // Verify submodule is initialized if (!isodriveDir.resolve("src").exists()) { throw GradleException("isodrive submodule not initialized. Run: git submodule update --init") } val sourceFiles = listOf( "src/util.cpp", "src/configfsisomanager.cpp", "src/androidusbisomanager.cpp", "src/main.cpp" ) val srcs = sourceFiles.joinToString(" ") { "$isodriveDir/$it" } val cflags = "-I$isodriveDir/src/include -static-libstdc++ -Os -s" val compilers = mapOf( "arm64-v8a" to "aarch64-linux-android26-clang++", "armeabi-v7a" to "armv7a-linux-androideabi26-clang++", "x86_64" to "x86_64-linux-android26-clang++", "x86" to "i686-linux-android26-clang++" ) // Check if we're on NixOS (nix-shell available) val useNixShell = File("/etc/NIXOS").exists() || Runtime.getRuntime().exec(arrayOf("which", "nix-shell")).waitFor() == 0 if (useNixShell) { println("NixOS detected, using nix-shell for NDK...") // Build all architectures in one nix-shell invocation val buildScript = compilers.entries.joinToString("\n") { (arch, compiler) -> val archDir = outputDir.resolve(arch) val output = archDir.resolve("isodrive") if (output.exists()) { "echo 'isodrive for $arch already exists, skipping'" } else { """ mkdir -p "$archDir" echo "Building isodrive for $arch..." "${'$'}TOOLCHAIN/$compiler" $cflags $srcs -o "$output" """.trimIndent() } } exec { environment("SRCS", srcs) environment("CFLAGS", cflags) environment("NIXPKGS_ALLOW_UNFREE", "1") commandLine("nix-shell", "-p", "androidenv.androidPkgs.ndk-bundle", "--run", """ SDK_ROOT=${'$'}(find /nix/store -maxdepth 1 -name "*android-sdk-ndk*" -type d 2>/dev/null | head -1) NDK=${'$'}(ls -d "${'$'}SDK_ROOT/libexec/android-sdk/ndk/"* | head -1) TOOLCHAIN="${'$'}NDK/toolchains/llvm/prebuilt/linux-x86_64/bin" echo "Using NDK: ${'$'}NDK" $buildScript echo "Done building isodrive" """.trimIndent()) } } else { // Standard NDK lookup for non-NixOS val ndkDir = listOfNotNull( System.getenv("ANDROID_NDK_HOME"), System.getenv("ANDROID_NDK") ).firstOrNull { File(it).exists() } ?: throw GradleException("Android NDK not found. Set ANDROID_NDK_HOME or install NDK via SDK Manager.") val toolchain = "$ndkDir/toolchains/llvm/prebuilt/linux-x86_64/bin" if (!File(toolchain).exists()) { throw GradleException("NDK toolchain not found at: $toolchain") } compilers.forEach { (arch, compiler) -> val archDir = outputDir.resolve(arch) val output = archDir.resolve("isodrive") if (output.exists()) { println("isodrive for $arch already exists, skipping") return@forEach } archDir.mkdirs() println("Building isodrive for $arch...") exec { commandLine("sh", "-c", "$toolchain/$compiler $cflags $srcs -o $output") } } println("Done building isodrive") } } } // Hook into the build process - build isodrive before merging assets tasks.matching { it.name.startsWith("merge") && it.name.endsWith("Assets") }.configureEach { dependsOn(buildIsodrive) } android { namespace = "sh.sar.isodroid" compileSdk = 36 defaultConfig { applicationId = "sh.sar.isodroid" minSdk = 26 targetSdk = 36 versionCode = 6 versionName = "1.6" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = "11" } buildFeatures { compose = true buildConfig = true } } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) // Compose implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material.icons.extended) implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.navigation.compose) debugImplementation(libs.androidx.compose.ui.tooling) // libsu for root access implementation(libs.libsu.core) implementation(libs.libsu.service) // DataStore for preferences implementation(libs.androidx.datastore.preferences) // Coil for image loading (SVG support) implementation(libs.coil.compose) implementation(libs.coil.svg) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) }