Added proper automation for playstore builds.

This commit is contained in:
Koen J
2026-02-19 11:13:27 +01:00
parent fa1954ceef
commit 2bcfbf89d3
6 changed files with 246 additions and 64 deletions
+25 -16
View File
@@ -1,20 +1,17 @@
variables:
GIT_SUBMODULE_STRATEGY: recursive
stages:
- buildAndDeployApkUnstable
- buildAndDeployApkStable
- buildAndDeployPlaystore
buildAndDeployApkUnstable:
stage: buildAndDeployApkUnstable
script:
- sh deploy-unstable.sh
only:
- tags
except:
- ^(dev)
when: manual
needs: []
allow_failure: true
artifacts:
when: always
expire_in: 30 days
paths:
- app/build/outputs/apk/unstable/release/*.apk
buildAndDeployApkStable:
stage: buildAndDeployApkStable
@@ -22,16 +19,28 @@ buildAndDeployApkStable:
- sh deploy-stable.sh
only:
- tags
except:
- branches
when: manual
needs: []
artifacts:
when: always
expire_in: 30 days
paths:
- app/build/outputs/apk/stable/release/*.apk
buildAndDeployPlaystore:
stage: buildAndDeployPlaystore
script:
- sh deploy-playstore.sh
- sh build-playstore.sh
- bash tools/venv_playstore.sh
- . .venv-playstore/bin/activate
- python publish_playstore.py --sa /root/grayjay.json --package com.futo.platformplayer.playstore --aab ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab --track production --status completed
only:
- tags
except:
- branches
when: manual
when: on_success
needs:
- buildAndDeployApkStable
artifacts:
when: always
expire_in: 30 days
paths:
- app/build/outputs/bundle/playstoreRelease/*.aab
+13 -4
View File
@@ -1,5 +1,13 @@
#!/bin/sh
set -eu
DOCUMENT_ROOT=/var/www/html
MAINT_FILE="$DOCUMENT_ROOT/maintenance.file"
cleanup() {
rm -f "$MAINT_FILE"
}
trap cleanup EXIT INT TERM
# Sign sources
echo "Signing all sources..."
@@ -11,12 +19,12 @@ echo "Building content..."
# Take site offline
echo "Taking site offline..."
touch $DOCUMENT_ROOT/maintenance.file
touch "$MAINT_FILE"
# Swap over the content
echo "Deploying content..."
cp ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab $DOCUMENT_ROOT/app-playstore-release.aab
aws s3 cp ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab s3://artifacts-grayjay-app/app-playstore-release.aab
cp ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab \
"$DOCUMENT_ROOT/app-playstore-release.aab"
# Notify Cloudflare to wipe the CDN cache
echo "Purging Cloudflare cache for zone $CLOUDFLARE_ZONE_ID..."
@@ -29,4 +37,5 @@ sleep 30
# Take site back online
echo "Bringing site back online..."
rm $DOCUMENT_ROOT/maintenance.file
rm -f "$MAINT_FILE"
trap - EXIT INT TERM
+21 -26
View File
@@ -1,5 +1,13 @@
#!/bin/sh
set -eu
DOCUMENT_ROOT=/var/www/html
MAINT_FILE="$DOCUMENT_ROOT/maintenance.file"
cleanup() {
rm -f "$MAINT_FILE"
}
trap cleanup EXIT INT TERM
# Sign sources
echo "Signing all sources..."
@@ -11,35 +19,21 @@ echo "Building content..."
# Take site offline
echo "Taking site offline..."
touch $DOCUMENT_ROOT/maintenance.file
touch "$MAINT_FILE"
# Swap over the content
echo "Deploying content..."
cp ./app/build/outputs/apk/stable/release/app-stable-x86_64-release.apk $DOCUMENT_ROOT/app-x86_64-release.apk
cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk $DOCUMENT_ROOT/app-arm64-v8a-release.apk
cp ./app/build/outputs/apk/stable/release/app-stable-armeabi-v7a-release.apk $DOCUMENT_ROOT/app-armeabi-v7a-release.apk
cp ./app/build/outputs/apk/stable/release/app-stable-universal-release.apk $DOCUMENT_ROOT/app-universal-release.apk
cp ./app/build/outputs/apk/stable/release/app-stable-x86-release.apk $DOCUMENT_ROOT/app-x86-release.apk
cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk $DOCUMENT_ROOT/app-release.apk
cp ./app/build/outputs/apk/stable/release/app-stable-x86_64-release.apk "$DOCUMENT_ROOT/app-x86_64-release.apk"
cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk "$DOCUMENT_ROOT/app-arm64-v8a-release.apk"
cp ./app/build/outputs/apk/stable/release/app-stable-armeabi-v7a-release.apk "$DOCUMENT_ROOT/app-armeabi-v7a-release.apk"
cp ./app/build/outputs/apk/stable/release/app-stable-universal-release.apk "$DOCUMENT_ROOT/app-universal-release.apk"
cp ./app/build/outputs/apk/stable/release/app-stable-x86-release.apk "$DOCUMENT_ROOT/app-x86-release.apk"
cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk "$DOCUMENT_ROOT/app-release.apk"
VERSION=$(git describe --tags)
echo $VERSION > $DOCUMENT_ROOT/version.txt
mkdir -p $DOCUMENT_ROOT/changelogs
git tag -l --format='%(contents)' $VERSION > $DOCUMENT_ROOT/changelogs/$VERSION
aws s3 cp ./app/build/outputs/apk/stable/release/app-stable-x86_64-release.apk s3://artifacts-grayjay-app/app-x86_64-release.apk
aws s3 cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk s3://artifacts-grayjay-app/app-arm64-v8a-release.apk
aws s3 cp ./app/build/outputs/apk/stable/release/app-stable-armeabi-v7a-release.apk s3://artifacts-grayjay-app/app-armeabi-v7a-release.apk
aws s3 cp ./app/build/outputs/apk/stable/release/app-stable-universal-release.apk s3://artifacts-grayjay-app/app-universal-release.apk
aws s3 cp ./app/build/outputs/apk/stable/release/app-stable-x86-release.apk s3://artifacts-grayjay-app/app-x86-release.apk
aws s3 cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk s3://artifacts-grayjay-app/app-release.apk
VERSION=$(git describe --tags)
echo $VERSION > ./version.txt
git tag -l --format='%(contents)' $VERSION > ./changelog.txt
aws s3 cp ./version.txt s3://artifacts-grayjay-app/version.txt
aws s3 cp ./changelog.txt s3://artifacts-grayjay-app/changelogs/$VERSION
VERSION="$(git describe --tags)"
echo "$VERSION" > "$DOCUMENT_ROOT/version.txt"
mkdir -p "$DOCUMENT_ROOT/changelogs"
git tag -l --format='%(contents)' "$VERSION" > "$DOCUMENT_ROOT/changelogs/$VERSION"
# Notify Cloudflare to wipe the CDN cache
echo "Purging Cloudflare cache for zone $CLOUDFLARE_ZONE_ID..."
@@ -52,4 +46,5 @@ sleep 30
# Take site back online
echo "Bringing site back online..."
rm $DOCUMENT_ROOT/maintenance.file
rm -f "$MAINT_FILE"
trap - EXIT INT TERM
+19 -18
View File
@@ -1,5 +1,13 @@
#!/bin/sh
set -eu
DOCUMENT_ROOT=/var/www/html
MAINT_FILE="$DOCUMENT_ROOT/maintenance.file"
cleanup() {
rm -f "$MAINT_FILE"
}
trap cleanup EXIT INT TERM
# Sign sources
echo "Signing all sources..."
@@ -11,27 +19,19 @@ echo "Building content..."
# Take site offline
echo "Taking site offline..."
touch $DOCUMENT_ROOT/maintenance.file
touch "$MAINT_FILE"
# Swap over the content
echo "Deploying content..."
cp ./app/build/outputs/apk/unstable/release/app-unstable-x86_64-release.apk $DOCUMENT_ROOT/app-x86_64-release-unstable.apk
cp ./app/build/outputs/apk/unstable/release/app-unstable-arm64-v8a-release.apk $DOCUMENT_ROOT/app-arm64-v8a-release-unstable.apk
cp ./app/build/outputs/apk/unstable/release/app-unstable-armeabi-v7a-release.apk $DOCUMENT_ROOT/app-armeabi-v7a-release-unstable.apk
cp ./app/build/outputs/apk/unstable/release/app-unstable-universal-release.apk $DOCUMENT_ROOT/app-universal-release-unstable.apk
cp ./app/build/outputs/apk/unstable/release/app-unstable-x86-release.apk $DOCUMENT_ROOT/app-x86-release-unstable.apk
cp ./app/build/outputs/apk/unstable/release/app-unstable-arm64-v8a-release.apk $DOCUMENT_ROOT/app-release-unstable.apk
git describe --tags > $DOCUMENT_ROOT/version-unstable.txt
cp ./app/build/outputs/apk/unstable/release/app-unstable-x86_64-release.apk "$DOCUMENT_ROOT/app-x86_64-release-unstable.apk"
cp ./app/build/outputs/apk/unstable/release/app-unstable-arm64-v8a-release.apk "$DOCUMENT_ROOT/app-arm64-v8a-release-unstable.apk"
cp ./app/build/outputs/apk/unstable/release/app-unstable-armeabi-v7a-release.apk "$DOCUMENT_ROOT/app-armeabi-v7a-release-unstable.apk"
cp ./app/build/outputs/apk/unstable/release/app-unstable-universal-release.apk "$DOCUMENT_ROOT/app-universal-release-unstable.apk"
cp ./app/build/outputs/apk/unstable/release/app-unstable-x86-release.apk "$DOCUMENT_ROOT/app-x86-release-unstable.apk"
cp ./app/build/outputs/apk/unstable/release/app-unstable-arm64-v8a-release.apk "$DOCUMENT_ROOT/app-release-unstable.apk"
aws s3 cp ./app/build/outputs/apk/unstable/release/app-unstable-x86_64-release.apk s3://artifacts-grayjay-app/app-x86_64-release-unstable.apk
aws s3 cp ./app/build/outputs/apk/unstable/release/app-unstable-arm64-v8a-release.apk s3://artifacts-grayjay-app/app-arm64-v8a-release-unstable.apk
aws s3 cp ./app/build/outputs/apk/unstable/release/app-unstable-armeabi-v7a-release.apk s3://artifacts-grayjay-app/app-armeabi-v7a-release-unstable.apk
aws s3 cp ./app/build/outputs/apk/unstable/release/app-unstable-universal-release.apk s3://artifacts-grayjay-app/app-universal-release-unstable.apk
aws s3 cp ./app/build/outputs/apk/unstable/release/app-unstable-x86-release.apk s3://artifacts-grayjay-app/app-x86-release-unstable.apk
aws s3 cp ./app/build/outputs/apk/unstable/release/app-unstable-arm64-v8a-release.apk s3://artifacts-grayjay-app/app-release-unstable.apk
git describe --tags > ./version-unstable.txt
aws s3 cp ./version-unstable.txt s3://artifacts-grayjay-app/version-unstable.txt
VERSION="$(git describe --tags)"
echo "$VERSION" > "$DOCUMENT_ROOT/version-unstable.txt"
# Notify Cloudflare to wipe the CDN cache
echo "Purging Cloudflare cache for zone $CLOUDFLARE_ZONE_ID..."
@@ -44,4 +44,5 @@ sleep 30
# Take site back online
echo "Bringing site back online..."
rm $DOCUMENT_ROOT/maintenance.file
rm -f "$MAINT_FILE"
trap - EXIT INT TERM
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
import argparse
import os
import sys
import random
import time
import httplib2
import socket
from google_auth_httplib2 import AuthorizedHttp
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from googleapiclient.errors import HttpError
from googleapiclient.http import build_http
SCOPE = "https://www.googleapis.com/auth/androidpublisher"
socket.setdefaulttimeout(30 * 60)
def die(msg: str, code: int = 1):
print(msg, file=sys.stderr)
raise SystemExit(code)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--sa", required=True, help="Service account JSON file path")
ap.add_argument("--package", required=True, help="ApplicationId / package name")
ap.add_argument("--aab", required=True, help="Path to .aab file")
ap.add_argument("--track", default="internal", help="internal|alpha|beta|production")
ap.add_argument("--status", default="completed", help="draft|inProgress|halted|completed")
ap.add_argument("--name", default=None, help="Release name (defaults to CI_COMMIT_TAG)")
ap.add_argument("--rollout", type=float, default=None, help="For staged rollout: 0 < rollout < 1")
args = ap.parse_args()
if not os.path.isfile(args.sa):
die(f"Missing service account JSON: {args.sa}")
if not os.path.isfile(args.aab):
die(f"Missing AAB: {args.aab}")
release_name = args.name or os.environ.get("CI_COMMIT_TAG")
if not release_name:
die("Missing release name: pass --name or set CI_COMMIT_TAG")
staged = args.status in ("inProgress", "halted")
if staged:
if args.rollout is None:
die("--rollout is required when --status is inProgress or halted")
if not (0.0 < args.rollout < 1.0):
die("--rollout must satisfy 0 < rollout < 1")
else:
args.rollout = None
print(f"Loading service account")
creds = service_account.Credentials.from_service_account_file(
args.sa, scopes=[SCOPE]
)
print(f"Loaded service account")
print(f"Building service")
http = build_http()
authed_http = AuthorizedHttp(creds, http=http)
service = build("androidpublisher", "v3", http=authed_http, cache_discovery=False)
print(f"Built service")
try:
print(f"Creating edit")
edit = service.edits().insert(body={}, packageName=args.package).execute()
edit_id = edit["id"]
UPLOAD_CHUNK_SIZE = 10 * 1024 * 1024
MAX_RETRIES = 8
print(f"Media upload started")
media = MediaFileUpload(
args.aab,
mimetype="application/octet-stream",
resumable=True,
chunksize=UPLOAD_CHUNK_SIZE,
)
request = service.edits().bundles().upload(
packageName=args.package,
editId=edit_id,
media_body=media,
)
response = None
last_pct = -1
attempt = 0
while response is None:
try:
status, response = request.next_chunk(num_retries=3)
attempt = 0 # reset after any successful chunk
if status:
pct = int(status.progress() * 100)
if pct != last_pct:
last_pct = pct
print(f"Upload progress: {pct}%", flush=True)
except HttpError as e:
# Retry transient server-side errors with exponential backoff
code = getattr(getattr(e, "resp", None), "status", None)
if code in (500, 502, 503, 504) and attempt < MAX_RETRIES:
sleep_s = min(60, (2 ** attempt)) + random.random()
print(f"Transient HTTP {code}; retrying in {sleep_s:.1f}s...", flush=True)
time.sleep(sleep_s)
attempt += 1
continue
raise
print("Media upload finished")
bundle = response
version_code = bundle["versionCode"]
release = {
"name": release_name,
"status": args.status,
"versionCodes": [str(version_code)],
}
if args.rollout is not None:
release["userFraction"] = args.rollout
track_body = {"releases": [release]}
print(f"Updating track")
service.edits().tracks().update(
packageName=args.package,
editId=edit_id,
track=args.track,
body=track_body,
).execute()
print(f"Updated track")
print(f"Committing")
service.edits().commit(packageName=args.package, editId=edit_id).execute()
print(f"Committed")
print(f"OK: package={args.package} track={args.track} status={args.status} versionCode={version_code} name={release_name}")
except HttpError as e:
content = e.content.decode("utf-8", errors="replace") if getattr(e, "content", None) else str(e)
die(f"Google API error (HTTP {e.resp.status if e.resp else '??'}):\n{content}")
except Exception as e:
die(f"Unexpected error: {e}")
if __name__ == "__main__":
main()
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
VENV_DIR="${VENV_DIR:-.venv-playstore}"
if [[ ! -d "$VENV_DIR" ]]; then
python3 -m venv "$VENV_DIR"
fi
source "$VENV_DIR/bin/activate"
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade google-api-python-client google-auth google-auth-httplib2