fix: unique build number and split build/upload steps

- Pass --build-number $(date +%s) to flutter build for both APK and AAB
  so each CI run gets a unique version code (fixes "already been used" error)
- Extract UploadToPlayStore(aab, playStoreConfig) as its own Dagger function
  so the build and upload are independently callable
- Add build-android-bundle task (exports AAB via dagger export) and
  upload-android-bundle task (calls UploadToPlayStore with the local file)
- CI deploy-playstore job now has two steps: Build Android Bundle and
  Upload to Play Store, so a failed upload can be retried without rebuilding
- deploy-apk also gets --build-number to avoid version code collisions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Güttler
2026-05-18 08:18:33 +02:00
co-authored by Claude Sonnet 4.6
parent 484a183a19
commit 8783bcf5f0
3 changed files with 43 additions and 23 deletions
+7 -2
View File
@@ -105,13 +105,18 @@ jobs:
DAGGER_CLIENT_KEY: ${{ secrets.DAGGER_CLIENT_KEY }}
run: scripts/setup_dagger_remote.sh
- name: Build & Deploy to Play Store
- name: Build Android Bundle
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
DAGGER_NO_NAG: "1"
run: task build-android-bundle
- name: Upload to Play Store
env:
PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }}
DAGGER_NO_NAG: "1"
run: task publish-android
run: task upload-android-bundle
- name: Build & Deploy APK to server
continue-on-error: true
+25 -3
View File
@@ -203,8 +203,29 @@ tasks:
cmds:
- HASH=$(git rev-parse --short HEAD) && dagger call --progress=plain -q -m ci --source=. deploy-linux --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH"
build-android-bundle:
desc: Build signed AAB via Dagger and export to build/app/outputs/bundle/release/
preconditions:
- sh: test -n "$ANDROID_KEYSTORE_BASE64"
msg: "ANDROID_KEYSTORE_BASE64 is not set"
- sh: test -n "$ANDROID_KEYSTORE_PASSWORD"
msg: "ANDROID_KEYSTORE_PASSWORD is not set"
cmds:
- mkdir -p build/app/outputs/bundle/release
- dagger call --progress=plain -m ci --source=. build-android-release --keystore-base64 env:ANDROID_KEYSTORE_BASE64 --keystore-password env:ANDROID_KEYSTORE_PASSWORD --build-number "$(date +%s)" export --path build/app/outputs/bundle/release/app-release.aab
upload-android-bundle:
desc: Upload AAB from build/ to Play Store via Dagger
preconditions:
- sh: test -n "$PLAY_STORE_CONFIG_JSON"
msg: "PLAY_STORE_CONFIG_JSON is not set"
- sh: test -f build/app/outputs/bundle/release/app-release.aab
msg: "AAB not found — run build-android-bundle first"
cmds:
- dagger call --progress=plain -q -m ci --source=. upload-to-play-store --aab build/app/outputs/bundle/release/app-release.aab --play-store-config env:PLAY_STORE_CONFIG_JSON
publish-android:
desc: Build and publish Android App Bundle to Play Store via Dagger
desc: Build signed AAB and publish to Play Store via Dagger (build + upload)
preconditions:
- sh: test -n "$PLAY_STORE_CONFIG_JSON"
msg: "PLAY_STORE_CONFIG_JSON is not set"
@@ -213,7 +234,8 @@ tasks:
- sh: test -n "$ANDROID_KEYSTORE_PASSWORD"
msg: "ANDROID_KEYSTORE_PASSWORD is not set"
cmds:
- dagger call --progress=plain -q -m ci --source=. publish-android --play-store-config env:PLAY_STORE_CONFIG_JSON --keystore-base64 env:ANDROID_KEYSTORE_BASE64 --keystore-password env:ANDROID_KEYSTORE_PASSWORD
- task: build-android-bundle
- task: upload-android-bundle
deploy-apk:
desc: Build and deploy Android APK via Dagger
@@ -225,7 +247,7 @@ tasks:
- sh: test -n "$ANDROID_KEYSTORE_PASSWORD"
msg: "ANDROID_KEYSTORE_PASSWORD is not set"
cmds:
- HASH=$(git rev-parse --short HEAD) && dagger call --progress=plain -q -m ci --source=. deploy-apk --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH" --keystore-base64 env:ANDROID_KEYSTORE_BASE64 --keystore-password env:ANDROID_KEYSTORE_PASSWORD
- HASH=$(git rev-parse --short HEAD) && dagger call --progress=plain -q -m ci --source=. deploy-apk --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH" --keystore-base64 env:ANDROID_KEYSTORE_BASE64 --keystore-password env:ANDROID_KEYSTORE_PASSWORD --build-number "$(date +%s)"
publish-website:
desc: Build and publish website via Dagger
+11 -18
View File
@@ -351,10 +351,10 @@ func (m *Ci) setupKeystore(keystoreBase64 *dagger.Secret, keystorePassword *dagg
WithExec([]string{"/bin/sh", "-c", `echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app/upload-keystore.jks`})
}
// Build and return the Android APK
func (m *Ci) BuildAndroidApk(keystoreBase64 *dagger.Secret, keystorePassword *dagger.Secret) *dagger.File {
// Build and return the Android APK signed with the release key.
func (m *Ci) BuildAndroidApk(keystoreBase64 *dagger.Secret, keystorePassword *dagger.Secret, buildNumber string) *dagger.File {
return m.setupKeystore(keystoreBase64, keystorePassword).
WithExec([]string{"flutter", "build", "apk", "--release"}).
WithExec([]string{"flutter", "build", "apk", "--release", "--build-number", buildNumber}).
File("build/app/outputs/flutter-apk/app-release.apk")
}
@@ -367,11 +367,10 @@ func (m *Ci) DeployApk(
commitHash string,
keystoreBase64 *dagger.Secret,
keystorePassword *dagger.Secret,
buildNumber string,
) (string, error) {
// 1. Build the APK
apk := m.BuildAndroidApk(keystoreBase64, keystorePassword)
apk := m.BuildAndroidApk(keystoreBase64, keystorePassword, buildNumber)
// 2. Deploy
datePath := time.Now().Format("2006/01/02")
remoteDir := fmt.Sprintf("public_html/builds/%s", datePath)
apkName := fmt.Sprintf("sharedinbox-mua-%s.apk", commitHash)
@@ -383,29 +382,23 @@ func (m *Ci) DeployApk(
Stdout(ctx)
}
// Build and return the Android App Bundle (AAB)
func (m *Ci) BuildAndroidRelease(keystoreBase64 *dagger.Secret, keystorePassword *dagger.Secret) *dagger.File {
// Build and return the Android App Bundle (AAB) signed with the release key.
func (m *Ci) BuildAndroidRelease(keystoreBase64 *dagger.Secret, keystorePassword *dagger.Secret, buildNumber string) *dagger.File {
return m.setupKeystore(keystoreBase64, keystorePassword).
WithExec([]string{"flutter", "build", "appbundle", "--release"}).
WithExec([]string{"flutter", "build", "appbundle", "--release", "--build-number", buildNumber}).
File("build/app/outputs/bundle/release/app-release.aab")
}
// Publish the Android App Bundle to Google Play Store
func (m *Ci) PublishAndroid(
// UploadToPlayStore uploads a pre-built AAB to the Play Store internal track.
func (m *Ci) UploadToPlayStore(
ctx context.Context,
aab *dagger.File,
playStoreConfig *dagger.Secret,
keystoreBase64 *dagger.Secret,
keystorePassword *dagger.Secret,
) (string, error) {
// 1. Build the AAB
aab := m.BuildAndroidRelease(keystoreBase64, keystorePassword)
// 2. Prepare script source
scriptSource := m.Source.Filter(dagger.DirectoryFilterOpts{
Include: []string{"scripts/deploy_playstore.py"},
})
// 3. Deploy
return dag.Container().
From("python:3.12-alpine").
WithExec([]string{"apk", "add", "--no-cache", "curl"}).