feat: enable Play Store CI deploy via Google Play API
- Add ndk debugSymbolLevel=FULL to release build type (opt-B for debug symbols) - Add google-api-python-client to Nix devshell - Add scripts/deploy_playstore.py to upload AAB to internal track - Add deploy-android-bundle task to Taskfile - Enable release.yml (remove if:false, wire up task deploy-android-bundle) - Fix forbidden-files pre-commit hook to run task via nix develop (like dart-check) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
ebd6a27c1a
commit
65aba81952
@@ -8,11 +8,15 @@ jobs:
|
||||
deploy-playstore:
|
||||
name: Build & Deploy to Play Store
|
||||
runs-on: self-hosted
|
||||
if: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Enable Nix flakes
|
||||
run: |
|
||||
mkdir -p ~/.config/nix
|
||||
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
|
||||
|
||||
- name: Prepare Keystore
|
||||
env:
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
@@ -24,15 +28,8 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build App Bundle
|
||||
- name: Build & Deploy to Play Store
|
||||
env:
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
run: nix develop --command task build-android-bundle
|
||||
|
||||
# Play Store upload disabled for now
|
||||
# - name: Upload to Play Store
|
||||
# env:
|
||||
# PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }}
|
||||
# run: |
|
||||
# echo "$PLAY_STORE_CONFIG_JSON" > play-store-key.json
|
||||
# # nix develop --command fvm flutter pub run supply ...
|
||||
PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }}
|
||||
run: nix develop --command task deploy-android-bundle
|
||||
|
||||
@@ -15,7 +15,7 @@ repos:
|
||||
- id: forbidden-files-hook
|
||||
name: check for forbidden home-directory files
|
||||
language: system
|
||||
entry: task check-hygiene
|
||||
entry: bash -c 'cd "$(git rev-parse --show-toplevel)" && nix develop --command task check-hygiene'
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
- id: dart-check
|
||||
|
||||
@@ -268,6 +268,15 @@ tasks:
|
||||
cmds:
|
||||
- ANDROID_HOME=${ANDROID_HOME:-$HOME/Android/Sdk} fvm flutter build apk --release --no-pub | grep -Ev "was tree-shaken|Tree-shaking can be disabled"
|
||||
|
||||
deploy-android-bundle:
|
||||
desc: Build release AAB and upload to Play Store internal track
|
||||
deps: [build-android-bundle]
|
||||
preconditions:
|
||||
- sh: test -n "$PLAY_STORE_CONFIG_JSON"
|
||||
msg: "PLAY_STORE_CONFIG_JSON is not set"
|
||||
cmds:
|
||||
- python3 scripts/deploy_playstore.py
|
||||
|
||||
build-android-bundle:
|
||||
desc: Build a release App Bundle (AAB)
|
||||
deps: [_preflight, _android-sdk-check, _pub-get, generate-changelog]
|
||||
|
||||
@@ -53,6 +53,9 @@ android {
|
||||
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
ndk {
|
||||
debugSymbolLevel = "FULL"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,10 @@
|
||||
curl
|
||||
jq
|
||||
sqlite
|
||||
python3 # used by stalwart-dev/start to pick random ports
|
||||
# python3 base + Google Play API client (for scripts/deploy_playstore.py)
|
||||
(python3.withPackages (ps: with ps; [
|
||||
google-api-python-client
|
||||
])) # used by stalwart-dev/start and deploy_playstore.py
|
||||
fgj # Codeberg/Forgejo CLI (like gh for GitHub)
|
||||
]);
|
||||
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Upload an Android App Bundle to the Google Play Store internal track."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.http import MediaFileUpload
|
||||
|
||||
PACKAGE_NAME = "de.sharedinbox.mua"
|
||||
AAB_PATH = "build/app/outputs/bundle/release/app-release.aab"
|
||||
TRACK = "internal"
|
||||
|
||||
|
||||
def main():
|
||||
config_json = os.environ.get("PLAY_STORE_CONFIG_JSON")
|
||||
if not config_json:
|
||||
print("Error: PLAY_STORE_CONFIG_JSON environment variable not set", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(AAB_PATH):
|
||||
print(f"Error: AAB not found at {AAB_PATH}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
creds = service_account.Credentials.from_service_account_info(
|
||||
json.loads(config_json),
|
||||
scopes=["https://www.googleapis.com/auth/androidpublisher"],
|
||||
)
|
||||
|
||||
service = build("androidpublisher", "v3", credentials=creds)
|
||||
|
||||
edit = service.edits().insert(body={}, packageName=PACKAGE_NAME).execute()
|
||||
edit_id = edit["id"]
|
||||
|
||||
media = MediaFileUpload(AAB_PATH, mimetype="application/octet-stream", resumable=True)
|
||||
bundle = (
|
||||
service.edits()
|
||||
.bundles()
|
||||
.upload(packageName=PACKAGE_NAME, editId=edit_id, media_body=media)
|
||||
.execute()
|
||||
)
|
||||
version_code = bundle["versionCode"]
|
||||
print(f"Uploaded AAB, version code: {version_code}")
|
||||
|
||||
service.edits().tracks().update(
|
||||
packageName=PACKAGE_NAME,
|
||||
editId=edit_id,
|
||||
track=TRACK,
|
||||
body={"releases": [{"versionCodes": [version_code], "status": "completed"}]},
|
||||
).execute()
|
||||
|
||||
service.edits().commit(packageName=PACKAGE_NAME, editId=edit_id).execute()
|
||||
print(f"Deployed version {version_code} to {TRACK} track")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Helper script to generate a production Android Keystore.
|
||||
# Run this from the project root.
|
||||
|
||||
# Define the path relative to the project root
|
||||
KEYSTORE_PATH="android/app/upload-keystore.jks"
|
||||
|
||||
echo "Generating keystore at: $KEYSTORE_PATH"
|
||||
|
||||
keytool -genkey -v \
|
||||
-keystore "$KEYSTORE_PATH" \
|
||||
-alias upload \
|
||||
-keyalg RSA \
|
||||
-keysize 2048 \
|
||||
-validity 10000
|
||||
|
||||
echo ""
|
||||
echo "Done."
|
||||
echo "1. Remember your password!"
|
||||
echo "2. Back up $KEYSTORE_PATH safely."
|
||||
echo "3. Do NOT commit the .jks file or passwords to git."
|
||||
+5
-1
@@ -12,7 +12,7 @@ Data Protection blabla page!
|
||||
* **Taskfile:** Added `task build-android-bundle` to generate the `.aab` file.
|
||||
* **CI Workflow:** Created `.forgejo/workflows/release.yml` which triggers on merge to `main`.
|
||||
|
||||
## 2. What you need to do next
|
||||
|
||||
### A. Create the Keystore
|
||||
Run the helper script I created for you:
|
||||
```bash
|
||||
@@ -26,6 +26,7 @@ Go to **Settings > Actions > Secrets** in your Codeberg repo and add:
|
||||
2. **`ANDROID_KEYSTORE_PASSWORD`**: Your keystore password.
|
||||
3. **`PLAY_STORE_CONFIG_JSON`**: The JSON key from your Google Play Service Account.
|
||||
|
||||
|
||||
### C. First Manual Upload
|
||||
Google Play requires the **very first upload** to be done manually through the web console:
|
||||
1. Generate your keystore using `./t.sh`.
|
||||
@@ -37,6 +38,9 @@ Google Play requires the **very first upload** to be done manually through the w
|
||||
3. Upload the resulting `.aab` from `build/app/outputs/bundle/release/app-release.aab` to the Play Console (Internal Testing or Production track).
|
||||
4. This "locks in" your signing key.
|
||||
|
||||
## 2. What you need to do next
|
||||
|
||||
|
||||
## 3. Firebase Test Lab
|
||||
Once you have the Service Account JSON, you can add a task to `Taskfile.yml` to run automated tests on real devices:
|
||||
```yaml
|
||||
|
||||
Reference in New Issue
Block a user