-
Notifications
You must be signed in to change notification settings - Fork 193
386 lines (347 loc) · 14.6 KB
/
Copy pathandroid-apk.yml
File metadata and controls
386 lines (347 loc) · 14.6 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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
name: Android APK (debug)
# Triggers:
# - push v*-tauri tag → release APK (signed), updater manifest, attach to GitHub Release
# - workflow_dispatch → debug APK only, upload artifact (no release)
#
# Scope: full overlay/accessibility APK for ADB testing and tag releases.
on:
push:
tags:
- 'v*-tauri'
workflow_dispatch:
jobs:
build-android-apk:
permissions:
contents: write
runs-on: ubuntu-latest
env:
CI: true
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
OPENLESS_RELEASE_CHANNEL: ${{ endsWith(github.ref_name, '-beta-tauri') && 'beta' || 'stable' }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Detect build mode
id: mode
shell: bash
run: |
if [[ "${{ github.ref }}" == refs/tags/v* ]] && [[ "${{ github.ref_name }}" == *-tauri ]]; then
echo "mode=release" >> "$GITHUB_OUTPUT"
echo "label=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
else
echo "mode=debug" >> "$GITHUB_OUTPUT"
echo "label=run-${{ github.run_number }}" >> "$GITHUB_OUTPUT"
fi
- name: Check updater signing availability (tag release)
if: steps.mode.outputs.mode == 'release'
shell: bash
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY:-}" ]; then
echo "::error::TAURI_SIGNING_PRIVATE_KEY is required for signed Android release artifacts."
exit 1
fi
- name: Check Android keystore secrets (tag release)
if: steps.mode.outputs.mode == 'release'
shell: bash
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: |
missing=()
for name in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do
if [ -z "${!name:-}" ]; then
missing+=("$name")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "::error::Android release signing secrets are required for tag builds: ${missing[*]}"
echo "::error::Configure ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD in repo secrets."
exit 1
fi
- name: Setup Java 17 (Zulu)
uses: actions/setup-java@v4
with:
distribution: zulu
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
packages: platform-tools
- name: Install NDK and accept SDK licenses
shell: bash
run: |
set -euo pipefail
sdkmanager "ndk;26.1.10909125"
ndk_dir="$ANDROID_HOME/ndk/26.1.10909125"
if [ ! -d "$ndk_dir" ]; then
echo "::error::NDK not found at $ndk_dir"
exit 1
fi
echo "ANDROID_NDK_HOME=$ndk_dir" >> "$GITHUB_ENV"
echo "NDK_HOME=$ndk_dir" >> "$GITHUB_ENV"
set +o pipefail
yes | sdkmanager --licenses
sdkmanager_exit=${PIPESTATUS[1]}
set -o pipefail
if [ "$sdkmanager_exit" -ne 0 ]; then
echo "::error::sdkmanager --licenses failed with exit code $sdkmanager_exit"
exit "$sdkmanager_exit"
fi
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: openless-all/app/package-lock.json
- uses: dtolnay/rust-toolchain@stable
with:
targets: >-
aarch64-linux-android,
armv7-linux-androideabi,
i686-linux-android,
x86_64-linux-android
- name: Cache Cargo
uses: swatinem/rust-cache@v2
with:
workspaces: openless-all/app/src-tauri -> target
- uses: gradle/actions/setup-gradle@v4
- name: Install npm deps
working-directory: openless-all/app
run: npm ci
- name: Build frontend
working-directory: openless-all/app
run: npm run build
- name: Initialize Android project
working-directory: openless-all/app
run: npm run tauri -- android init --ci
- name: Copy Android scaffolding (Kotlin + XML)
working-directory: openless-all/app
run: node scripts/copy-android-scaffolding.mjs
- name: Merge APK v1 manifest permissions
working-directory: openless-all/app
run: node scripts/merge-android-v1-manifest.mjs
- name: Merge overlay / accessibility manifest
working-directory: openless-all/app
run: node scripts/merge-android-overlay-manifest.mjs
- name: Merge updater / install manifest
working-directory: openless-all/app
run: node scripts/merge-android-updater-manifest.mjs
- name: Configure Android release signing
if: steps.mode.outputs.mode == 'release'
working-directory: openless-all/app
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
run: node scripts/configure-android-release-signing.mjs
- name: Prime Gradle wrapper
working-directory: openless-all/app
shell: bash
run: |
set -euo pipefail
for attempt in 1 2 3; do
if src-tauri/gen/android/gradlew --project-dir src-tauri/gen/android --version --no-daemon; then
exit 0
fi
if [ "$attempt" -lt 3 ]; then
sleep $([ "$attempt" -eq 1 ] && echo 15 || echo 30)
fi
done
exit 1
- name: Build Android debug APK
if: steps.mode.outputs.mode == 'debug'
working-directory: openless-all/app
run: npm run tauri:android:build:debug
- name: Build Android release APK
if: steps.mode.outputs.mode == 'release'
working-directory: openless-all/app
run: npm run tauri:android:build:release
- name: Free disk before artifact upload
shell: bash
working-directory: openless-all/app
run: |
set -euo pipefail
rm -rf src-tauri/target
rm -rf ~/.cargo/registry ~/.cargo/git ~/.gradle/caches
df -h
- name: Collect split APKs
id: apk
shell: bash
working-directory: openless-all/app
env:
OPENLESS_APK_MODE: ${{ steps.mode.outputs.mode }}
OPENLESS_APK_LABEL: ${{ steps.mode.outputs.label }}
run: |
set -euo pipefail
python - <<'PY'
import json
import os
import shutil
import sys
import zipfile
from pathlib import Path
expected = {
"arm64-v8a": "arm64_v8a",
"armeabi-v7a": "armeabi_v7a",
"x86": "x86",
"x86_64": "x86_64",
}
arch_map = {
"arm64-v8a": "aarch64",
"armeabi-v7a": "armv7",
"x86": "i686",
"x86_64": "x86_64",
}
root = Path("src-tauri/gen/android")
mode = os.environ["OPENLESS_APK_MODE"]
label = os.environ["OPENLESS_APK_LABEL"]
version = json.loads(Path("package.json").read_text(encoding="utf-8"))["version"]
out_dir = Path(os.environ["RUNNER_TEMP"]) / f"openless-android-{mode}-split"
out_dir.mkdir(parents=True, exist_ok=True)
found = {}
candidates = [
apk for apk in sorted(root.rglob("*.apk"))
if "outputs" in apk.parts
]
if not candidates:
print("::error::No APK found under src-tauri/gen/android/**/outputs/")
for apk in sorted(root.rglob("*.apk")):
print(apk)
sys.exit(1)
for apk in candidates:
with zipfile.ZipFile(apk) as archive:
abis = sorted({
name.split("/")[1]
for name in archive.namelist()
if name.startswith("lib/") and len(name.split("/")) >= 3
})
if len(abis) != 1:
print(f"::error::{apk} contains ABI directories {abis}; expected exactly one ABI per APK")
sys.exit(1)
abi = abis[0]
if abi not in expected:
print(f"::error::{apk} contains unexpected ABI {abi}")
sys.exit(1)
if abi in found:
print(f"::error::Duplicate APKs for ABI {abi}: {found[abi]} and {apk}")
sys.exit(1)
if mode == "release":
dest = out_dir / f"OpenLess_{version}_{abi}.apk"
else:
dest = out_dir / f"OpenLess-android-debug-{abi}-{label}.apk"
shutil.copy2(apk, dest)
found[abi] = dest
print(f"Collected {abi}: {apk} -> {dest}")
missing = sorted(set(expected) - set(found))
if missing:
print(f"::error::Missing split APKs for ABI(s): {', '.join(missing)}")
sys.exit(1)
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
for abi, key in expected.items():
output.write(f"{key}_path={found[abi]}\n")
output.write(f"{key}_arch={arch_map[abi]}\n")
output.write(f"out_dir={out_dir}\n")
output.write("release_files<<EOF\n")
for abi in expected:
output.write(f"{found[abi]}\n")
output.write("EOF\n")
PY
- name: Sign release APKs (minisign)
if: steps.mode.outputs.mode == 'release'
working-directory: openless-all/app
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
set -euo pipefail
node scripts/sign-android-apks.mjs \
"${{ steps.apk.outputs.arm64_v8a_path }}" \
"${{ steps.apk.outputs.armeabi_v7a_path }}" \
"${{ steps.apk.outputs.x86_path }}" \
"${{ steps.apk.outputs.x86_64_path }}"
- name: Write Android updater manifests
if: steps.mode.outputs.mode == 'release'
working-directory: openless-all/app
env:
OPENLESS_UPDATE_APK_DIR: ${{ steps.apk.outputs.out_dir }}
OPENLESS_UPDATE_REPO: appergb/openless
OPENLESS_UPDATE_MIRROR_BASE_URL: https://fastgit.cc/https://github.com
OPENLESS_RELEASE_CHANNEL: ${{ env.OPENLESS_RELEASE_CHANNEL }}
OPENLESS_RELEASE_TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
for arch in aarch64 armv7 i686 x86_64; do
OPENLESS_UPDATE_TARGET=android OPENLESS_UPDATE_ARCH="$arch" \
node scripts/write-updater-manifest.mjs
done
- name: Append release files (manifests + signatures)
if: steps.mode.outputs.mode == 'release'
id: release_assets
shell: bash
run: |
set -euo pipefail
out_dir="${{ steps.apk.outputs.out_dir }}"
{
echo "${{ steps.apk.outputs.release_files }}"
for f in "$out_dir"/*.apk.sig "$out_dir"/latest-android-*.json; do
[ -e "$f" ] && echo "$f"
done
} > "$RUNNER_TEMP/android-release-files.txt"
echo "files<<EOF" >> "$GITHUB_OUTPUT"
cat "$RUNNER_TEMP/android-release-files.txt" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Upload Android APK artifact (arm64-v8a)
uses: actions/upload-artifact@v4
with:
name: ${{ steps.mode.outputs.mode == 'release' && 'openless-android-release-arm64-v8a' || 'openless-android-debug-arm64-v8a' }}
path: ${{ steps.apk.outputs.arm64_v8a_path }}
if-no-files-found: error
- name: Upload Android APK artifact (armeabi-v7a)
uses: actions/upload-artifact@v4
with:
name: ${{ steps.mode.outputs.mode == 'release' && 'openless-android-release-armeabi-v7a' || 'openless-android-debug-armeabi-v7a' }}
path: ${{ steps.apk.outputs.armeabi_v7a_path }}
if-no-files-found: error
- name: Upload Android APK artifact (x86)
uses: actions/upload-artifact@v4
with:
name: ${{ steps.mode.outputs.mode == 'release' && 'openless-android-release-x86' || 'openless-android-debug-x86' }}
path: ${{ steps.apk.outputs.x86_path }}
if-no-files-found: error
- name: Upload Android APK artifact (x86_64)
uses: actions/upload-artifact@v4
with:
name: ${{ steps.mode.outputs.mode == 'release' && 'openless-android-release-x86_64' || 'openless-android-debug-x86_64' }}
path: ${{ steps.apk.outputs.x86_64_path }}
if-no-files-found: error
- name: Prepare Android release body
if: steps.mode.outputs.mode == 'release'
shell: bash
run: |
cat > "$RUNNER_TEMP/android-release-body.md" << 'EOF'
### Android 安装说明
- **推荐**:真机下载 `OpenLess_<version>_arm64-v8a.apk`(arm64 设备)。
- **模拟器**:x86_64 模拟器用 `OpenLess_<version>_x86_64.apk`。
- 首次安装需在系统设置中允许「安装未知应用」;应用内更新会拉起系统安装器。
- adb 调试:`adb install -r OpenLess_<version>_arm64-v8a.apk`
### Android 应用内更新
- Stable 用户检查 `latest-android-aarch64.json`(及 mirror 变体)。
- Beta 用户(设置 → 高级 → 加入 Beta)检查带 `-beta` 后缀的 manifest。
EOF
- name: Attach Android assets to GitHub Release
if: steps.mode.outputs.mode == 'release'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: OpenLess ${{ github.ref_name }}
draft: false
prerelease: ${{ env.OPENLESS_RELEASE_CHANNEL == 'beta' }}
append_body: true
body_path: ${{ runner.temp }}/android-release-body.md
files: ${{ steps.release_assets.outputs.files }}