import java.util.Properties plugins { alias(libs.plugins.android.application) } android { namespace = "de.ladkau.schwertundmagieonpebblecompanionapp" compileSdk { version = release(36) { minorApiLevel = 1 } } ndkVersion = "30.0.14904198" defaultConfig { applicationId = "de.ladkau.schwertundmagieonpebblecompanionapp" minSdk = 24 targetSdk = 36 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" ndk { abiFilters += listOf("arm64-v8a", "x86_64") } externalNativeBuild { cmake { arguments("-DANDROID_STL=c++_shared") // HAVE_VICE_SRC is detected automatically by CMakeLists.txt // based on whether vice-libs//libvice.a exists. } } } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } externalNativeBuild { cmake { path = file("src/main/jni/CMakeLists.txt") version = "3.22.1" } } } // --------------------------------------------------------------------------- // VICE cross-compilation task // // Runs automatically before any CMake build step. // Skipped entirely if vice-libs//libvice.a already exists. // The tarball at /res/vice-3.8.tar.gz is unpacked by build_vice.sh. // --------------------------------------------------------------------------- val viceLibArm = layout.projectDirectory.file("src/main/jni/vice-libs/arm64-v8a/libvice.a") val viceLibX86 = layout.projectDirectory.file("src/main/jni/vice-libs/x86_64/libvice.a") val viceTarball = layout.projectDirectory.file("../res/vice-3.8.tar.gz") tasks.register("buildVice") { group = "build" description = "Cross-compile VICE 3.8 headless for Android (ARM64 + x86_64)" inputs.file(viceTarball) outputs.file(viceLibArm) outputs.file(viceLibX86) doLast { if (viceLibArm.asFile.exists() && viceLibX86.asFile.exists()) { logger.lifecycle("VICE already built — skipping") return@doLast } val localProps = Properties().apply { rootProject.file("local.properties").takeIf { it.exists() } ?.reader()?.use { load(it) } } val sdkDir = localProps.getProperty("sdk.dir") ?: System.getenv("ANDROID_HOME") ?: "" val ndkPath = localProps.getProperty("ndk.dir") ?: System.getenv("ANDROID_NDK_HOME") ?: System.getenv("ANDROID_NDK_ROOT") ?: System.getenv("NDK") ?: sdkDir.takeIf { it.isNotEmpty() }?.let { sdk -> // NDK side-by-side: pick the first installed version file("$sdk/ndk").takeIf { it.isDirectory } ?.listFiles()?.sorted()?.lastOrNull()?.absolutePath ?: "$sdk/ndk-bundle".takeIf { file("$sdk/ndk-bundle").isDirectory } } ?: throw GradleException( "Android NDK not found.\n" + "Install via: Android Studio → SDK Manager → SDK Tools → NDK (Side by side)" ) logger.lifecycle("Building VICE with NDK at $ndkPath …") val proc = ProcessBuilder("bash", "build_vice.sh") .directory(layout.projectDirectory.dir("src/main/jni").asFile) .redirectErrorStream(true) .also { it.environment()["NDK"] = ndkPath } .start() proc.inputStream.bufferedReader().forEachLine { logger.lifecycle(it) } val exit = proc.waitFor() if (exit != 0) throw GradleException("build_vice.sh failed (exit $exit)") // Touch CMakeLists.txt so CMake re-evaluates the EXISTS check on the // next build and picks up the newly created libvice.a. layout.projectDirectory.file("src/main/jni/CMakeLists.txt") .asFile.setLastModified(System.currentTimeMillis()) logger.lifecycle("VICE build complete — CMakeLists.txt touched for re-evaluation") } } // --------------------------------------------------------------------------- // nibtools cross-compilation task // // Builds nibconv (NIB/NBZ -> G64/D64 disk image conversion, used by the // "Download Disks" feature) the same way buildVice builds VICE above. // Skipped entirely if nibtools-libs//libnibtools.a already exists. // The tarball at /res/nibtools-91344e0ee3.tar.gz is unpacked by // build_nibtools.sh. // --------------------------------------------------------------------------- val nibtoolsLibArm = layout.projectDirectory.file("src/main/jni/nibtools-libs/arm64-v8a/libnibtools.a") val nibtoolsLibX86 = layout.projectDirectory.file("src/main/jni/nibtools-libs/x86_64/libnibtools.a") val nibtoolsTarball = layout.projectDirectory.file("../res/nibtools-91344e0ee3.tar.gz") tasks.register("buildNibtools") { group = "build" description = "Cross-compile nibtools' nibconv for Android (ARM64 + x86_64)" inputs.file(nibtoolsTarball) outputs.file(nibtoolsLibArm) outputs.file(nibtoolsLibX86) doLast { if (nibtoolsLibArm.asFile.exists() && nibtoolsLibX86.asFile.exists()) { logger.lifecycle("nibtools already built — skipping") return@doLast } val localProps = Properties().apply { rootProject.file("local.properties").takeIf { it.exists() } ?.reader()?.use { load(it) } } val sdkDir = localProps.getProperty("sdk.dir") ?: System.getenv("ANDROID_HOME") ?: "" val ndkPath = localProps.getProperty("ndk.dir") ?: System.getenv("ANDROID_NDK_HOME") ?: System.getenv("ANDROID_NDK_ROOT") ?: System.getenv("NDK") ?: sdkDir.takeIf { it.isNotEmpty() }?.let { sdk -> file("$sdk/ndk").takeIf { it.isDirectory } ?.listFiles()?.sorted()?.lastOrNull()?.absolutePath ?: "$sdk/ndk-bundle".takeIf { file("$sdk/ndk-bundle").isDirectory } } ?: throw GradleException( "Android NDK not found.\n" + "Install via: Android Studio → SDK Manager → SDK Tools → NDK (Side by side)" ) logger.lifecycle("Building nibtools with NDK at $ndkPath …") val proc = ProcessBuilder("bash", "build_nibtools.sh") .directory(layout.projectDirectory.dir("src/main/jni").asFile) .redirectErrorStream(true) .also { it.environment()["NDK"] = ndkPath } .start() proc.inputStream.bufferedReader().forEachLine { logger.lifecycle(it) } val exit = proc.waitFor() if (exit != 0) throw GradleException("build_nibtools.sh failed (exit $exit)") layout.projectDirectory.file("src/main/jni/CMakeLists.txt") .asFile.setLastModified(System.currentTimeMillis()) logger.lifecycle("nibtools build complete — CMakeLists.txt touched for re-evaluation") } } // Run buildVice and buildNibtools before any CMake configure or build step. tasks.whenTaskAdded { if (name.startsWith("configureCMake") || name.startsWith("buildCMake") || name.startsWith("externalNativeBuild")) { dependsOn("buildVice") dependsOn("buildNibtools") } } // Note: C64/1541 ROMs are intentionally NOT bundled into the APK — they are // copyrighted Commodore firmware. The app prompts the user to import their // own ROM dump at runtime instead (see MainActivity.showRomImportDialog()). // res/extract_roms.sh remains available for extracting ROMs from the VICE // tarball locally (e.g. to sideload onto a test device), but nothing in this // build copies them into assets/ or the APK. dependencies { implementation(libs.androidx.activity.ktx) implementation(libs.androidx.appcompat) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.core.ktx) implementation(libs.material) implementation(libs.nanohttpd) implementation(libs.androidx.documentfile) testImplementation(libs.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.junit) }