diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 7d455ecd..107584d5 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -144,28 +144,26 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build app module (release) + - name: Build app module (debug) if: env.BUILD_APP == 'true' - run: ./gradlew :app:assembleRelease + run: ./gradlew :app:assembleDebug - - name: Build humanoperator module (release) + - name: Build humanoperator module (debug) if: env.BUILD_HUMANOPERATOR == 'true' - run: ./gradlew :humanoperator:assembleRelease + run: ./gradlew :humanoperator:assembleDebug - name: Upload app APK if: env.BUILD_APP == 'true' uses: actions/upload-artifact@v4 with: - name: app-release-unsigned - path: app/build/outputs/apk/release/app-release-unsigned.apk - + name: app-debug + path: app/build/outputs/apk/debug/app-debug.apk - name: Upload humanoperator APK if: env.BUILD_HUMANOPERATOR == 'true' uses: actions/upload-artifact@v4 with: - name: humanoperator-release-unsigned - path: humanoperator/build/outputs/apk/release/humanoperator-release-unsigned.apk - + name: humanoperator-debug + path: humanoperator/build/outputs/apk/debug/humanoperator-debug.apk - name: Build summary run: | echo "### Build Summary" >> $GITHUB_STEP_SUMMARY diff --git a/README.md b/README.md index 426e7c6d..1c95dbf1 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,7 @@ Free models accessible via an API can be found [here](https://github.com/cheahjs If you in your Google account identified as under 18, you need an adult account because Google is (unreasonably) denying you the API key. Preview models will eventually be removed by Google and unfortunately won't be redirected to finished equivalents. If this happens, please change the API in the code. + +## CI Release Signing + +Dokumentation für CI-Secrets und Verhalten bei fehlender Signing-Konfiguration: [docs/ci-signing.md](docs/ci-signing.md) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8e1528cb..6a69feb4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,3 @@ - plugins { id("com.android.application") id("org.jetbrains.kotlin.android") @@ -14,6 +13,23 @@ System.getenv("SCREENOPERATOR_BUILD_DIR")?.takeIf { it.isNotBlank() }?.let { cus layout.buildDirectory = file(customBuildDir) } +val releaseSigningEnv = mapOf( + "ANDROID_KEYSTORE_PATH" to System.getenv("ANDROID_KEYSTORE_PATH"), + "ANDROID_KEY_ALIAS" to System.getenv("ANDROID_KEY_ALIAS"), + "ANDROID_KEYSTORE_PASSWORD" to System.getenv("ANDROID_KEYSTORE_PASSWORD"), + "ANDROID_KEY_PASSWORD" to System.getenv("ANDROID_KEY_PASSWORD"), +) + +val missingReleaseSigningEnv = releaseSigningEnv + .filterValues { it.isNullOrBlank() } + .keys + +val isReleaseTaskRequested = gradle.startParameter.taskNames.any { task -> + task.contains("release", ignoreCase = true) +} + +val missingReleaseSigningEnvText = missingReleaseSigningEnv.joinToString(separator = ", ") + android { namespace = "com.google.ai.sample" compileSdk = 35 @@ -34,12 +50,24 @@ android { } } + signingConfigs { + create("release") { + if (missingReleaseSigningEnv.isEmpty()) { + storeFile = file(releaseSigningEnv.getValue("ANDROID_KEYSTORE_PATH")!!) + storePassword = releaseSigningEnv.getValue("ANDROID_KEYSTORE_PASSWORD") + keyAlias = releaseSigningEnv.getValue("ANDROID_KEY_ALIAS") + keyPassword = releaseSigningEnv.getValue("ANDROID_KEY_PASSWORD") + } + } + } + buildTypes { getByName("debug") { isDebuggable = true } getByName("release") { isDebuggable = false + signingConfig = if (missingReleaseSigningEnv.isEmpty()) signingConfigs.getByName("release") else null } create("samples") { initWith(getByName("debug")) @@ -67,6 +95,13 @@ android { } } +if (isReleaseTaskRequested && missingReleaseSigningEnv.isNotEmpty()) { + error( + "Release signing env vars missing for module :app: ${missingReleaseSigningEnvText}. " + + "Set ANDROID_KEYSTORE_PATH, ANDROID_KEY_ALIAS, ANDROID_KEYSTORE_PASSWORD and ANDROID_KEY_PASSWORD." + ) +} + dependencies { constraints { implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.20") diff --git a/docs/ci-signing.md b/docs/ci-signing.md new file mode 100644 index 00000000..1758ae5c --- /dev/null +++ b/docs/ci-signing.md @@ -0,0 +1,19 @@ +# CI Signing für Release-Builds + +Die Module `app` und `humanoperator` erwarten für Release-Tasks eine Signing-Konfiguration über Umgebungsvariablen. + +## Benötigte CI-Secrets + +- `ANDROID_KEYSTORE_PATH`: Absoluter oder relativ zum Projekt auflösbarer Pfad zur Keystore-Datei. +- `ANDROID_KEY_ALIAS`: Alias des Release-Keys. +- `ANDROID_KEYSTORE_PASSWORD`: Passwort der Keystore-Datei. +- `ANDROID_KEY_PASSWORD`: Passwort des Keys. + +## Verhalten bei fehlenden Variablen + +- Für **Release-Tasks** (Taskname enthält `release`) wird der Build mit einer klaren Fehlermeldung abgebrochen, wenn eine der Variablen fehlt. +- Für Nicht-Release-Tasks bleibt die Signing-Config ungesetzt, damit lokale Debug-Builds weiter funktionieren. + +## Wichtiger Hinweis zu Firebase + +`google-services.json` bleibt unverändert versioniert und ist **nicht** Teil der Signing-Logik. diff --git a/humanoperator/build.gradle.kts b/humanoperator/build.gradle.kts index 9091bc13..40ee7026 100644 --- a/humanoperator/build.gradle.kts +++ b/humanoperator/build.gradle.kts @@ -4,6 +4,23 @@ plugins { id("com.google.gms.google-services") } +val releaseSigningEnv = mapOf( + "ANDROID_KEYSTORE_PATH" to System.getenv("ANDROID_KEYSTORE_PATH"), + "ANDROID_KEY_ALIAS" to System.getenv("ANDROID_KEY_ALIAS"), + "ANDROID_KEYSTORE_PASSWORD" to System.getenv("ANDROID_KEYSTORE_PASSWORD"), + "ANDROID_KEY_PASSWORD" to System.getenv("ANDROID_KEY_PASSWORD"), +) + +val missingReleaseSigningEnv = releaseSigningEnv + .filterValues { it.isNullOrBlank() } + .keys + +val isReleaseTaskRequested = gradle.startParameter.taskNames.any { task -> + task.contains("release", ignoreCase = true) +} + +val missingReleaseSigningEnvText = missingReleaseSigningEnv.joinToString(separator = ", ") + android { namespace = "com.screenoperator.humanoperator" compileSdk = 35 @@ -21,10 +38,22 @@ android { } } + signingConfigs { + create("release") { + if (missingReleaseSigningEnv.isEmpty()) { + storeFile = file(releaseSigningEnv.getValue("ANDROID_KEYSTORE_PATH")!!) + storePassword = releaseSigningEnv.getValue("ANDROID_KEYSTORE_PASSWORD") + keyAlias = releaseSigningEnv.getValue("ANDROID_KEY_ALIAS") + keyPassword = releaseSigningEnv.getValue("ANDROID_KEY_PASSWORD") + } + } + } + buildTypes { release { isMinifyEnabled = false proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + signingConfig = if (missingReleaseSigningEnv.isEmpty()) signingConfigs.getByName("release") else null } } @@ -43,6 +72,13 @@ android { } } +if (isReleaseTaskRequested && missingReleaseSigningEnv.isNotEmpty()) { + error( + "Release signing env vars missing for module :humanoperator: ${missingReleaseSigningEnvText}. " + + "Set ANDROID_KEYSTORE_PATH, ANDROID_KEY_ALIAS, ANDROID_KEYSTORE_PASSWORD and ANDROID_KEY_PASSWORD." + ) +} + dependencies { implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")