-
-
Notifications
You must be signed in to change notification settings - Fork 205
304 lines (274 loc) · 15 KB
/
Copy pathrelease-playstore.yml
File metadata and controls
304 lines (274 loc) · 15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
name: Release Android to Play Store (internal testing)
on:
workflow_dispatch:
inputs:
publish:
description: 'Actually upload to Play Console internal track (unchecked = build + sign only, nothing leaves this workflow run)'
type: boolean
required: true
default: false
concurrency:
group: release-playstore
cancel-in-progress: false
jobs:
release-android:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
submodules: 'false'
- name: Checkout submodules with specific branches
run: |
git submodule init
git submodule update --init --recursive
- name: Install packages required to run QZ inside workflow
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
- name: Install Qt Android
uses: jdpurcell/install-qt-action@v5
with:
version: '5.15.0'
host: 'linux'
target: 'android'
arch: 'android'
modules: 'qtcharts qtnetworkauth qtpurchasing'
dir: '${{ github.workspace }}/output/android/'
cache: 'false'
- name: Install custom 16K-page-size Qt (overlay onto stock Qt install)
run: |
QT_DIR="${{ github.workspace }}/output/android/Qt/5.15.0/android"
curl -fsSL -o /tmp/qt5.15.0-android-page16.zip https://github.com/cagnulein/qt-5.15.0-android-page16k/releases/download/1.0.0/qt5.15.0-android-page16.zip
unzip -q /tmp/qt5.15.0-android-page16.zip -d /tmp/qt-page16k
# Overlay the 16K-page-aligned target libs/plugins/qml/mkspecs/jar/include onto the
# stock Qt install; keep the stock Linux host tools (bin/) since the custom Qt's
# host tools were built on macOS (Mach-O, not usable on this Linux runner).
rsync -a --exclude='bin/' "/tmp/qt-page16k/qt5.15.0-android-page16/usr/local/Qt-5.15.0/" "$QT_DIR/"
# qdevice.pri comes from the overlay carrying the macOS origin's NDK host value;
# fix it back to linux so qmake resolves the NDK toolchain path correctly.
sed -i 's/darwin-x86_64/linux-x86_64/' "$QT_DIR/mkspecs/qdevice.pri"
rm -rf /tmp/qt5.15.0-android-page16.zip /tmp/qt-page16k
- name: Install Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: patching qt for bluetooth
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
- name: download 3rd party files for qthttpserver
run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/
- name: Cache Android NDK
id: cache-ndk
uses: actions/cache@v4
with:
path: /usr/local/lib/android/sdk/ndk/21.1.6352462
key: android-ndk-21.1.6352462-${{ runner.os }}
- name: Install Android SDK packages
run: |
SDKMANAGER="/usr/local/lib/android/sdk/cmdline-tools/latest/bin/sdkmanager"
echo "y" | $SDKMANAGER "ndk;21.1.6352462" "platforms;android-35" "build-tools;35.0.0"
- name: Setup ccache
run: |
sudo apt-get install -y ccache
echo "CCACHE_DIR=${{ github.workspace }}/.ccache" >> $GITHUB_ENV
ccache --set-config=max_size=2G
ccache --set-config=compression=true
ccache --set-config=compression_level=6
ccache --set-config=base_dir=${{ github.workspace }}
ccache --set-config=sloppiness=pch_defines,time_macros,include_file_mtime,file_macro
ccache --zero-stats
- name: Restore ccache
uses: actions/cache@v4
with:
path: .ccache
key: ccache-android-ndk21_1-${{ hashFiles('src/**/*.cpp', 'src/**/*.h', 'src/*.pro') }}
restore-keys: |
ccache-android-ndk21_1-
- name: Setup NDK environment with ccache
run: |
NDK_PATH=/usr/local/lib/android/sdk/ndk/21.1.6352462
NDK_BIN=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin
ln -sfn $NDK_PATH /usr/local/lib/android/sdk/ndk-bundle
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
echo "ANDROID_NDK=/usr/local/lib/android/sdk/ndk-bundle" >> $GITHUB_ENV
echo "ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk-bundle" >> $GITHUB_ENV
if [ ! -f "$NDK_BIN/clang.real" ]; then
mv $NDK_BIN/clang $NDK_BIN/clang.real
mv $NDK_BIN/clang++ $NDK_BIN/clang++.real
fi
printf '#!/bin/bash\nexec /usr/bin/ccache %s/clang.real "$@"\n' "$NDK_BIN" > $NDK_BIN/clang
printf '#!/bin/bash\nexec /usr/bin/ccache %s/clang++.real "$@"\n' "$NDK_BIN" > $NDK_BIN/clang++
chmod +x $NDK_BIN/clang $NDK_BIN/clang++
- name: Generate secrets
run: |
cd src
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" >> secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
- name: Prepare Android signing key
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
run: |
if [ -z "$SIGNING_KEY" ] || [ -z "${{ secrets.ALIAS }}" ] || [ -z "${{ secrets.KEY_STORE_PASSWORD }}" ] || [ -z "${{ secrets.KEY_PASSWORD }}" ]; then
echo "Android signing secrets are required to publish a release build; aborting."
exit 1
fi
KEYSTORE="${{ github.workspace }}/src/android/ci-signing-key.jks"
printf '%s' "$SIGNING_KEY" | base64 --decode > "$KEYSTORE"
if [ ! -s "$KEYSTORE" ]; then
echo "Failed to decode Android signing key."
rm -f "$KEYSTORE"
exit 1
fi
echo "CI_ANDROID_SIGNING_KEYSTORE=$KEYSTORE" >> "$GITHUB_ENV"
- name: Compute next Play Store versionCode
id: next_version
env:
PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
run: |
echo "$PLAY_SERVICE_ACCOUNT_JSON" > /tmp/play-sa.json
NEXT_VC=$(python3 scripts/play_next_version_code.py --sa-json /tmp/play-sa.json)
rm -f /tmp/play-sa.json
echo "version_code=$NEXT_VC" >> "$GITHUB_OUTPUT"
echo "Computed next versionCode: $NEXT_VC"
- name: Bump Android versionCode/versionName for this release (ephemeral, never committed)
run: |
NEXT_VC="${{ steps.next_version.outputs.version_code }}"
BASE_VERSION_NAME=$(grep -oE 'android:versionName="[^"]+"' src/android/AndroidManifest.xml | sed -E 's/.*"(.*)"/\1/')
NEW_VERSION_NAME="${BASE_VERSION_NAME}-internal.${{ github.run_number }}"
sed -i "s/android:versionCode=\"[0-9]*\"/android:versionCode=\"$NEXT_VC\"/" src/android/AndroidManifest.xml
sed -i "s/android:versionName=\"[^\"]*\"/android:versionName=\"$NEW_VERSION_NAME\"/" src/android/AndroidManifest.xml
echo "Using versionCode=$NEXT_VC versionName=$NEW_VERSION_NAME (this change is not committed back to the repo)"
- name: Build qthttpserver for Android
run: |
cd src/qthttpserver
qmake
make -j8
make install
- name: Build QZ for Android (4 ABIs)
run: |
lrelease src/qdomyos-zwift.pri
# CONFIG+=debug matches the validated reference build: keeps .so unstripped and,
# with this custom Qt, naturally leaves android-extra-libs unset - the bundled
# android_openssl libs are not 16K-page-aligned, so we must NOT inject them.
qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.1.6352462' 'CONFIG+=debug' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install
cat src/android-qdomyos-zwift-deployment-settings.json
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
${{ github.workspace }}/.gradle-android/caches
${{ github.workspace }}/.gradle-android/wrapper
key: gradle-android-${{ runner.os }}-v2
restore-keys: |
gradle-android-${{ runner.os }}-
- name: Build Android package (release)
run: |
cd src
mkdir -p ${{ github.workspace }}/output/android
rsync -a --exclude build --exclude .gradle android/ ${{ github.workspace }}/output/android/
androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-35 --gradle --aux-mode
export GRADLE_USER_HOME="${{ github.workspace }}/.gradle-android"
export GRADLE_OPTS="-Dgradle.user.home=$GRADLE_USER_HOME"
export GRADLE_DIST_DIR="${{ github.workspace }}/gradle-8.13"
export QT_ANDROID_DIR="${{ github.workspace }}/output/android/Qt/5.15.0/android"
mkdir -p "$GRADLE_USER_HOME"
rm -f "$GRADLE_USER_HOME/gradle.properties"
rm -f "$HOME/.gradle/gradle.properties"
find ${{ github.workspace }}/output/android \( -name '*.properties' -o -name '*.gradle' -o -name '*.gradle.kts' \) -exec sed -i '/android\.bundle\.enableUncompressedNativeLibs/d' {} +
printf '\nqt5AndroidDir=%s/src/android/java\n' "$QT_ANDROID_DIR" >> ${{ github.workspace }}/output/android/gradle.properties
if grep -RInE "android\\.bundle\\.enableUncompressedNativeLibs|enableUncompressedNativeLibs" ${{ github.workspace }}/output/android "$GRADLE_USER_HOME" "$HOME/.gradle" 2>/dev/null; then
echo "Unexpected residual enableUncompressedNativeLibs matches found"
exit 1
fi
if [ ! -x "$GRADLE_DIST_DIR/bin/gradle" ]; then
curl -fsSL https://services.gradle.org/distributions/gradle-8.13-bin.zip -o /tmp/gradle-8.13-bin.zip
unzip -q /tmp/gradle-8.13-bin.zip -d ${{ github.workspace }}
fi
"$GRADLE_DIST_DIR/bin/gradle" -g "$GRADLE_USER_HOME" --no-daemon -p ${{ github.workspace }}/output/android assembleRelease bundleRelease
- name: Sign release APK and AAB
run: |
APK_PATH="${{ github.workspace }}/output/android/build/outputs/apk/release/android-release-unsigned.apk"
AAB_PATH="${{ github.workspace }}/output/android/build/outputs/bundle/release/android-release.aab"
APKSIGNER=$(find /usr/local/lib/android/sdk/build-tools -name apksigner | sort | tail -n 1)
"$APKSIGNER" sign \
--ks "$CI_ANDROID_SIGNING_KEYSTORE" \
--ks-key-alias "${{ secrets.ALIAS }}" \
--ks-pass "pass:${{ secrets.KEY_STORE_PASSWORD }}" \
--key-pass "pass:${{ secrets.KEY_PASSWORD }}" \
--out "${{ github.workspace }}/output/android-release-signed.apk" \
"$APK_PATH"
jarsigner -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore "$CI_ANDROID_SIGNING_KEYSTORE" \
-storepass "${{ secrets.KEY_STORE_PASSWORD }}" \
-keypass "${{ secrets.KEY_PASSWORD }}" \
-signedjar "${{ github.workspace }}/output/android-release-signed.aab" \
"$AAB_PATH" "${{ secrets.ALIAS }}"
rm -f "$CI_ANDROID_SIGNING_KEYSTORE"
- name: Verify 16K page-size alignment
run: |
NDK_LIBDIR=/usr/local/lib/android/sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/bin
FAIL=0
OK=0
WORKDIR=$(mktemp -d)
unzip -q "${{ github.workspace }}/output/android-release-signed.apk" -d "$WORKDIR"
while IFS= read -r -d '' so; do
ALIGN=$("$NDK_LIBDIR/llvm-readelf" -lW "$so" 2>/dev/null | awk '/LOAD/ {print $NF}' | sort -u | tail -1)
if [ "$ALIGN" = "0x4000" ]; then
OK=$((OK+1))
else
echo "FAIL (align=$ALIGN): $so"
FAIL=$((FAIL+1))
fi
done < <(find "$WORKDIR/lib" -name '*.so' -print0)
rm -rf "$WORKDIR"
echo "-- Summary: OK=$OK, FAIL=$FAIL --"
if [ "$FAIL" -ne 0 ]; then
echo "16K page-size check failed - aborting before publish."
exit 1
fi
- name: Archive signed APK/AAB
uses: actions/upload-artifact@v4
with:
name: release-playstore-${{ steps.next_version.outputs.version_code }}
path: |
${{ github.workspace }}/output/android-release-signed.apk
${{ github.workspace }}/output/android-release-signed.aab
# Whether Play requires (or forbids) changesNotSentForReview on commit
# depends on that specific release, not on the app/track - it flips
# between "must be set" and "must not be set" across otherwise
# identical runs. So try one way, and if Play rejects it, retry the
# other way instead of hardcoding a value that will eventually be wrong.
- name: Publish to Play Store (internal testing track)
id: publish_attempt1
if: ${{ inputs.publish }}
continue-on-error: true
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: org.cagnulen.qdomyoszwift
releaseFiles: ${{ github.workspace }}/output/android-release-signed.aab
track: internal
status: completed
- name: Publish to Play Store (retry with changesNotSentForReview inverted)
id: publish_attempt2
if: ${{ inputs.publish && steps.publish_attempt1.outcome == 'failure' }}
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
packageName: org.cagnulen.qdomyoszwift
releaseFiles: ${{ github.workspace }}/output/android-release-signed.aab
track: internal
changesNotSentForReview: true
status: completed
- name: Fail job if both publish attempts failed
if: ${{ inputs.publish && steps.publish_attempt1.outcome == 'failure' && steps.publish_attempt2.outcome != 'success' }}
run: |
echo "Both publish attempts failed (with and without changesNotSentForReview) - see the two steps above for the actual errors."
exit 1