@@ -36,91 +36,70 @@ jobs:
3636 build-android :
3737 runs-on : ubuntu-latest
3838 steps :
39- - name : Checkout code
40- uses : actions/checkout@v4
39+ # ------------------------------------------------
40+ # 0️⃣ Checkout + basic setup
41+ # ------------------------------------------------
42+ - uses : actions/checkout@v4
4143
42- - name : Set up Node.js
43- uses : actions/setup-node@v4
44+ - uses : actions/setup-node@v4
4445 with :
45- node-version : ' 20 '
46- cache : ' npm'
46+ node-version : " 20 "
47+ cache : npm
4748
48- - name : Set up JDK 17
49- uses : actions/setup-java@v4
49+ - uses : actions/setup-java@v4
5050 with :
51- distribution : ' temurin'
52- java-version : ' 17 '
51+ distribution : temurin
52+ java-version : 17
5353
54- - name : Setup Android SDK
55- uses : android-actions/setup-android@v3
54+ - uses : android-actions/setup-android@v3
5655
57- - name : Install dependencies
58- run : npm install --legacy-peer-deps
56+ # ------------------------------------------------
57+ # 1️⃣ Install npm deps (Expo pre‑build)
58+ # ------------------------------------------------
59+ - run : npm ci --legacy-peer-deps
5960
60- - name : Expo Prebuild
61- run : npx expo prebuild --platform android --clean
61+ - run : npx expo prebuild --platform android --clean
6262
63- - name : Setup Gradle cache
64- uses : gradle/actions/setup-gradle@v3
63+ # ------------------------------------------------
64+ # 2️⃣ Cache Gradle (speed‑up)
65+ # ------------------------------------------------
66+ - uses : gradle/actions/setup-gradle@v3
6567 with :
6668 gradle-home-cache-cleanup : true
6769
68- - name : Decode Keystore
70+ # ------------------------------------------------
71+ # 3️⃣ Decode the keystore (used by Gradle for signing)
72+ # ------------------------------------------------
73+ - name : Decode keystore
6974 run : |
70- if [ -z "${{ secrets.KEYSTORE_BASE64 }}" ]; then
71- echo "❌ Missing secret: KEYSTORE_BASE64"
72- exit 1
73- fi
74- echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > release.keystore
75+ echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > release.keystore
7576 chmod 600 release.keystore
76- ls -lh release.keystore || true
77- echo "✅ Keystore decoded"
77+ env :
78+ KEYSTORE_BASE64 : ${{ secrets.KEYSTORE_BASE64 }}
7879
79- - name : Make gradlew executable
80- run : chmod +x android/gradlew
80+ # ------------------------------------------------
81+ # 4️⃣ Make gradlew executable
82+ # ------------------------------------------------
83+ - run : chmod +x android/gradlew
8184
85+ # ------------------------------------------------
86+ # 5️⃣ Build the artefacts (release or debug)
87+ # ------------------------------------------------
8288 - name : Build Release APK (unsigned)
8389 if : ${{ github.event.inputs.output_format == 'apk' || github.event.inputs.output_format == 'both' }}
8490 run : |
85- set -e
8691 cd android
8792 ./gradlew assembleRelease --no-daemon --stacktrace
8893
89- - name : Build Release AAB
94+ - name : Build Release AAB (unsigned)
9095 if : ${{ github.event.inputs.output_format == 'aab' || github.event.inputs.output_format == 'both' }}
9196 run : |
92- set -e
9397 cd android
9498 ./gradlew bundleRelease --no-daemon --stacktrace
9599
96- - name : Inspect environment & SDK
97- run : |
98- echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT"
99- echo "Listing available build-tools:"
100- ls -1 "$ANDROID_SDK_ROOT/build-tools" || true
101- echo "apksigner version (if available):"
102- for p in "$ANDROID_SDK_ROOT"/build-tools/*/apksigner; do
103- if [ -x "$p" ]; then
104- echo "-> $p"
105- "$p" version || true
106- fi
107- done
108- echo "java version:"
109- java -version || true
110-
111- - name : Inspect Keystore
112- if : ${{ github.event.inputs.should_sign == 'true' }}
113- env :
114- KEYSTORE_PASSWORD : ${{ secrets.KEYSTORE_PASSWORD }}
115- run : |
116- set -e
117- if [ ! -f release.keystore ]; then
118- echo "❌ release.keystore not found"
119- exit 1
120- fi
121- echo "=== Keystore contents ==="
122- keytool -list -v -keystore release.keystore -storepass "${KEYSTORE_PASSWORD}" || true
123-
100+ # ------------------------------------------------
101+ # 6️⃣ (Optional) Sign the APK only – AAB stays unsigned
102+ # ------------------------------------------------
124103 - name : Sign APK
125104 if : ${{ github.event.inputs.should_sign == 'true' && (github.event.inputs.output_format == 'apk' || github.event.inputs.output_format == 'both') }}
126105 id : sign_apk
@@ -130,147 +109,59 @@ jobs:
130109 KEY_ALIAS : ${{ secrets.KEY_ALIAS }}
131110 run : |
132111 set -euo pipefail
133- cd android || exit 1
134-
135- echo "Finding APK (searching typical output locations)..."
136- # find the most likely release APK (unsigned or signed)
137- APK_PATH=$(find app/build/outputs/apk -type f -iname "*release*.apk" | sort | tail -n1 || true)
138-
139- if [ -z "$APK_PATH" ]; then
140- echo "❌ APK not found!"
141- ls -R app/build/outputs || true
142- exit 1
143- fi
144-
145- echo "📦 Found APK: $APK_PATH"
112+ cd android
146113
147- # Locate build-tools
148- if [ -d "$ANDROID_SDK_ROOT/build-tools/34.0.0" ]; then
149- BUILD_TOOLS_PATH="$ANDROID_SDK_ROOT/build-tools/34.0.0"
150- else
151- BUILD_TOOLS_VERSION=$(ls -1 "$ANDROID_SDK_ROOT/build-tools" | sort -V | tail -1 || true)
152- if [ -z "$BUILD_TOOLS_VERSION" ]; then
153- echo "❌ No build-tools found under $ANDROID_SDK_ROOT/build-tools"
154- exit 1
155- fi
156- BUILD_TOOLS_PATH="$ANDROID_SDK_ROOT/build-tools/$BUILD_TOOLS_VERSION"
157- fi
114+ # locate the unsigned apk
115+ APK_PATH=$(find app/build/outputs/apk -type f -name "*release*.apk" | sort | tail -n1)
116+ echo "🔎 Unsigned APK: $APK_PATH"
158117
159- echo "Build tools path: $BUILD_TOOLS_PATH"
160- if [ ! -x "$BUILD_TOOLS_PATH/zipalign" ] || [ ! -x "$BUILD_TOOLS_PATH/apksigner" ]; then
161- echo "❌ zipalign or apksigner not found in $BUILD_TOOLS_PATH"
162- ls -l "$BUILD_TOOLS_PATH" || true
163- exit 1
164- fi
118+ # locate the newest build‑tools folder (contains zipalign & apksigner)
119+ BT=$(ls -1 "$ANDROID_SDK_ROOT/build-tools" | sort -V | tail -1)
120+ echo "Using build‑tools $BT"
121+ ZIPALIGN="$ANDROID_SDK_ROOT/build-tools/$BT/zipalign"
122+ APKSIGNER="$ANDROID_SDK_ROOT/build-tools/$BT/apksigner"
165123
166- echo "🔧 Aligning APK..."
167- # Always create a fresh aligned APK as input to apksigner
168- "$BUILD_TOOLS_PATH/zipalign" -v -p 4 "$APK_PATH" release-aligned.apk
124+ # Align
125+ "$ZIPALIGN" -v -p 4 "$APK_PATH" release-aligned.apk
169126
170- echo "🔐 Signing APK..."
171- "$BUILD_TOOLS_PATH/apksigner " sign \
127+ # Sign
128+ "$APKSIGNER " sign \
172129 --ks ../release.keystore \
173130 --ks-key-alias "${KEY_ALIAS}" \
174131 --ks-pass "pass:${KEYSTORE_PASSWORD}" \
175132 --key-pass "pass:${KEY_PASSWORD}" \
176133 --out release-signed.apk \
177134 release-aligned.apk
178135
179- echo "✅ APK signed : release-signed.apk"
136+ echo "✅ Signed APK ready : release-signed.apk"
180137 echo "signed_apk_path=release-signed.apk" >> $GITHUB_OUTPUT
181138
182- - name : Verify APK signature
139+ # ------------------------------------------------
140+ # 7️⃣ Upload artefacts
141+ # ------------------------------------------------
142+ - name : Upload signed APK
183143 if : ${{ github.event.inputs.should_sign == 'true' && (github.event.inputs.output_format == 'apk' || github.event.inputs.output_format == 'both') }}
184- env :
185- BUILD_TOOLS_PATH : ${{ env.BUILD_TOOLS_PATH }}
186- run : |
187- set -euo pipefail
188- cd android || exit 1
189-
190- # re-detect build-tools (for safety)
191- if [ -d "$ANDROID_SDK_ROOT/build-tools/34.0.0" ]; then
192- BUILD_TOOLS_PATH="$ANDROID_SDK_ROOT/build-tools/34.0.0"
193- else
194- BUILD_TOOLS_VERSION=$(ls -1 "$ANDROID_SDK_ROOT/build-tools" | sort -V | tail -1 || true)
195- BUILD_TOOLS_PATH="$ANDROID_SDK_ROOT/build-tools/$BUILD_TOOLS_VERSION"
196- fi
197-
198- if [ ! -f release-signed.apk ]; then
199- echo "❌ release-signed.apk not found"
200- ls -lh . || true
201- exit 1
202- fi
203-
204- echo "🔍 Verifying APK signature..."
205- # use exit code of apksigner verify to determine success
206- if "$BUILD_TOOLS_PATH/apksigner" verify --print-certs release-signed.apk >/dev/null 2>&1; then
207- echo "✅ APK is properly signed"
208- echo ""
209- echo "=== Certificate Information ==="
210- "$BUILD_TOOLS_PATH/apksigner" verify --print-certs release-signed.apk || true
211- else
212- echo "❌ APK signature verification failed!"
213- "$BUILD_TOOLS_PATH/apksigner" verify release-signed.apk || true
214- echo ""
215- echo "Listing release dir for debugging:"
216- ls -l
217- exit 1
218- fi
219-
220- - name : Sign AAB
221- if : ${{ github.event.inputs.should_sign == 'true' && (github.event.inputs.output_format == 'aab' || github.event.inputs.output_format == 'both') }}
222- env :
223- KEYSTORE_PASSWORD : ${{ secrets.KEYSTORE_PASSWORD }}
224- KEY_PASSWORD : ${{ secrets.KEY_PASSWORD }}
225- KEY_ALIAS : ${{ secrets.KEY_ALIAS }}
226- run : |
227- set -euo pipefail
228- cd android || exit 1
229-
230- AAB_PATH=$(find app/build/outputs/bundle -type f -iname "*.aab" | sort | tail -n1 || true)
231- if [ -z "$AAB_PATH" ]; then
232- echo "❌ AAB not found!"
233- ls -R app/build/outputs || true
234- exit 1
235- fi
236-
237- echo "📦 Found AAB: $AAB_PATH"
238- echo "🔐 Signing AAB with jarsigner..."
239- # jarsigner may be present on the runner; this will sign the AAB container.
240- # If you use Play App Signing by Google Play, consider uploading unsigned and let Play handle signing.
241- jarsigner -verbose \
242- -sigalg SHA256withRSA \
243- -digestalg SHA-256 \
244- -keystore ../release.keystore \
245- -storepass "${KEYSTORE_PASSWORD}" \
246- -keypass "${KEY_PASSWORD}" \
247- "${AAB_PATH}" \
248- "${KEY_ALIAS}"
249-
250- cp "$AAB_PATH" ../release-signed.aab
251- echo "✅ AAB signed: release-signed.aab"
252-
253- - name : Upload APK artifact
254- if : ${{ (github.event.inputs.output_format == 'apk' || github.event.inputs.output_format == 'both') && github.event.inputs.should_sign == 'true' }}
255144 uses : actions/upload-artifact@v4
256145 with :
257146 name : app-release-apk-${{ github.run_number }}
258147 path : android/release-signed.apk
259148 retention-days : 30
260149
261- - name : Upload AAB artifact
262- if : ${{ ( github.event.inputs.output_format == 'aab' || github.event.inputs.output_format == 'both') && github.event.inputs.should_sign == 'true ' }}
150+ - name : Upload AAB (unsigned – Play will sign it)
151+ if : ${{ github.event.inputs.output_format == 'aab' || github.event.inputs.output_format == 'both' }}
263152 uses : actions/upload-artifact@v4
264153 with :
265154 name : app-release-aab-${{ github.run_number }}
266- path : release-signed .aab
155+ path : android/app/build/outputs/bundle/ release/app-release .aab
267156 retention-days : 30
268157
269- - name : Cleanup sensitive files
158+ # ------------------------------------------------
159+ # 8️⃣ Cleanup secret files
160+ # ------------------------------------------------
161+ - name : Cleanup
270162 if : always()
271163 run : |
272- rm -f release.keystore || true
273- rm -f android/release-aligned.apk || true
274- rm -f android/release-signed.apk || true
275- rm -f release-signed.aab || true
276- echo "✅ Cleanup completed"
164+ rm -f release.keystore
165+ rm -f android/release-aligned.apk
166+ rm -f android/release-signed.apk
167+ echo "🧹 Cleanup done"
0 commit comments