Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 241 additions & 0 deletions .github/workflows/android-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
name: Android APK (debug)

# Triggers:
# - push v*-tauri tag → build debug APK, upload artifact + attach to GitHub Release
# - workflow_dispatch → build debug APK, upload artifact only (no release)
#
# Scope: full overlay/accessibility APK for ADB testing (v1 RECORD_AUDIO + overlay manifest merge).

on:
push:
tags:
- 'v*-tauri'
workflow_dispatch:

jobs:
build-android-apk:
permissions:
contents: write
runs-on: ubuntu-latest
env:
CI: true
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- 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: 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
working-directory: openless-all/app
run: npm run tauri:android:build

- 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 debug APKs
id: apk
shell: bash
working-directory: openless-all/app
run: |
set -euo pipefail
if [[ "${{ github.ref }}" == refs/tags/v* ]] && [[ "${{ github.ref_name }}" == *-tauri ]]; then
label="${{ github.ref_name }}"
else
label="run-${{ github.run_number }}"
fi
export OPENLESS_APK_LABEL="$label"
python - <<'PY'
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",
}
root = Path("src-tauri/gen/android")
label = os.environ["OPENLESS_APK_LABEL"]
out_dir = Path(os.environ["RUNNER_TEMP"]) / "openless-android-debug-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)
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("release_files<<EOF\n")
for abi in expected:
output.write(f"{found[abi]}\n")
output.write("EOF\n")
PY

- name: Upload Android APK artifact (arm64-v8a)
uses: actions/upload-artifact@v4
with:
name: 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: 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: 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: openless-android-debug-x86_64
path: ${{ steps.apk.outputs.x86_64_path }}
if-no-files-found: error

- name: Attach APK to GitHub Release
if: startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-tauri')
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: OpenLess ${{ github.ref_name }}
draft: false
prerelease: ${{ endsWith(github.ref_name, '-beta-tauri') }}
files: ${{ steps.apk.outputs.release_files }}
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
branches: [main, beta]
pull_request:
branches: [main, beta]
workflow_dispatch:

jobs:
cross-platform:
Expand Down
24 changes: 23 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Package.resolved
xcuserdata/
DerivedData/
ChatGPT Image*.jpg

.cursor
# Local config / secrets
*.env
config.local.json
Expand All @@ -33,6 +33,28 @@ video-materials/
node_modules/
dist/
target/
# 本地从 CI / Actions 下载的 APK 及解压目录(非源码)
ci-artifacts/
*.apk

# Android 真机调试产物(日志 / 截图 / adb 导出 / 数据备份 / 临时还原目录)
diagnostics/
android-logs/
test-captures/
extracted/
android-crash*.log
android-run*.log
qa-*.log
qa-*.png
openless-package*.txt
openless-screenshot*.png
openless-android-run*.png
/openless_*.png
/run*.png
openless-appdata*.tar
openless-run*-ui.xml
tmp-run*/

.cargo/registry/
.cargo/git/

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ Go to [Releases](../../releases) and download:
xattr -cr /Applications/OpenLess.app
```
- **Windows**: `OpenLess_<version>_x64-setup.exe` — run the installer.
- **Android debug APK**: choose exactly one split APK for your device:
- `OpenLess-android-debug-arm64-v8a-*.apk` for most modern Android phones.
- `OpenLess-android-debug-armeabi-v7a-*.apk` for older 32-bit ARM phones.
- `OpenLess-android-debug-x86_64-*.apk` for most 64-bit Android emulator images.
- `OpenLess-android-debug-x86-*.apk` for older 32-bit x86 emulator images.
- If unsure, run `adb shell getprop ro.product.cpu.abi` and download the APK matching that ABI. Do not install the x86 builds on a normal ARM phone.
- **macOS (Homebrew)**:
```bash
brew tap appergb/openless https://github.com/appergb/openless
Expand Down
Loading
Loading