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