Skip to content

Commit 781f4d4

Browse files
authored
Better Android signing & Google Play bundle building (#1670)
2 parents 5a42426 + 44643f2 commit 781f4d4

File tree

2 files changed

+81
-25
lines changed

2 files changed

+81
-25
lines changed

.github/workflows/gradle.yaml

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,15 @@ jobs:
118118
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
119119
run: cd gui && pnpm run build
120120

121-
- name: Decode keystore secret to file
122-
env:
123-
ANDROID_STORE_FILE: ${{ secrets.ANDROID_STORE_FILE }}
124-
run: |
125-
mkdir -p server/android/secrets/
126-
echo $ANDROID_STORE_FILE | base64 --decode > server/android/secrets/keystore.jks
127-
128121
- name: Build with Gradle
129122
run: ./gradlew :server:android:build
130123
env:
124+
ANDROID_STORE_FILE: ${{ secrets.ANDROID_STORE_FILE }}
131125
ANDROID_STORE_PASSWD: ${{ secrets.ANDROID_STORE_PASSWD }}
132126
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
133127
ANDROID_KEY_PASSWD: ${{ secrets.ANDROID_KEY_PASSWD }}
134128

135-
- name: Upload the Android Build Artifact
129+
- name: Upload the Android build artifact
136130
uses: actions/upload-artifact@v5
137131
with:
138132
# Artifact name
@@ -154,6 +148,24 @@ jobs:
154148
files: |
155149
./SlimeVR-android.apk
156150
151+
- name: Build Google Play release bundle
152+
if: startsWith(github.ref, 'refs/tags/')
153+
run: ./gradlew :server:android:bundleRelease
154+
env:
155+
ANDROID_STORE_FILE: ${{ secrets.ANDROID_GPLAY_STORE_FILE }}
156+
ANDROID_STORE_PASSWD: ${{ secrets.ANDROID_GPLAY_STORE_PASSWD }}
157+
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_GPLAY_KEY_ALIAS }}
158+
ANDROID_KEY_PASSWD: ${{ secrets.ANDROID_GPLAY_KEY_PASSWD }}
159+
160+
- name: Upload the Google Play artifact
161+
uses: actions/upload-artifact@v5
162+
if: startsWith(github.ref, 'refs/tags/')
163+
with:
164+
# Artifact name
165+
name: 'SlimeVR-Android-GPDev' # optional, default is artifact
166+
# A file, directory or wildcard pattern that describes what to upload
167+
path: server/android/build/outputs/bundle/release/*
168+
157169
bundle-linux:
158170
strategy:
159171
matrix:

server/android/build.gradle.kts

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
* For more details take a look at the Java Libraries chapter in the Gradle
66
* User Manual available at https://docs.gradle.org/6.3/userguide/java_library_plugin.html
77
*/
8+
import com.android.build.gradle.internal.tasks.BaseTask
89
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
10+
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
911
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
12+
import java.util.Base64
1013

1114
plugins {
1215
kotlin("android")
@@ -28,7 +31,7 @@ java {
2831
}
2932
}
3033

31-
tasks.register<Copy>("copyGuiAssets") {
34+
val copyGuiAssets = tasks.register<Copy>("copyGuiAssets") {
3235
val target = layout.projectDirectory.dir("src/main/assets/web-gui")
3336
delete(target)
3437
from(rootProject.layout.projectDirectory.dir("gui/dist"))
@@ -37,17 +40,45 @@ tasks.register<Copy>("copyGuiAssets") {
3740
throw GradleException("You need to run \"pnpm run build\" on the gui folder first!")
3841
}
3942
}
40-
tasks.register("validateKeyStore") {
41-
val storeFile = android.buildTypes.getByName("release").signingConfig?.storeFile
42-
// Only warn for now since this is run even when irrelevant
43-
if (storeFile?.isFile != true) {
44-
logger.error("Android KeyStore file does not exist or is not a file: ${storeFile?.path}")
45-
} else if (storeFile.length() <= 0) {
46-
logger.error("Android KeyStore file is empty: ${storeFile.path}")
43+
tasks.preBuild {
44+
dependsOn(copyGuiAssets)
45+
}
46+
47+
// Set up signing pre/post tasks
48+
val preSign = tasks.register("preSign") {
49+
dependsOn(writeTempKeyStore)
50+
}
51+
val postSign = tasks.register("postSign") {
52+
finalizedBy(deleteTempKeyStore)
53+
}
54+
tasks.withType<BaseTask> {
55+
dependsOn(preSign)
56+
finalizedBy(postSign)
57+
}
58+
59+
// Handle GitHub secret Android KeyStore files
60+
val envKeyStore: String? = System.getenv("ANDROID_STORE_FILE")?.takeIf { it.isNotBlank() }
61+
val tempKeyStore = project.layout.buildDirectory.file("tmp/keystore.tmp.jks").get().asFile
62+
val writeTempKeyStore = tasks.register("writeTempKeyStore") {
63+
if (envKeyStore != null) {
64+
doLast {
65+
tempKeyStore.apply {
66+
ensureParentDirsCreated()
67+
tempKeyStore.writeBytes(Base64.getDecoder().decode(envKeyStore))
68+
tempKeyStore.deleteOnExit()
69+
}
70+
}
71+
finalizedBy(deleteTempKeyStore)
72+
} else {
73+
enabled = false
4774
}
4875
}
49-
tasks.preBuild {
50-
dependsOn(":server:android:copyGuiAssets", ":server:android:validateKeyStore")
76+
val deleteTempKeyStore = tasks.register<Delete>("deleteTempKeyStore") {
77+
if (envKeyStore != null) {
78+
delete(tempKeyStore)
79+
} else {
80+
enabled = false
81+
}
5182
}
5283

5384
tasks.withType<KotlinCompile> {
@@ -136,17 +167,30 @@ android {
136167
// Defines a user-friendly version name for your app.
137168
versionName = extra["gitVersionName"] as? String ?: "v0.0.0"
138169

139-
logger.lifecycle("Configured for SlimeVR Android version $versionName ($versionCode)")
170+
logger.lifecycle("i: Configured for SlimeVR Android version \"$versionName\" ($versionCode).")
140171

141172
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
142173
}
143174

144175
signingConfigs {
145-
create("release") {
146-
storeFile = file("./secrets/keystore.jks")
147-
storePassword = System.getenv("ANDROID_STORE_PASSWD")
148-
keyAlias = System.getenv("ANDROID_KEY_ALIAS")
149-
keyPassword = System.getenv("ANDROID_KEY_PASSWD")
176+
val inputKeyStore: File? = if (envKeyStore != null) {
177+
logger.lifecycle("i: \"ANDROID_STORE_FILE\" environment variable found, using for signing config.")
178+
tempKeyStore
179+
} else {
180+
file("secrets/keystore.jks").takeIf { it.canRead() && it.length() > 0 }
181+
}
182+
183+
if (inputKeyStore != null) {
184+
logger.info("i: Configuring signing for Android KeyStore file: \"${inputKeyStore.path}\".")
185+
186+
create("release") {
187+
storeFile = inputKeyStore
188+
storePassword = System.getenv("ANDROID_STORE_PASSWD")
189+
keyAlias = System.getenv("ANDROID_KEY_ALIAS") ?: "key0"
190+
keyPassword = System.getenv("ANDROID_KEY_PASSWD")
191+
}
192+
} else {
193+
logger.warn("w: Android KeyStore file is not valid or not found, skipping signing.")
150194
}
151195
}
152196

@@ -168,7 +212,7 @@ android {
168212
getDefaultProguardFile("proguard-android-optimize.txt"),
169213
"proguard-rules.pro",
170214
)
171-
signingConfig = signingConfigs.getByName("release")
215+
signingConfig = signingConfigs.findByName("release")
172216
}
173217
}
174218

0 commit comments

Comments
 (0)