Better Android signing & Google Play bundle building (#1670)

This commit is contained in:
lucas lelievre
2025-12-11 19:56:07 +01:00
committed by GitHub
2 changed files with 81 additions and 25 deletions

View File

@@ -118,21 +118,15 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: cd gui && pnpm run build
- name: Decode keystore secret to file
env:
ANDROID_STORE_FILE: ${{ secrets.ANDROID_STORE_FILE }}
run: |
mkdir -p server/android/secrets/
echo $ANDROID_STORE_FILE | base64 --decode > server/android/secrets/keystore.jks
- name: Build with Gradle
run: ./gradlew :server:android:build
env:
ANDROID_STORE_FILE: ${{ secrets.ANDROID_STORE_FILE }}
ANDROID_STORE_PASSWD: ${{ secrets.ANDROID_STORE_PASSWD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWD: ${{ secrets.ANDROID_KEY_PASSWD }}
- name: Upload the Android Build Artifact
- name: Upload the Android build artifact
uses: actions/upload-artifact@v5
with:
# Artifact name
@@ -154,6 +148,24 @@ jobs:
files: |
./SlimeVR-android.apk
- name: Build Google Play release bundle
if: startsWith(github.ref, 'refs/tags/')
run: ./gradlew :server:android:bundleRelease
env:
ANDROID_STORE_FILE: ${{ secrets.ANDROID_GPLAY_STORE_FILE }}
ANDROID_STORE_PASSWD: ${{ secrets.ANDROID_GPLAY_STORE_PASSWD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_GPLAY_KEY_ALIAS }}
ANDROID_KEY_PASSWD: ${{ secrets.ANDROID_GPLAY_KEY_PASSWD }}
- name: Upload the Google Play artifact
uses: actions/upload-artifact@v5
if: startsWith(github.ref, 'refs/tags/')
with:
# Artifact name
name: 'SlimeVR-Android-GPDev' # optional, default is artifact
# A file, directory or wildcard pattern that describes what to upload
path: server/android/build/outputs/bundle/release/*
bundle-linux:
strategy:
matrix:

View File

@@ -5,8 +5,11 @@
* For more details take a look at the Java Libraries chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.3/userguide/java_library_plugin.html
*/
import com.android.build.gradle.internal.tasks.BaseTask
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.util.Base64
plugins {
kotlin("android")
@@ -28,7 +31,7 @@ java {
}
}
tasks.register<Copy>("copyGuiAssets") {
val copyGuiAssets = tasks.register<Copy>("copyGuiAssets") {
val target = layout.projectDirectory.dir("src/main/assets/web-gui")
delete(target)
from(rootProject.layout.projectDirectory.dir("gui/dist"))
@@ -37,17 +40,45 @@ tasks.register<Copy>("copyGuiAssets") {
throw GradleException("You need to run \"pnpm run build\" on the gui folder first!")
}
}
tasks.register("validateKeyStore") {
val storeFile = android.buildTypes.getByName("release").signingConfig?.storeFile
// Only warn for now since this is run even when irrelevant
if (storeFile?.isFile != true) {
logger.error("Android KeyStore file does not exist or is not a file: ${storeFile?.path}")
} else if (storeFile.length() <= 0) {
logger.error("Android KeyStore file is empty: ${storeFile.path}")
tasks.preBuild {
dependsOn(copyGuiAssets)
}
// Set up signing pre/post tasks
val preSign = tasks.register("preSign") {
dependsOn(writeTempKeyStore)
}
val postSign = tasks.register("postSign") {
finalizedBy(deleteTempKeyStore)
}
tasks.withType<BaseTask> {
dependsOn(preSign)
finalizedBy(postSign)
}
// Handle GitHub secret Android KeyStore files
val envKeyStore: String? = System.getenv("ANDROID_STORE_FILE")?.takeIf { it.isNotBlank() }
val tempKeyStore = project.layout.buildDirectory.file("tmp/keystore.tmp.jks").get().asFile
val writeTempKeyStore = tasks.register("writeTempKeyStore") {
if (envKeyStore != null) {
doLast {
tempKeyStore.apply {
ensureParentDirsCreated()
tempKeyStore.writeBytes(Base64.getDecoder().decode(envKeyStore))
tempKeyStore.deleteOnExit()
}
}
finalizedBy(deleteTempKeyStore)
} else {
enabled = false
}
}
tasks.preBuild {
dependsOn(":server:android:copyGuiAssets", ":server:android:validateKeyStore")
val deleteTempKeyStore = tasks.register<Delete>("deleteTempKeyStore") {
if (envKeyStore != null) {
delete(tempKeyStore)
} else {
enabled = false
}
}
tasks.withType<KotlinCompile> {
@@ -136,17 +167,30 @@ android {
// Defines a user-friendly version name for your app.
versionName = extra["gitVersionName"] as? String ?: "v0.0.0"
logger.lifecycle("Configured for SlimeVR Android version $versionName ($versionCode)")
logger.lifecycle("i: Configured for SlimeVR Android version \"$versionName\" ($versionCode).")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
create("release") {
storeFile = file("./secrets/keystore.jks")
storePassword = System.getenv("ANDROID_STORE_PASSWD")
keyAlias = System.getenv("ANDROID_KEY_ALIAS")
keyPassword = System.getenv("ANDROID_KEY_PASSWD")
val inputKeyStore: File? = if (envKeyStore != null) {
logger.lifecycle("i: \"ANDROID_STORE_FILE\" environment variable found, using for signing config.")
tempKeyStore
} else {
file("secrets/keystore.jks").takeIf { it.canRead() && it.length() > 0 }
}
if (inputKeyStore != null) {
logger.info("i: Configuring signing for Android KeyStore file: \"${inputKeyStore.path}\".")
create("release") {
storeFile = inputKeyStore
storePassword = System.getenv("ANDROID_STORE_PASSWD")
keyAlias = System.getenv("ANDROID_KEY_ALIAS") ?: "key0"
keyPassword = System.getenv("ANDROID_KEY_PASSWD")
}
} else {
logger.warn("w: Android KeyStore file is not valid or not found, skipping signing.")
}
}
@@ -168,7 +212,7 @@ android {
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
signingConfig = signingConfigs.getByName("release")
signingConfig = signingConfigs.findByName("release")
}
}