Compare commits

...
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 7f9bc65965 fix(android): disable AGP new-DSL mode to fix signReleaseBundle NullPointerException
AGP 9+ defaults to new-DSL-only mode, which causes a NullPointerException
when conditionally creating signingConfigs inside the android {} block.
Setting android.newDsl=false restores the previous evaluation behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:34:51 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 e76c536e0e fix: pass keystore password as Dagger secret to Android builds
ANDROID_KEYSTORE_PASSWORD was set in the CI runner environment but never
forwarded into the Dagger container, so System.getenv() returned null
inside the Flutter build, causing a NullPointerException in
FinalizeBundleTask when signing the release bundle.

- Add keystorePassword *dagger.Secret param to BuildAndroidRelease,
  BuildAndroidApk, PublishAndroid, and DeployApk in the Dagger module
- Pass ANDROID_KEYSTORE_PASSWORD via WithSecretVariable to the build container
- Update ci.yml to supply env:ANDROID_KEYSTORE_PASSWORD to both
  publish-android and deploy-apk dagger calls
- Refactor build.gradle.kts to conditionally create the signing config
  only when both the keystore file and password are available, avoiding
  null values being passed to the signing config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 11:18:00 +02:00
4 changed files with 24 additions and 18 deletions
+2 -2
View File
@@ -97,7 +97,7 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }} PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }}
run: | run: |
nix develop --no-warn-dirty --command dagger call --progress=plain -m ci publish-android --source . --play-store-config env:PLAY_STORE_CONFIG_JSON nix develop --no-warn-dirty --command dagger call --progress=plain -m ci publish-android --source . --play-store-config env:PLAY_STORE_CONFIG_JSON --keystore-password env:ANDROID_KEYSTORE_PASSWORD
- name: Build & Deploy APK to server - name: Build & Deploy APK to server
continue-on-error: true continue-on-error: true
@@ -108,7 +108,7 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
run: | run: |
HASH=$(git rev-parse --short HEAD) HASH=$(git rev-parse --short HEAD)
nix develop --no-warn-dirty --command dagger call --progress=plain -m ci deploy-apk --source . --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH" nix develop --no-warn-dirty --command dagger call --progress=plain -m ci deploy-apk --source . --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH" --keystore-password env:ANDROID_KEYSTORE_PASSWORD
publish-website: publish-website:
name: Publish Website Build History name: Publish Website Build History
+13 -12
View File
@@ -20,15 +20,18 @@ android {
jvmTarget = JavaVersion.VERSION_17.toString() jvmTarget = JavaVersion.VERSION_17.toString()
} }
signingConfigs { val keystoreFile = file("upload-keystore.jks")
create("release") { val keystorePass: String? = System.getenv("ANDROID_KEYSTORE_PASSWORD")
// Hardcoded alias matching t.sh val hasKeystore = keystoreFile.exists() && keystorePass != null
keyAlias = "upload"
// Use the same password for both key and keystore if (hasKeystore) {
val pass = System.getenv("ANDROID_KEYSTORE_PASSWORD") signingConfigs {
storePassword = pass create("release") {
keyPassword = pass keyAlias = "upload"
storeFile = file("upload-keystore.jks") storePassword = keystorePass
keyPassword = keystorePass
storeFile = keystoreFile
}
} }
} }
@@ -44,9 +47,7 @@ android {
buildTypes { buildTypes {
release { release {
// Use the signing config defined above for release builds. signingConfig = if (hasKeystore) {
// If the keystore file exists (e.g. in CI or manually placed), sign it.
signingConfig = if (signingConfigs.getByName("release").storeFile?.exists() == true) {
signingConfigs.getByName("release") signingConfigs.getByName("release")
} else { } else {
signingConfigs.getByName("debug") signingConfigs.getByName("debug")
+1
View File
@@ -1,2 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true
android.newDsl=false
+8 -4
View File
@@ -262,8 +262,9 @@ func (m *Ci) DeployLinux(
} }
// Build and return the Android APK // Build and return the Android APK
func (m *Ci) BuildAndroidApk(source *dagger.Directory) *dagger.File { func (m *Ci) BuildAndroidApk(source *dagger.Directory, keystorePassword *dagger.Secret) *dagger.File {
return m.Setup(source). return m.Setup(source).
WithSecretVariable("ANDROID_KEYSTORE_PASSWORD", keystorePassword).
WithExec([]string{"flutter", "build", "apk", "--release"}). WithExec([]string{"flutter", "build", "apk", "--release"}).
File("build/app/outputs/flutter-apk/app-release.apk") File("build/app/outputs/flutter-apk/app-release.apk")
} }
@@ -276,9 +277,10 @@ func (m *Ci) DeployApk(
sshUser string, sshUser string,
sshHost string, sshHost string,
commitHash string, commitHash string,
keystorePassword *dagger.Secret,
) (string, error) { ) (string, error) {
// 1. Build the APK // 1. Build the APK
apk := m.BuildAndroidApk(source) apk := m.BuildAndroidApk(source, keystorePassword)
// 2. Deploy // 2. Deploy
datePath := time.Now().Format("2006/01/02") datePath := time.Now().Format("2006/01/02")
@@ -293,8 +295,9 @@ func (m *Ci) DeployApk(
} }
// Build and return the Android App Bundle (AAB) // Build and return the Android App Bundle (AAB)
func (m *Ci) BuildAndroidRelease(source *dagger.Directory) *dagger.File { func (m *Ci) BuildAndroidRelease(source *dagger.Directory, keystorePassword *dagger.Secret) *dagger.File {
return m.Setup(source). return m.Setup(source).
WithSecretVariable("ANDROID_KEYSTORE_PASSWORD", keystorePassword).
WithExec([]string{"flutter", "build", "appbundle", "--release"}). WithExec([]string{"flutter", "build", "appbundle", "--release"}).
File("build/app/outputs/bundle/release/app-release.aab") File("build/app/outputs/bundle/release/app-release.aab")
} }
@@ -304,9 +307,10 @@ func (m *Ci) PublishAndroid(
ctx context.Context, ctx context.Context,
source *dagger.Directory, source *dagger.Directory,
playStoreConfig *dagger.Secret, playStoreConfig *dagger.Secret,
keystorePassword *dagger.Secret,
) (string, error) { ) (string, error) {
// 1. Build the AAB // 1. Build the AAB
aab := m.BuildAndroidRelease(source) aab := m.BuildAndroidRelease(source, keystorePassword)
// 2. Prepare script source // 2. Prepare script source
scriptSource := source.Filter(dagger.DirectoryFilterOpts{ scriptSource := source.Filter(dagger.DirectoryFilterOpts{