Skip to content

Android Build and Release #18

Android Build and Release

Android Build and Release #18

name: Android Build and Release
on:
workflow_dispatch:
inputs:
release_notes:
description: '发布注释(可选,支持多行)'
required: false
default: ''
type: string
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: write
jobs:
build:
name: Build APKs and publish Release
runs-on: ubuntu-latest
env:
JAVA_HOME_21_X64: /usr/lib/jvm/java-21-openjdk-amd64
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Init submodules (llama.cpp)
run: |
git submodule update --init --depth 1
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper/
key: gradle-cache-${{ runner.os }}-v1
- name: Ensure Gradle wrapper version
run: |
./gradlew --version || true
# If you want to force wrapper update add a step to set distributionUrl in gradle/wrapper/gradle-wrapper.properties
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Accept Android SDK licenses
run: |
# 自动查找 sdkmanager 的位置并接受许可
SDK_MANAGER=$(find $ANDROID_HOME -name sdkmanager | head -n 1)
if [ -n "$SDK_MANAGER" ]; then
echo "Found sdkmanager at $SDK_MANAGER"
yes | $SDK_MANAGER --licenses || true
else
echo "sdkmanager not found in $ANDROID_HOME"
# 尝试直接使用环境变量中可能存在的路径
yes | sdkmanager --licenses || true
fi
- name: Build Debug APK
run: |
# Set JVM max heap via GRADLE_OPTS to avoid shell/PowerShell parsing issues with -Porg.gradle.jvmargs
GRADLE_OPTS="-Xmx3g" ./gradlew assembleDebug
- name: Decode keystore (if provided)
shell: bash
env:
RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
run: |
set -euo pipefail
if [ -n "${RELEASE_KEYSTORE_BASE64:-}" ]; then
mkdir -p app/release
echo "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "$PWD/app/release/release-keystore.jks"
ls -l "$PWD/app/release/release-keystore.jks"
else
echo "RELEASE_KEYSTORE_BASE64 not set; skipping keystore decode"
fi
- name: Build Release APK
env:
RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
RELEASE_KEYSTORE_KEY: ${{ secrets.RELEASE_KEYSTORE_KEY }}
RELEASE_KEYSTORE_SUBKEY: ${{ secrets.RELEASE_KEYSTORE_SUBKEY }}
run: |
set -euo pipefail
# Set JVM max heap via GRADLE_OPTS to avoid shell/PowerShell parsing issues with -Porg.gradle.jvmargs
GRADLE_OPTS="-Xmx3g"
# If keystore secret is present but file missing, decode it now (defensive)
if [ -n "${RELEASE_KEYSTORE_BASE64:-}" ] && [ ! -f "$PWD/app/release/release-keystore.jks" ]; then
mkdir -p app/release
echo "$RELEASE_KEYSTORE_BASE64" | base64 --decode > "$PWD/app/release/release-keystore.jks"
chmod 600 "$PWD/app/release/release-keystore.jks"
fi
# Build with signing properties if keystore exists and passwords provided
STORE_FILE="$PWD/app/release/release-keystore.jks"
if [ -f "$STORE_FILE" ] && [ -n "${RELEASE_KEYSTORE_KEY:-}" ] && [ -n "${RELEASE_KEYSTORE_SUBKEY:-}" ]; then
echo "Keystore found — building signed release APK"
./gradlew assembleRelease -Pandroid.injected.signing.store.file="$STORE_FILE" -Pandroid.injected.signing.store.password="${RELEASE_KEYSTORE_KEY}" -Pandroid.injected.signing.key.password="${RELEASE_KEYSTORE_SUBKEY}" -Pandroid.injected.signing.key.alias=release
else
echo "No keystore/passwords provided — building release APK without injected signing properties"
./gradlew assembleRelease
fi
- name: Prepare artifacts
run: |
set -euo pipefail
mkdir -p release_artifacts
# 搜集所有生成的 APK (包括不同架构和 universal 包)
find app/build/outputs/apk/ -name "*.apk" -exec cp {} release_artifacts/ \;
# 检查是否搜集到了文件
COUNT=$(ls release_artifacts/*.apk | wc -l)
if [ "$COUNT" -eq 0 ]; then
echo "No APKs found. Listing outputs for debugging:"
ls -R app/build/outputs/apk/ || true
exit 1
fi
echo "Found $COUNT APKs."
- name: Compute release metadata
id: meta
env:
RELEASE_NOTES: ${{ github.event.inputs.release_notes }}
run: |
set -euo pipefail
# Derive version (Gradle file preferred, fallback to properties)
VERSION=""
if [ -f app/build.gradle ]; then
VERSION=$(grep -m1 "versionName" app/build.gradle | sed -E "s/.*versionName[[:space:]]+['\"]([^'\"]+)['\"].*/\1/" || true)
fi
if [ -z "$VERSION" ] && [ -f app/build.gradle.kts ]; then
VERSION=$(grep -m1 "versionName" app/build.gradle.kts | sed -E "s/.*versionName\\s*=?\\s*\"([^\"]+)\".*/\1/" || true)
fi
if [ -z "$VERSION" ] && [ -f gradle.properties ]; then
VERSION=$(grep -m1 -E "^(VERSION_NAME|versionName|VERSION)=" gradle.properties | cut -d'=' -f2- | tr -d '[:space:]' || true)
fi
VERSION=${VERSION:-"build-${GITHUB_RUN_ID}"}
COMMIT_ID=$(git rev-parse --short HEAD)
# Ensure tag name is version with a leading 'v' (e.g., v0.0.1)
case "$VERSION" in
v*|V*) TAG_NAME="$VERSION" ;;
*) TAG_NAME="v$VERSION" ;;
esac
# Prefer user-provided release notes from workflow dispatch input; fallback to git log
if [ -n "${RELEASE_NOTES:-}" ]; then
BODY="$RELEASE_NOTES"
else
BODY=$(git log --pretty=format:"%h %ad - %s (%an)" --date=short --no-merges -n 200)
fi
echo "tag_name=${TAG_NAME}" >> "$GITHUB_OUTPUT"
echo "release_name=${VERSION}" >> "$GITHUB_OUTPUT"
{
echo 'body<<EOF'
echo "$BODY"
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Create GitHub Release and upload APKs
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.meta.outputs.tag_name }}
name: ${{ steps.meta.outputs.release_name }}
body: ${{ steps.meta.outputs.body }}
fail_on_unmatched_files: false
files: release_artifacts/*.apk