Bundle Geode with Play Store build (#88)

* add gradle task to download geode binaries

* disable automatic update on play build

* get download url from release api

* fix incorrect priorities between external library and prebundled

* bundle geode resources
this also fixes the release api fetch as i never tested it until now, oops

* add message for loading from files dir

* raise minimum gd version with bundled geode

* clear old resources prior to copying
This commit is contained in:
Chloe authored and GitHub committed 2026-09-07 17:19:12 -07:00
1 parent 9e86bbe355
commit 3194dff7ff
14 files changed
+455 -83

No files matched your search

+247
View File
@@ -1,3 +1,9 @@
import groovy.json.JsonSlurper
import java.io.IOException
import java.net.URI
import java.security.MessageDigest
import java.util.zip.ZipInputStream
kotlin {
compilerOptions {
extraWarnings.set(true)
@@ -10,6 +16,232 @@ plugins {
id("org.jetbrains.kotlin.plugin.compose")
}
val currentGeodeVersion = "5.10.1"
abstract class DownloadVersionMetadataTask : DefaultTask() {
@get:Input
abstract val geodeVersion: Property<String>
@get:OutputFile
abstract val targetFile: RegularFileProperty
@TaskAction
fun execute() {
val downloadGeodeVersion = geodeVersion.get()
val destinationFile = targetFile.get().asFile
val releaseUrl = "https://api.geode-sdk.org/v1/loader/versions/$downloadGeodeVersion"
URI.create(releaseUrl).toURL().openStream().use { response ->
destinationFile.outputStream().use { destination ->
response.copyTo(destination)
}
}
}
}
abstract class DownloadGeodeFileTask : DefaultTask() {
@get:Input
abstract val platform: Property<String>
@get:InputFile
abstract val metadata: RegularFileProperty
private fun downloadAndVerifyFile(filename: String, url: String, hash: String?): File {
logger.info("Downloading $filename from $url")
val tempFile = File(temporaryDir, filename)
URI.create(url).toURL().openStream().use { response ->
tempFile.outputStream().use { output ->
response.copyTo(output)
}
}
if (hash != null) {
val downloadedHash = MessageDigest.getInstance("SHA-256")
.digest(tempFile.readBytes())
.toHexString()
if (downloadedHash != hash) {
throw GradleException("Hash check failed for file $filename, expected $hash but found $downloadedHash.")
}
}
return tempFile
}
fun downloadZip(fallbackUrl: String?): File {
val downloadPlatform = platform.get()
val metadataFile = metadata.get().asFile
val json = JsonSlurper().parse(metadataFile) as Map<*, *>
val payload = json["payload"] as? Map<*, *>
val downloads = payload?.get("downloads") as? Map<*, *>
val platformInfo = downloads?.get(downloadPlatform) as? Map<*, *>
val downloadUrl = platformInfo?.get("url") as? String ?: fallbackUrl
if (downloadUrl == null) {
throw GradleException("Failed to fetch download url for platform $platform.")
}
val expectedHash = platformInfo?.get("hash") as? String
return downloadAndVerifyFile("$downloadPlatform.zip", downloadUrl, expectedHash)
}
}
abstract class DownloadAndUnzipGeodeTask : DownloadGeodeFileTask() {
@get:Input
abstract val geodeVersion: Property<String>
@get:OutputFile
abstract val targetFile: RegularFileProperty
@TaskAction
fun execute() {
val downloadGeodeVersion = geodeVersion.get()
val downloadPlatform = platform.get()
val destinationFile = targetFile.get().asFile
val tempOutput = this.downloadZip("https://github.com/geode-sdk/geode/releases/download/v$downloadGeodeVersion/geode-v$downloadGeodeVersion-$downloadPlatform.zip")
val expectedFile = "Geode.$downloadPlatform.so"
ZipInputStream(tempOutput.inputStream()).use { zip ->
var entry = zip.nextEntry
while (entry != null) {
if (entry.name == expectedFile) {
destinationFile.outputStream().use {
zip.copyTo(it)
}
return@execute
}
entry = zip.nextEntry
}
throw GradleException("Could not find Geode release.")
}
}
}
abstract class DownloadAndUnzipGeodeResourcesTask : DownloadGeodeFileTask() {
init {
platform.set("resources")
}
@get:Input
abstract val geodeVersion: Property<String>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@TaskAction
fun execute() {
val downloadGeodeVersion = geodeVersion.get()
val tempOutput = this.downloadZip("https://github.com/geode-sdk/geode/releases/download/v$downloadGeodeVersion/resources.zip")
val baseOutput = outputDir.get().asFile
if (baseOutput.exists()) baseOutput.deleteRecursively()
val output = File(baseOutput, "geode.loader")
output.mkdirs()
val canonicalOutput = output.canonicalPath
ZipInputStream(tempOutput.inputStream()).use { zipStream ->
var entry = zipStream.nextEntry
while (entry != null) {
val destination = File(output, entry.name)
if (!destination.canonicalPath.startsWith(canonicalOutput)) {
throw GradleException("attempted copy to outside of output directory: ${entry.name}")
}
if (destination.isDirectory) {
destination.mkdirs()
} else {
destination.parentFile?.mkdirs()
destination.outputStream().use { destinationStream ->
zipStream.copyTo(destinationStream)
}
}
entry = zipStream.nextEntry
}
}
}
}
val fetchGeodeMetadataTask = tasks.register<DownloadVersionMetadataTask>("fetchGeodeMetadata") {
description = "Fetches and saves a Geode version's metadata"
inputs.property("geodeVersion", currentGeodeVersion)
geodeVersion = currentGeodeVersion
targetFile = temporaryDir.resolve("metadata.json")
}
val downloadGeode32Task = tasks.register<DownloadAndUnzipGeodeTask>("downloadGeode32Binary") {
description = "Downloads the 32bit Geode release archive"
inputs.property("geodeVersion", currentGeodeVersion)
geodeVersion = currentGeodeVersion
platform = "android32"
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
targetFile = temporaryDir.resolve("libgeode.so")
}
val downloadGeode64Task = tasks.register<DownloadAndUnzipGeodeTask>("downloadGeode64Binary") {
description = "Downloads the 64bit Geode release archive"
inputs.property("geodeVersion", currentGeodeVersion)
geodeVersion = currentGeodeVersion
platform = "android64"
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
targetFile = temporaryDir.resolve("libgeode.so")
}
val downloadGeodeResourcesTask = tasks.register<DownloadAndUnzipGeodeResourcesTask>("downloadGeodeResources") {
description = "Downloads Geode resources"
inputs.property("geodeVersion", currentGeodeVersion)
geodeVersion = currentGeodeVersion
metadata = fetchGeodeMetadataTask.flatMap { it.targetFile }
outputDir = layout.buildDirectory.dir("geode-resources")
}
abstract class MergeGeodeFilesTask : DefaultTask() {
@get:InputFile
abstract val binary32: RegularFileProperty
@get:InputFile
abstract val binary64: RegularFileProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@TaskAction
fun execute() {
val out = outputDir.get().asFile
out.deleteRecursively()
out.mkdirs()
binary32.get().asFile.copyTo(outputDir.file("armeabi-v7a/libgeode.so").get().asFile)
binary64.get().asFile.copyTo(outputDir.file("arm64-v8a/libgeode.so").get().asFile)
}
}
val fullDownloadTask = tasks.register<MergeGeodeFilesTask>("downloadGeodeFull") {
description = "Downloads both Geode releases"
binary32 = downloadGeode32Task.flatMap { it.targetFile }
binary64 = downloadGeode64Task.flatMap { it.targetFile }
outputDir = layout.buildDirectory.dir("geode-prebuilt")
}
android {
compileSdk = 37
@@ -54,12 +286,14 @@ android {
dimension = "distribution"
isDefault = true
buildConfigField("boolean", "GOOGLE_PLAY_BUILD", "false")
buildConfigField("String", "PREBUNDLED_GEODE", "null")
}
create("googlePlay") {
dimension = "distribution"
applicationId = "com.geode.launcher.play"
buildConfigField("boolean", "GOOGLE_PLAY_BUILD", "true")
buildConfigField("String", "PREBUNDLED_GEODE", "\"$currentGeodeVersion\"")
}
}
@@ -100,6 +334,19 @@ android {
ndkVersion = "29.0.14206865"
}
androidComponents {
onVariants(selector().withFlavor("distribution" to "googlePlay")) { variant ->
variant.sources.jniLibs?.addGeneratedSourceDirectory(
fullDownloadTask,
MergeGeodeFilesTask::outputDir
)
variant.sources.assets?.addGeneratedSourceDirectory(
downloadGeodeResourcesTask,
DownloadAndUnzipGeodeResourcesTask::outputDir
)
}
}
dependencies {
implementation (platform("androidx.compose:compose-bom:2026.08.00"))
implementation ("androidx.core:core-ktx:1.19.0")