Files
dsh-pet-floating/.gitea/workflows/build.yml
T
jkooe 3dcda9fde4
Build APK / build (push) Failing after 38s
fix(ci): 修正 gradle 调用与素材地址,失败信息直指根因
Probe(run 343)实测结论:
- job 跑在 Docker 容器内,容器里没有 java,也没有 /opt/jdk17、/opt/android-sdk、/opt/gradle
- 127.0.0.1 不可达(容器内 127 指向自身);172.17.0.1(Docker 网关)17.6 MB/s;公网 370 KB/s

改动:
- 素材地址顺序改为 172.17.0.1 → 127.0.0.1 → server_url(按容器/host 两种模式的实际可达性排)
- 构建改用探测到的系统 gradle:本项目没有 gradlew(build.sh 用的是 /opt/gradle/gradle-8.5)
- 找不到 SDK 时的报错补上「容器看不到宿主机 /opt」这一层,并给出改 host 模式的具体命令
2026-11-09 18:36:41 +08:00

235 lines
12 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
# 地址选择(实测数据,见 Probe 探测):
# 172.17.0.1:3000(Docker 网关 → 宿主机) 17.6 MB/s ← 容器模式下的正确地址
# 127.0.0.1:3000 不可达(容器内 127 是容器自己)
# 123.206.124.151:3000 370 KB/s(走公网绕行,慢 47 倍)
# 顺序按"容器模式优先、host 模式兜底"排:先试网关,再试回环,最后才回公网。
GH_REPO="${{ github.repository }}"
GIT_URL=""
for cand in "http://172.17.0.1:3000" "http://127.0.0.1:3000" "${{ github.server_url }}"; do
if timeout 8 curl -sf -o /dev/null "$cand/api/v1/version" 2>/dev/null; then
GIT_URL="$cand/$GH_REPO.git"
echo "选用 Gitea 地址:$cand"
break
fi
done
[ -n "$GIT_URL" ] || { echo "::error::所有 Gitea 地址都不可达"; exit 1; }
# 优先用宿主机上的素材缓存:构建机本来就有完整仓库,没必要每次重下 650MB。
# 预置方式:把 assets_hq / assets_small / assets_net 放到 /opt/petfloat-assets/ 下。
ASSET_CACHE="${PET_ASSET_CACHE:-/opt/petfloat-assets}"
if [ -d "$ASSET_CACHE" ] && [ -d "$ASSET_CACHE/assets_hq" ]; then
echo "使用本地素材缓存:$ASSET_CACHE"
for d in assets_hq assets_small assets_net; do
rm -rf "app/src/main/$d"
if [ -d "$ASSET_CACHE/$d" ]; then
cp -r "$ASSET_CACHE/$d" "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 "::error::$ASSET_CACHE 缺少 $d"; exit 1
fi
done
else
echo "无本地缓存($ASSET_CACHE 不存在),改从 full-assets 分支取(超时保护 900s)"
git remote add fa "$GIT_URL" 2>/dev/null || true
if ! timeout 900 git fetch --depth 1 fa full-assets; then
echo "::error::拉取 full-assets 超时或失败。"
echo " 650MB 素材经公网下载极慢,建议在 runner 机器上预置缓存:"
echo " mkdir -p /opt/petfloat-assets && cp -r <仓库>/app/src/main/assets_{hq,small,net} /opt/petfloat-assets/"
exit 1
fi
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 "::error::full-assets 分支缺少 app/src/main/$d"
exit 1
fi
done
fi
- 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::找不到 Android SDK(ANDROID_HOME 未设置且常见路径均不存在)"
echo " 环境线索:java = $(java -version 2>&1 | head -1 || echo '未安装')"
if [ -f /.dockerenv ]; then
echo " ⚠ 本 job 运行在 Docker 容器内 —— 容器**看不到**宿主机的 /opt/jdk17、/opt/android-sdk。"
echo " (Probe 实测:容器内 java 不存在、/opt/* 全空、127.0.0.1 不可达、172.17.0.1 达 17.6MB/s)"
fi
echo " 两条出路:"
echo " 1)(推荐,最快)把 runner 改成 host 模式,直接复用宿主机现成环境:"
echo " sed -i 's|\"ubuntu-latest:docker://[^\"]*\"|\"ubuntu-latest:host\"|' /data/.runner"
echo " 然后重启 runner 容器。host 模式下 /opt/jdk17 与 /opt/android-sdk 直接可用。"
echo " 2) 给 runner 换一个自带 JDK17 + Android SDK 的镜像(约 +3GB)。"
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"
# 本项目**不带 gradlew**(build.sh 走的是系统 gradle),所以按优先级探测可执行文件
GRADLE_BIN=""
for c in /opt/gradle/gradle-8.5/bin/gradle /opt/gradle/bin/gradle ./gradlew; do
[ -x "$c" ] && GRADLE_BIN="$c" && break
done
if [ -z "$GRADLE_BIN" ]; then
GRADLE_BIN=$(command -v gradle 2>/dev/null || true)
fi
[ -n "$GRADLE_BIN" ] || {
echo "::error::找不到 gradle —— 项目无 gradlew,必须用系统 gradle"
echo " 期望路径:/opt/gradle/gradle-8.5/bin/gradle(build.sh 里用的就是它)"
exit 1
}
echo "gradle: $GRADLE_BIN"
"$GRADLE_BIN" :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"