Android Build and Release #8
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 Build and Release | |
| on: | |
| workflow_dispatch: | |
| inputs: {} | |
| 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: 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: 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 | |
| # Standard apk paths | |
| DEBUG_APK=app/build/outputs/apk/debug/app-debug.apk | |
| RELEASE_APK=app/build/outputs/apk/release/app-release.apk | |
| COPIED=0 | |
| if [ -f "$DEBUG_APK" ]; then | |
| cp "$DEBUG_APK" release_artifacts/app-debug.apk | |
| COPIED=$((COPIED+1)) | |
| else | |
| echo "Debug APK not found at $DEBUG_APK" | |
| fi | |
| if [ -f "$RELEASE_APK" ]; then | |
| cp "$RELEASE_APK" release_artifacts/app-release.apk | |
| COPIED=$((COPIED+1)) | |
| else | |
| echo "Release APK not found at $RELEASE_APK" | |
| fi | |
| if [ $COPIED -ne 2 ]; then | |
| echo "Missing apk. Listing outputs for debugging:" | |
| ls -R app/build || true | |
| fi | |
| - name: Create GitHub Release (use Gradle version, commit history and commit id) | |
| id: create_release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| # 1) 获取版本号(优先:app/build.gradle -> app/build.gradle.kts -> gradle.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}"} | |
| # 2) commit id(短 sha)和 commit history 作为 body | |
| COMMIT_ID=$(git rev-parse --short HEAD) | |
| BODY=$(git log --pretty=format:"%h %ad - %s (%an)" --date=short --no-merges -n 200) | |
| # 3) 构造 JSON payload(使用 jq 确保正确的 JSON 转义) | |
| if ! command -v jq >/dev/null 2>&1; then | |
| echo "jq is required but not installed in runner. Exiting." | |
| exit 1 | |
| fi | |
| PAYLOAD=$(jq -n --arg tag "${COMMIT_ID}" --arg name "${VERSION}" --arg body "${BODY}" \ | |
| '{ tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false }') | |
| # 4) 调用 GitHub Releases API 创建 release(创建时为最新) | |
| RESPONSE=$(curl -s -X POST \ | |
| -H "Authorization: token ${GITHUB_TOKEN}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| -H "Content-Type: application/json" \ | |
| -d "${PAYLOAD}" \ | |
| "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases") | |
| # 输出响应以便日志可见 | |
| echo "GitHub release response:" | |
| echo "${RESPONSE}" | jq . | |
| # 5) 从响应中取 upload_url 并导出为步骤输出,供后续 upload-release-asset 使用 | |
| UPLOAD_URL=$(echo "${RESPONSE}" | jq -r '.upload_url // empty') | |
| if [ -z "$UPLOAD_URL" ] || [ "$UPLOAD_URL" = "null" ]; then | |
| echo "Failed to create release or upload_url missing. Full response above." | |
| exit 1 | |
| fi | |
| echo "upload_url=${UPLOAD_URL}" >> "$GITHUB_OUTPUT" | |
| - name: Skip turnstyle (not required) | |
| run: echo "Skipping turnstyle wrapper; uploading directly using upload-release-asset" | |
| - name: Upload Debug APK to release | |
| uses: actions/upload-release-asset@v1 | |
| with: | |
| upload_url: ${{ steps.create_release.outputs.upload_url }} | |
| asset_path: release_artifacts/app-debug.apk | |
| asset_name: app-debug.apk | |
| asset_content_type: application/vnd.android.package-archive | |
| - name: Upload Release APK to release | |
| uses: actions/upload-release-asset@v1 | |
| with: | |
| upload_url: ${{ steps.create_release.outputs.upload_url }} | |
| asset_path: release_artifacts/app-release.apk | |
| asset_name: app-release.apk | |
| asset_content_type: application/vnd.android.package-archive |