Build APK / build (push) Canceled after 7m12s
- .gitea/workflows/build.yml:打 tag 触发构建。代码取当前 ref,素材目录单独从 full-assets 分支叠加 (main 保持 ~8MB 不追踪 assets_hq/assets_small/assets_net,缺它们构建出来的包宠物是空白的)。 用 secret 注入的固定 keystore 签 release 包,构建后发布 APK + version.json 到 Gitea Release。 - app/build.gradle.kts:签名改为从环境变量读取(KEYSTORE_PATH / KEYSTORE_PASSWORD / KEY_ALIAS / KEY_PASSWORD),本地无 keystore 时回退 debug 签名;versionCode 支持 CI 注入,保证单调递增 (更新机制要求新包 versionCode 严格大于已装版本,否则系统拒绝覆盖安装)。 - UpdateChecker.kt:读 Gitea Release API 按 versionCode 判版本 → 下载并校验 SHA256 → 经自带 ApkProvider(content://)拉起系统安装器。项目零第三方依赖,用不了 androidx FileProvider, 故自带一个只读 ContentProvider(只暴露 cacheDir/update 下的 .apk,路径取 basename 防穿越)。 - MainActivity 设置页新增「关于 / 检查更新」入口,下载前先检查「安装未知来源应用」授权。 - AndroidManifest 新增 REQUEST_INSTALL_PACKAGES 与 ApkProvider 声明。 - .gitignore 增加 *.jks / *.keystore / keystore.properties,防止签名文件误入库。
175 lines
8.2 KiB
YAML
175 lines
8.2 KiB
YAML
name: Build APK
|
||
|
||
# 触发方式:
|
||
# · push tag(v*) → 构建并发布到 Gitea Release(App 内更新机制读的就是这里)
|
||
# · workflow_dispatch → 手动构建,只上传 artifact,不发布
|
||
#
|
||
# 关键设计:**代码取当前 ref,资源目录从 full-assets 分支单独检出**
|
||
# main 分支为保持 ~8MB 不追踪 assets_hq / assets_small / assets_net(见 README「分支约定」),
|
||
# 而这三个目录是宠物动画与表情包本体,缺了它们构建出来的包宠物是空白的。
|
||
# 所以构建时把 full-assets 的这三个目录叠加到工作树,而不是让代码跟着 650MB 分支走。
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
variant:
|
||
description: 'asset 变体(hq=640x360 原画质 / small=320x180 小体积)'
|
||
required: false
|
||
default: 'hq'
|
||
push:
|
||
tags:
|
||
- 'v*'
|
||
|
||
jobs:
|
||
build:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: 检出代码
|
||
run: |
|
||
git clone --depth 1 --branch "${{ github.ref_name }}" \
|
||
"${{ github.server_url }}/${{ github.repository }}.git" .
|
||
git log --oneline -1
|
||
|
||
- name: 叠加完整素材(full-assets 分支)
|
||
run: |
|
||
set -e
|
||
git remote add fa "${{ github.server_url }}/${{ github.repository }}.git" 2>/dev/null || true
|
||
git fetch --depth 1 fa full-assets
|
||
for d in assets_hq assets_small assets_net; do
|
||
if git ls-tree -d --name-only FETCH_HEAD "app/src/main/$d" | grep -q .; then
|
||
rm -rf "app/src/main/$d"
|
||
git checkout FETCH_HEAD -- "app/src/main/$d"
|
||
echo "✓ $d $(find "app/src/main/$d" -type f | wc -l) 个文件 $(du -sh "app/src/main/$d" | cut -f1)"
|
||
else
|
||
echo "✗ full-assets 分支缺少 app/src/main/$d" >&2
|
||
exit 1
|
||
fi
|
||
done
|
||
|
||
- name: 准备构建环境
|
||
run: |
|
||
set -e
|
||
# JDK 17
|
||
if ! java -version 2>&1 | grep -qE '"(17|2[1-9])'; then
|
||
for c in /opt/jdk17 /usr/lib/jvm/java-17-openjdk-amd64 /usr/lib/jvm/java-21-openjdk-amd64; do
|
||
[ -x "$c/bin/java" ] && export JAVA_HOME="$c" && break
|
||
done
|
||
fi
|
||
[ -n "${JAVA_HOME:-}" ] && echo "JAVA_HOME=$JAVA_HOME" >> "$GITHUB_ENV"
|
||
java -version 2>&1 | head -2
|
||
# Android SDK
|
||
if [ -z "${ANDROID_HOME:-}" ] || [ ! -d "${ANDROID_HOME:-}" ]; then
|
||
for p in /opt/android-sdk /usr/local/android-sdk "$HOME/Android/Sdk" /root/android-sdk; do
|
||
[ -d "$p" ] && export ANDROID_HOME="$p" && break
|
||
done
|
||
fi
|
||
if [ -z "${ANDROID_HOME:-}" ] || [ ! -d "$ANDROID_HOME" ]; then
|
||
echo "::error::runner 上找不到 Android SDK(ANDROID_HOME 未设置且常见路径均不存在)"
|
||
echo " 请在 runner 机器安装 Android commandline-tools + platform-34 + build-tools-35.0.0,"
|
||
echo " 或设置 ANDROID_HOME 环境变量后重试。"
|
||
exit 1
|
||
fi
|
||
echo "ANDROID_HOME=$ANDROID_HOME" >> "$GITHUB_ENV"
|
||
echo "ANDROID_SDK_ROOT=$ANDROID_HOME" >> "$GITHUB_ENV"
|
||
echo "SDK: $ANDROID_HOME"
|
||
ls "$ANDROID_HOME/platforms" 2>/dev/null | head -3
|
||
|
||
- name: 准备签名密钥
|
||
env:
|
||
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||
run: |
|
||
set -e
|
||
if [ -z "${KEYSTORE_BASE64:-}" ]; then
|
||
echo "::warning::未配置 KEYSTORE_BASE64,本次用 debug 签名构建(仅验证链路,不可分发)。"
|
||
echo " ⚠ debug 签名每台机器不同,手机**无法覆盖安装**,更新机制会失效。"
|
||
echo " 按 README「发布与更新」配好 secrets 后重跑,即可得到正式签名包。"
|
||
exit 0
|
||
fi
|
||
printf '%s' "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.jks"
|
||
echo "KEYSTORE_PATH=$RUNNER_TEMP/release.jks" >> "$GITHUB_ENV"
|
||
echo "KEY_ALIAS=${{ secrets.KEY_ALIAS || 'petfloat' }}" >> "$GITHUB_ENV"
|
||
|
||
- name: 构建 Release APK
|
||
env:
|
||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||
VERSION_CODE: ${{ github.run_number }}
|
||
VARIANT: ${{ inputs.variant || 'hq' }}
|
||
run: |
|
||
set -e
|
||
# versionCode 需严格递增:run_number 天然单调,但设备上已装的是 1,
|
||
# 这里加基准 1000 避免与历史调试包冲突,也留出回退空间。
|
||
export VERSION_CODE=$((1000 + VERSION_CODE))
|
||
export VERSION_NAME="1.0.${GITHUB_RUN_NUMBER}"
|
||
echo "构建版本:versionCode=$VERSION_CODE versionName=$VERSION_NAME variant=$VARIANT"
|
||
|
||
ARGS=""
|
||
[ "$VARIANT" = "hq" ] && ARGS="-Phq"
|
||
|
||
chmod +x gradlew
|
||
./gradlew :app:assembleRelease $ARGS --no-daemon --stacktrace
|
||
|
||
APK=app/build/outputs/apk/release/app-release.apk
|
||
[ -f "$APK" ] || { echo "::error::构建产物不存在:$APK"; exit 1; }
|
||
|
||
# 产物登记:体积 + 指纹 + 签名摘要(更新机制靠 versionCode,人靠 SHA 核对)
|
||
APKSHA=$(sha256sum "$APK" | cut -d' ' -f1)
|
||
SIZE=$(stat -c%s "$APK")
|
||
echo "APK_SHA=$APKSHA" >> "$GITHUB_ENV"
|
||
echo "APK_SIZE=$SIZE" >> "$GITHUB_ENV"
|
||
echo "APK_NAME=petfloat-${VERSION_NAME}-${VARIANT}.apk" >> "$GITHUB_ENV"
|
||
cp "$APK" "$RUNNER_TEMP/petfloat-${VERSION_NAME}-${VARIANT}.apk"
|
||
|
||
# version.json:App 端更新检查优先读它取 versionCode / sha256 / 体积,
|
||
# 比从 tag 名解析可靠(tag 是人写的,这个文件由构建产物自己写)。
|
||
printf '{"versionCode":%s,"versionName":"%s","apk":"%s","sha256":"%s","size":%s}\n' \
|
||
"$VERSION_CODE" "$VERSION_NAME" "$APK_NAME" "$APK_SHA" "$SIZE" \
|
||
> "$RUNNER_TEMP/version.json"
|
||
cat "$RUNNER_TEMP/version.json"
|
||
|
||
echo "APK: $APK_SHA ${SIZE} bytes"
|
||
|
||
# 签名核验:确保用的是发布密钥而不是 debug 密钥(debug 密钥每台机器不同,装不上)
|
||
APKSIGNER=$(find "$ANDROID_HOME"/build-tools -name apksigner 2>/dev/null | sort -V | tail -1)
|
||
if [ -n "$APKSIGNER" ]; then
|
||
"$APKSIGNER" verify --print-certs "$APK" | head -3
|
||
fi
|
||
|
||
- name: 上传构建产物
|
||
run: echo "$RUNNER_TEMP/$APK_NAME"
|
||
|
||
- name: 发布到 Gitea Release
|
||
if: startsWith(github.ref, 'refs/tags/')
|
||
env:
|
||
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||
run: |
|
||
set -e
|
||
API="${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
||
TAG="${{ github.ref_name }}"
|
||
|
||
# 幂等:tag 已存在 release 就复用它,避免重跑流水线时 409
|
||
RID=$(curl -s -H "Authorization: token $TOKEN" "$API/releases/tags/$TAG" \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||
if [ -z "$RID" ]; then
|
||
RID=$(curl -s -X POST -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
|
||
-d "{\"tag_name\":\"$TAG\",\"name\":\"PetFloat $TAG\",\"body\":\"versionCode=$VERSION_CODE\\nSHA256=$APK_SHA\",\"target_commitish\":\"main\"}" \
|
||
"$API/releases" \
|
||
| python3 -c "import sys,json;print(json.load(sys.stdin).get('id',''))")
|
||
fi
|
||
echo "release id=$RID"
|
||
|
||
# 先删同名附件(重跑时避免重名失败),再上传
|
||
for aid in $(curl -s -H "Authorization: token $TOKEN" "$API/releases/$RID/assets" \
|
||
| python3 -c "import sys,json;[print(a['id']) for a in json.load(sys.stdin)]" 2>/dev/null); do
|
||
curl -s -X DELETE -H "Authorization: token $TOKEN" "$API/releases/$RID/assets/$aid" || true
|
||
done
|
||
# 上传 APK 与 version.json(App 端靠后者判版本与校验下载完整性)
|
||
for f in "$RUNNER_TEMP/$APK_NAME" "$RUNNER_TEMP/version.json"; do
|
||
n=$(basename "$f")
|
||
curl -s -X POST -H "Authorization: token $TOKEN" \
|
||
-F "attachment=@$f" \
|
||
"$API/releases/$RID/assets?name=$n" | head -c 200
|
||
echo " ← $n"
|
||
done
|
||
echo "已发布:${{ github.server_url }}/${{ github.repository }}/releases/tag/$TAG"
|