Files
dsh-pet-floating/.gitea/workflows/build.yml
T
jkooe 43275ae7a0
Build APK / build (push) Canceled after 7m12s
feat(ci): Gitea Actions 构建完整版 APK 并发布 Release;App 内更新检查
- .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,防止签名文件误入库。
2026-11-09 17:16:41 +08:00

175 lines
8.2 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"