| 1 | #!/usr/bin/env bash |
| 2 | # build-android.sh |
| 3 | # |
| 4 | # Full Play Store pipeline for Siti AI (Android): |
| 5 | # 1. Clean — rm -rf src-tauri/gen/android |
| 6 | # 2. Init — cargo tauri android init (prod Android config) |
| 7 | # 3. Icons — pnpm gim |
| 8 | # 4. Override — copy src-tauri/gen-override/android/* into gen/android/ |
| 9 | # 5. Keystore — patch storeFile path in gen/android/keystore.properties |
| 10 | # 6. Build — cargo tauri android build --aab |
| 11 | # |
| 12 | # The build produces a signed universal .aab ready to upload to the Play |
| 13 | # Console. versionCode is injected at init/build time as YYYYMMDDhh (UTC) via a |
| 14 | # second -c flag, so tauri.prod-android.conf.json is never mutated. |
| 15 | # |
| 16 | # Every configurable value has a hardcoded default that can be overridden by |
| 17 | # setting the corresponding environment variable before invoking the script. |
| 18 | # |
| 19 | # Usage: |
| 20 | # ./scripts/build-android.sh # full pipeline |
| 21 | # ./scripts/build-android.sh clean # rm gen/android only |
| 22 | # ./scripts/build-android.sh init # android init only |
| 23 | # ./scripts/build-android.sh icons # generate icons only |
| 24 | # ./scripts/build-android.sh override # copy gen-override files only |
| 25 | # ./scripts/build-android.sh keystore # patch keystore.properties only |
| 26 | # ./scripts/build-android.sh build # android build only |
| 27 | # |
| 28 | # make psmobile # full pipeline via Makefile |
| 29 | # make psmobile-build |
| 30 | |
| 31 | set -euo pipefail |
| 32 | |
| 33 | # ── Paths ───────────────────────────────────────────────────────────────────── |
| 34 | |
| 35 | REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
| 36 | ANDROID_CONFIG="${REPO_ROOT}/src-tauri/tauri.prod-android.conf.json" |
| 37 | GEN_ANDROID_DIR="${REPO_ROOT}/src-tauri/gen/android" |
| 38 | GEN_OVERRIDE_DIR="${REPO_ROOT}/src-tauri/gen-override/android" |
| 39 | KEYSTORE_PROPERTIES="${GEN_ANDROID_DIR}/keystore.properties" |
| 40 | |
| 41 | # ── Overridable config ──────────────────────────────────────────────────────── |
| 42 | |
| 43 | # Absolute path to the upload keystore (.jks). Required — no default, so that no |
| 44 | # personal path is baked into the public repo. Copy |
| 45 | # src-tauri/gen-override/android/keystore.properties.example to keystore.properties |
| 46 | # and set storeFile, or export KEYSTORE_PATH before running this script. |
| 47 | KEYSTORE_PATH="${KEYSTORE_PATH:?set KEYSTORE_PATH to the absolute path of your upload-keystore.jks}" |
| 48 | |
| 49 | # versionCode: YYYYMMDDhh (UTC) — same UTC timestamp base as the iOS bundleVersion |
| 50 | # Integer form of the date+hour; fits the Play Store 32-bit limit (≤ 2,100,000,000) through 2099 |
| 51 | # Monotonically increasing → satisfies Play Store requirement |
| 52 | VERSION_CODE="$(date -u +"%Y%m%d%H")" |
| 53 | VERSION_CODE_JSON="{\"bundle\":{\"android\":{\"versionCode\":${VERSION_CODE}}}}" |
| 54 | |
| 55 | # ── Helpers ─────────────────────────────────────────────────────────────────── |
| 56 | |
| 57 | step() { echo ""; echo "── $* ──────────────────────────────────────────────"; } |
| 58 | ok() { echo "✅ $*"; } |
| 59 | fail() { echo "❌ $*" >&2; exit 1; } |
| 60 | |
| 61 | # ── Steps ───────────────────────────────────────────────────────────────────── |
| 62 | |
| 63 | do_clean() { |
| 64 | step "1/6 Clean gen/android" |
| 65 | |
| 66 | echo "🗑 rm -rf ${GEN_ANDROID_DIR}" |
| 67 | rm -rf "${GEN_ANDROID_DIR}" |
| 68 | ok "Cleaned" |
| 69 | } |
| 70 | |
| 71 | do_init() { |
| 72 | step "2/6 Android init" |
| 73 | |
| 74 | [[ -f "$ANDROID_CONFIG" ]] || fail "Android config not found: $ANDROID_CONFIG" |
| 75 | |
| 76 | echo "🏷 versionCode → ${VERSION_CODE}" |
| 77 | echo "📂 config → ${ANDROID_CONFIG}" |
| 78 | echo "" |
| 79 | |
| 80 | cargo tauri android init \ |
| 81 | -c "${ANDROID_CONFIG}" \ |
| 82 | -c "${VERSION_CODE_JSON}" |
| 83 | |
| 84 | ok "Android project initialised → ${GEN_ANDROID_DIR}" |
| 85 | } |
| 86 | |
| 87 | do_icons() { |
| 88 | step "3/6 Generate icons" |
| 89 | |
| 90 | pnpm gim |
| 91 | ok "Icons generated" |
| 92 | } |
| 93 | |
| 94 | do_override() { |
| 95 | step "4/6 Apply gen-override/android" |
| 96 | |
| 97 | # Nothing to do if the override directory contains only .keep files. |
| 98 | OVERRIDE_COUNT="$(find "${GEN_OVERRIDE_DIR}" -not -name '.keep' -not -type d | wc -l | tr -d ' ')" |
| 99 | |
| 100 | if [[ "$OVERRIDE_COUNT" -eq 0 ]]; then |
| 101 | echo "ℹ️ No override files — skipping." |
| 102 | return |
| 103 | fi |
| 104 | |
| 105 | echo "📋 Copying ${OVERRIDE_COUNT} file(s) from gen-override/android → gen/android" |
| 106 | |
| 107 | rsync -av \ |
| 108 | --exclude='.keep' \ |
| 109 | "${GEN_OVERRIDE_DIR}/" \ |
| 110 | "${GEN_ANDROID_DIR}/" |
| 111 | |
| 112 | ok "Overrides applied" |
| 113 | } |
| 114 | |
| 115 | do_keystore() { |
| 116 | step "5/6 Patch keystore.properties" |
| 117 | |
| 118 | [[ -f "$KEYSTORE_PROPERTIES" ]] || fail "keystore.properties not found: ${KEYSTORE_PROPERTIES}\n Run 'override' step first." |
| 119 | [[ -f "$KEYSTORE_PATH" ]] || fail "Keystore file not found: ${KEYSTORE_PATH}\n Set KEYSTORE_PATH env var to the correct .jks path." |
| 120 | |
| 121 | echo "🔑 storeFile → ${KEYSTORE_PATH}" |
| 122 | |
| 123 | # Replace the storeFile= line in-place, leaving all other keys untouched. |
| 124 | sed -i '' "s|^storeFile=.*|storeFile=${KEYSTORE_PATH}|" "${KEYSTORE_PROPERTIES}" |
| 125 | |
| 126 | ok "Patched → ${KEYSTORE_PROPERTIES}" |
| 127 | } |
| 128 | |
| 129 | do_build() { |
| 130 | step "6/6 Build" |
| 131 | |
| 132 | [[ -f "$ANDROID_CONFIG" ]] || fail "Android config not found: $ANDROID_CONFIG" |
| 133 | |
| 134 | echo "🏷 versionCode → ${VERSION_CODE}" |
| 135 | echo "📦 config → ${ANDROID_CONFIG}" |
| 136 | echo "" |
| 137 | |
| 138 | cargo tauri android build \ |
| 139 | --aab \ |
| 140 | -c "${ANDROID_CONFIG}" \ |
| 141 | -c "${VERSION_CODE_JSON}" |
| 142 | |
| 143 | AAB_PATH="${GEN_ANDROID_DIR}/app/build/outputs/bundle/universalRelease/app-universal-release.aab" |
| 144 | if [[ -f "$AAB_PATH" ]]; then |
| 145 | ok "Build complete → ${AAB_PATH}" |
| 146 | else |
| 147 | ok "Build complete (locate the .aab under ${GEN_ANDROID_DIR}/app/build/outputs/bundle/)" |
| 148 | fi |
| 149 | } |
| 150 | |
| 151 | # ── Entry point ─────────────────────────────────────────────────────────────── |
| 152 | |
| 153 | STEP="${1:-all}" |
| 154 | |
| 155 | case "$STEP" in |
| 156 | clean) do_clean ;; |
| 157 | init) do_init ;; |
| 158 | icons) do_icons ;; |
| 159 | override) do_override ;; |
| 160 | keystore) do_keystore ;; |
| 161 | build) do_build ;; |
| 162 | all) |
| 163 | do_clean |
| 164 | do_init |
| 165 | do_icons |
| 166 | do_override |
| 167 | do_keystore |
| 168 | do_build |
| 169 | echo "" |
| 170 | echo "🎉 Pipeline complete: Siti AI Android .aab is ready for Play Store upload." |
| 171 | ;; |
| 172 | *) |
| 173 | echo "Usage: $0 [clean|init|icons|override|keystore|build|all]" >&2 |
| 174 | exit 1 |
| 175 | ;; |
| 176 | esac |