Repository navigation
Merge pull request #9 from tall-1997/feature/chat-pill-menu-drawer #523
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Android APK | |
| on: | |
| push: | |
| branches: [main] | |
| tags: | |
| - "v*" | |
| - "*-linux" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: android-apk-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| JAVA_VERSION: "17" | |
| ANDROID_PLATFORM: "android-36" | |
| ANDROID_BUILD_TOOLS: "35.0.0" | |
| ANDROID_CMAKE: "3.22.1" | |
| # Must match ndkVersion and minis-android-sdk-setup NDK_REV. | |
| ANDROID_NDK: "29.0.14206865" | |
| GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=1g" | |
| jobs: | |
| native-deps: | |
| uses: ./.github/workflows/native-deps.yml | |
| sandbox-assets: | |
| uses: ./.github/workflows/sandbox-assets.yml | |
| build: | |
| name: Build Minis Ultra APK | |
| needs: [native-deps, sandbox-assets] | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 180 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| - name: Set up JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: ${{ env.JAVA_VERSION }} | |
| cache: gradle | |
| - name: Restore Gradle caches | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/configuration-cache | |
| key: gradle-${{ runner.os }}-${{ hashFiles('src/android/**/*.gradle.kts', 'src/android/gradle.properties', 'src/android/gradle/wrapper/gradle-wrapper.properties', 'src/android/build-logic/**/*.kt') }} | |
| restore-keys: | | |
| gradle-${{ runner.os }}- | |
| - name: Install native build dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y curl tar unzip make gawk sed file python3 cmake ninja-build g++ git | |
| - name: Install Android SDK commandline tools | |
| run: | | |
| mkdir -p /usr/local/lib/android/sdk/cmdline-tools | |
| cd /tmp | |
| curl -sL -o cmdline-tools.zip \ | |
| "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" | |
| unzip -q cmdline-tools.zip -d /tmp/sdk | |
| mv /tmp/sdk/cmdline-tools /usr/local/lib/android/sdk/cmdline-tools/latest | |
| echo "ANDROID_SDK_ROOT=/usr/local/lib/android/sdk" >> "$GITHUB_ENV" | |
| echo "ANDROID_HOME=/usr/local/lib/android/sdk" >> "$GITHUB_ENV" | |
| yes | /usr/local/lib/android/sdk/cmdline-tools/latest/bin/sdkmanager --licenses >/dev/null || true | |
| - name: Install Android SDK components | |
| run: | | |
| /usr/local/lib/android/sdk/cmdline-tools/latest/bin/sdkmanager \ | |
| "platform-tools" \ | |
| "platforms;${ANDROID_PLATFORM}" \ | |
| "build-tools;${ANDROID_BUILD_TOOLS}" \ | |
| "cmake;${ANDROID_CMAKE}" \ | |
| "ndk;${ANDROID_NDK}" | |
| echo "ANDROID_NDK_HOME=/usr/local/lib/android/sdk/ndk/${ANDROID_NDK}" >> "$GITHUB_ENV" | |
| echo "ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/${ANDROID_NDK}" >> "$GITHUB_ENV" | |
| - name: Download native dependencies | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: native-deps-${{ github.sha }} | |
| path: /tmp/native-deps | |
| - name: Install native dependencies | |
| run: | | |
| mkdir -p src/android/app/src/main/assets src/android/app/src/main/jniLibs/arm64-v8a src/android/app/libs | |
| cp /tmp/native-deps/assets/proot-aarch64 src/android/app/src/main/assets/ | |
| cp /tmp/native-deps/jniLibs/arm64-v8a/libproot.so src/android/app/src/main/jniLibs/arm64-v8a/ | |
| cp /tmp/native-deps/libs/rclone.aar src/android/app/libs/rclone.aar | |
| find /usr/local/lib/android/sdk/ndk/${ANDROID_NDK}/toolchains/llvm/prebuilt -path '*sysroot/usr/lib/aarch64-linux-android' -type d -print0 | while IFS= read -r -d '' d; do cp /tmp/native-deps/libunwind.a "$d/libunwind.a"; done | |
| - name: Download sandbox assets | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: sandbox-assets-${{ github.sha }} | |
| path: /tmp/sandbox-assets | |
| - name: Install embedded sandbox assets | |
| run: | | |
| for f in ubuntu-base.tar.gz android-sdk-tools-aarch64.zip android-cmdline-tools.zip; do | |
| src=$(find /tmp/sandbox-assets -name "$f" -type f -print -quit) | |
| test -n "$src" && test -s "$src" | |
| cp "$src" "src/android/app/src/main/assets/$f" | |
| done | |
| - name: Provider customization stub | |
| run: | | |
| test -f src/android/app/provider-customization.properties \ | |
| || cp src/android/app/provider-customization.properties.example \ | |
| src/android/app/provider-customization.properties | |
| - name: Upload keystore | |
| env: | |
| B64: ${{ secrets.MINIS_UPLOAD_KEYSTORE_BASE64 }} | |
| run: | | |
| if [ -n "$B64" ]; then | |
| echo "$B64" | base64 -d > /tmp/minis-upload.jks | |
| echo "MINIS_UPLOAD_STORE_FILE=/tmp/minis-upload.jks" >> "$GITHUB_ENV" | |
| echo "MINIS_UPLOAD_STORE_PASSWORD=${{ secrets.MINIS_UPLOAD_STORE_PASSWORD }}" >> "$GITHUB_ENV" | |
| echo "MINIS_UPLOAD_KEY_ALIAS=${{ secrets.MINIS_UPLOAD_KEY_ALIAS }}" >> "$GITHUB_ENV" | |
| echo "MINIS_UPLOAD_KEY_PASSWORD=${{ secrets.MINIS_UPLOAD_KEY_PASSWORD }}" >> "$GITHUB_ENV" | |
| echo "signed with GitHub Actions secret keystore" | |
| elif [ -f src/android/release.keystore ] && [ -f src/android/signing.properties ]; then | |
| echo "signed with committed src/android/release.keystore" | |
| else | |
| echo "No upload keystore — APK will be debug-signed (cannot replace a differently signed install)" | |
| fi | |
| - name: Assemble release APK | |
| working-directory: src/android | |
| env: | |
| ANDROID_NDK_HOME: /usr/local/lib/android/sdk/ndk/${{ env.ANDROID_NDK }} | |
| ANDROID_SDK_ROOT: /usr/local/lib/android/sdk | |
| run: | | |
| # gradle.properties is phone-tuned: 2g daemons + 1 worker. On a CI | |
| # runner a cold full compile OOMs the Kotlin daemon at 2g (IR | |
| # lowering, execIncrementalCompiler) and workers.max=1 neuters | |
| # --parallel. Restore what every green run before cb6307f actually | |
| # used: Kotlin daemon inheriting the 6g Gradle heap, 4 workers. | |
| sed -i 's|org.gradle.jvmargs=.*|org.gradle.jvmargs=-Xmx6g -Dfile.encoding=UTF-8|' gradle.properties | |
| sed -i 's|kotlin.daemon.jvmargs=.*|kotlin.daemon.jvmargs=-Xmx6g|' gradle.properties | |
| sed -i 's|org.gradle.workers.max=.*|org.gradle.workers.max=4|' gradle.properties | |
| chmod +x gradlew | |
| # [T-ci-build-speed] The debug variant is disabled via androidComponents | |
| # (app/build.gradle.kts), removing the kspDebug / kspRelease Room | |
| # schema race. --parallel is safe and cuts build time. | |
| ./gradlew :app:assembleRelease --parallel --stacktrace | |
| - name: Stage APK | |
| run: | | |
| SRC=src/android/app/build/outputs/apk/release/app-release.apk | |
| test -s "$SRC" | |
| mkdir -p dist | |
| cp "$SRC" dist/minis-ultra-com.openminis.linux.apk | |
| unzip -l "$SRC" | grep -E "libproot|proot-aarch64|ubuntu-base|android-sdk-tools|android-cmdline-tools" || true | |
| NDK="/usr/local/lib/android/sdk/ndk/${ANDROID_NDK}" | |
| UNWIND_SO=$(find "$NDK" -path '*aarch64-linux-android*' -name libunwind.so 2>/dev/null | head -n 1 || true) | |
| UNWIND_A=$(find "$NDK" -path '*aarch64-linux-android*' -name libunwind.a 2>/dev/null | head -n 1 || true) | |
| if [ -n "$UNWIND_SO" ]; then | |
| cp "$UNWIND_SO" dist/libunwind.so | |
| echo "staged libunwind.so from $UNWIND_SO" | |
| fi | |
| if [ -n "$UNWIND_A" ]; then | |
| cp "$UNWIND_A" dist/libunwind.a | |
| echo "staged libunwind.a from $UNWIND_A" | |
| fi | |
| if [ -z "$UNWIND_SO" ] && [ -z "$UNWIND_A" ]; then | |
| echo "libunwind not in NDK sysroot — see docs/SIGNING.md" | |
| fi | |
| ls -lh dist/ | |
| - name: Stage mapping.txt and native symbols | |
| run: | | |
| mkdir -p dist/symbols | |
| MAP=src/android/app/build/outputs/mapping/release/mapping.txt | |
| if [ -f "$MAP" ]; then cp "$MAP" dist/symbols/mapping.txt; fi | |
| find src/android/app/build -name 'native-debug-symbols.zip' -print -exec cp {} dist/symbols/ \; || true | |
| MERGED=$(find src/android/app/build/intermediates -path '*merged_native_libs*release*' -name '*.so' | head -n 20) | |
| if [ -n "$MERGED" ]; then | |
| mkdir -p dist/symbols/so | |
| for f in $MERGED; do cp "$f" dist/symbols/so/; done | |
| fi | |
| ls -lhR dist/symbols || true | |
| - name: Upload APK artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: minis-ultra-apk | |
| path: dist/minis-ultra-com.openminis.linux.apk | |
| if-no-files-found: error | |
| - name: Upload mapping and native symbols | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: minis-ultra-symbols | |
| path: dist/symbols | |
| if-no-files-found: warn | |
| - name: Read app version | |
| id: appver | |
| run: | | |
| CODE=$(sed -n 's/.*versionCode = \([0-9][0-9]*\).*/\1/p' src/android/app/build.gradle.kts | head -1) | |
| NAME=$(sed -n 's/.*versionName = "\(.*\)".*/\1/p' src/android/app/build.gradle.kts | head -1) | |
| echo "code=$CODE" >> "$GITHUB_OUTPUT" | |
| echo "name=$NAME" >> "$GITHUB_OUTPUT" | |
| echo "app versionName=$NAME versionCode=$CODE" | |
| - name: Publish rolling pre-release | |
| if: github.ref == 'refs/heads/main' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: android-latest | |
| name: Minis Ultra (Android) | |
| prerelease: true | |
| generate_release_notes: false | |
| files: dist/* | |
| body: | | |
| 来自 `${{ github.sha }}` 的滚动 Android 构建。正式版请看带版本号的 Release。 | |
| - 包名:`com.openminis.linux` | |
| - 启动器名称:**Minis Ultra** | |
| - 架构:arm64-v8a | |
| - NDK:r29 `29.0.14206865`(与应用内安装同一修订号,不回退 r28) | |
| - versionName:`${{ steps.appver.outputs.name }}` | |
| - versionCode:${{ steps.appver.outputs.code }} | |
| - 签名说明见 docs/SIGNING.md | |
| - name: Select versioned release notes | |
| if: startsWith(github.ref, 'refs/tags/') | |
| id: notes | |
| run: | | |
| TAG="${GITHUB_REF_NAME}" | |
| SHORT="${TAG%-linux}" | |
| OUT="${RUNNER_TEMP}/RELEASE-BODY.md" | |
| for f in "docs/github-release-${TAG}.md" "docs/github-release-${SHORT}.md"; do | |
| if [ -f "$f" ]; then | |
| cp "$f" "$OUT" | |
| echo "path=$OUT" >> "$GITHUB_OUTPUT" | |
| echo "release body: $f" | |
| exit 0 | |
| fi | |
| done | |
| # RELEASE-NOTES.zh.md is the full changelog; never paste it wholesale. | |
| awk 'BEGIN{p=1} /^---[[:space:]]*$/{exit} {print}' docs/RELEASE-NOTES.zh.md > "$OUT" | |
| echo "path=$OUT" >> "$GITHUB_OUTPUT" | |
| echo "release body: first section of docs/RELEASE-NOTES.zh.md" | |
| # action-gh-release@v2 can exit 0 after creating an unpublished draft for a | |
| # non-prerelease tag. The public Release then 404s and has no APK. Publish | |
| # with gh and fail the job unless the APK is on a non-draft release. | |
| - name: Publish versioned release | |
| if: startsWith(github.ref, 'refs/tags/') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| NOTES: ${{ steps.notes.outputs.path }} | |
| run: | | |
| set -euo pipefail | |
| TAG="${GITHUB_REF_NAME}" | |
| test -s "$NOTES" | |
| mapfile -t ASSETS < <(find dist -maxdepth 1 -type f -size +0c | sort) | |
| if [ "${#ASSETS[@]}" -eq 0 ]; then | |
| echo "dist/ has no release files" | |
| ls -la dist || true | |
| exit 1 | |
| fi | |
| echo "uploading: ${ASSETS[*]}" | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| gh release upload "$TAG" "${ASSETS[@]}" --clobber | |
| gh release edit "$TAG" --draft=false --prerelease=false --latest --notes-file "$NOTES" | |
| else | |
| gh release create "$TAG" "${ASSETS[@]}" --title "$TAG" --notes-file "$NOTES" --latest | |
| fi | |
| gh release view "$TAG" --json isDraft,assets --jq '{draft:.isDraft,assets:[.assets[].name]}' | |
| gh release view "$TAG" --json isDraft,assets --jq 'select(.isDraft==false) | .assets[].name' \ | |
| | grep -qx 'minis-ultra-com.openminis.linux.apk' |