Files
opendance-android/build/verify.sh
T
zijunchiang 31b6ddd0fa test(packaging): APK 产物版本元数据校验(T-AND-013)+ 修 die 在定义前被调用
文档 §11.2 要求「版本号单一来源 + 可核对」。本仓库原有校验只覆盖**构建配置**
(gradle verifyReleaseVersion 比 versionName ↔ project.yaml)。但版本号真正被
系统与用户看到的地方是 **APK 的 AndroidManifest**:安装界面、应用信息页、
dumpsys package 读的都是它。构建缓存、复用旧 APK、打包时拷错文件都会造成
「配置全对、产物是旧版」——配置层校验抓不到。

这不是假设:同批工作里 Windows 端 v0.2.1 的发布包就是配置文件全一致、
exe 属性页显示 0.1.0(opendance-windows/tasks/017-pe-version.md)。

新增:
  * build/check_apk_version.py:直接解析 APK 内 AndroidManifest.xml(二进制 AXML)
    的 versionName / versionCode。只依赖 Python 标准库,不需要 Android SDK/aapt2。
    AXML 解析:顶层 ResChunk_header → 字符串池(UTF-8 与 UTF-16 都要处理)→
    START_ELEMENT;属性名本身就在字符串池里,按文本匹配即可,无需资源映射表;
  * build/check-apk-version.sh:薄包装,期望版本缺省取 project.yaml,
    并校验 versionCode == 10000*主 + 100*次 + 修订(§11.1);
  * build/verify.sh 步骤 3/3 新增「版本元数据」计分项(原先只 aapt2 打印一行);
  * build/package-release.sh 打包前校验源 APK,不一致即 die。

交叉验证(与 aapt2 dump badging 逐项一致):
  v0.1.0 APK -> versionCode 1 / versionName 0.1.0
  v0.2.0 APK -> versionCode 200 / versionName 0.2.0
  app/build/outputs/apk/release/app-release.apk -> 同 v0.2.0
反向验证(用真实旧产物当负例):
  bash build/check-apk-version.sh dist-release/opendance-android-v0.1.0/app-release.apk
    ✗ versionName 是 '0.1.0',期望 '0.2.0'
    ✗ versionCode 是 1,期望 200
    退出码 1

顺带修复:build/package-release.sh 第 31/33 行用 die 报版本不一致,但 die()
到第 40 行才定义。命中该分支时输出的是
    build/package-release.sh: 行 31: die: 未找到命令   (退出码 127)
脚本确实中止了,但用户完全看不出真实原因。现把 log()/die() 移到首次使用之前:
    [error] 版本号不一致:参数/versionName=9.9.9,project.yaml=0.2.0  (退出码 1)

未新发版本:APK 内容与 v0.2.0 完全一致(产物侧校验证明其 versionName/versionCode
本就正确),本次改动只涉及构建/校验脚本与文档,故不为它提版本号;
README.txt 的新增自检项会随下一次 Android 发布进入发布包。
2026-09-17 04:29:52 +08:00

155 lines
5.7 KiB
Bash
Executable File
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.
#!/bin/bash
# ============================================================
# OpenDance Studio —— Android 辅助 App 一键验证
#
# 依次执行:单元测试 → debug 构建 → release 构建 → 产物校验
# 任一环节失败即退出非 0。
#
# 用法:
# JAVA_HOME=/path/to/jdk17 ANDROID_HOME=/path/to/sdk bash build/verify.sh
# ============================================================
set -uo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_DIR"
FAILED=0
echo "============================================================"
echo " 1/3 单元测试(JVM,无需设备)"
echo "============================================================"
if ./gradlew testDebugUnitTest --console=plain -q 2>&1 | tail -20; then
echo "✓ 单元测试通过"
else
echo "✗ 单元测试失败"
FAILED=1
fi
echo " 测试报告:app/build/reports/tests/testDebugUnitTest/index.html"
echo ""
echo "============================================================"
echo " 2/3 构建 debug + release APK"
echo "============================================================"
if bash build/build-apk.sh all 2>&1 | grep -vE "^> Task|^$" | tail -20; then
:
else
FAILED=1
fi
echo ""
echo "============================================================"
echo " 3/3 产物校验(签名 / 版本元数据 / 包信息 / 模型资源 / ABI / R8)"
echo "============================================================"
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}}"
# 优先选用与 compileSdk 匹配的 build-tools,缺失时退回最新版
if [ -d "$SDK/build-tools/34.0.0" ]; then
BT="$SDK/build-tools/34.0.0"
else
BT="$(ls -d "$SDK"/build-tools/* 2>/dev/null | sort -V | tail -1)"
fi
RELEASE_APK="app/build/outputs/apk/release/app-release.apk"
if [ ! -f "$RELEASE_APK" ]; then
echo "✗ 未生成 $RELEASE_APK"
FAILED=1
elif [ -z "$BT" ]; then
echo "✗ 未找到 build-tools,无法校验"
FAILED=1
else
# 以**退出码**为准:不同 build-tools 版本的 apksigner 输出文案不一致
# (34.0.0 会打印 "Verifies",36.0.0 配 --print-certs 时只打印证书信息),
# 依赖日志字符串判断会导致误判。
"$BT/apksigner" verify "$RELEASE_APK" > /tmp/apk_verify.txt 2>&1
VERIFY_STATUS=$?
"$BT/apksigner" verify --print-certs "$RELEASE_APK" > /tmp/apk_sign.txt 2>/dev/null
if [ "$VERIFY_STATUS" -eq 0 ]; then
echo "✓ 签名校验通过(apksigner 退出码 0)"
grep -E "Signer #1 certificate DN" /tmp/apk_sign.txt | sed 's/^/ /' || true
if grep -q "APK Signature Scheme v2" /tmp/apk_verify.txt 2>/dev/null; then
echo " v2 签名方案:已启用"
fi
else
echo "✗ 签名校验失败(apksigner 退出码 $VERIFY_STATUS)"
cat /tmp/apk_verify.txt
FAILED=1
fi
"$BT/aapt2" dump badging "$RELEASE_APK" 2>/dev/null | head -1 | sed 's/^/ /'
# ---- 版本元数据必须落在**产物本身**上(文档 §11.2)----
# gradle 的 verifyReleaseVersion 只比构建配置;APK 的 AndroidManifest 才是
# 安装界面 / 应用信息页读到的版本。构建缓存或拷错文件都只会在这一层暴露。
if bash "$PROJECT_DIR/build/check-apk-version.sh" "$RELEASE_APK"; then
:
else
echo "✗ APK 的 versionName/versionCode 与 project.yaml 不一致"
FAILED=1
fi
if unzip -l "$RELEASE_APK" | grep -q "assets/pose_landmarker_lite.task"; then
echo "✓ MediaPipe 模型已随包分发"
else
echo "✗ APK 中缺少 MediaPipe 模型"
FAILED=1
fi
# ---- T-AND-012:ABI 必须恰为真机 ABI,且每个 ABI 下都有 MediaPipe native 库 ----
ABIS=$(unzip -l "$RELEASE_APK" | grep -oE "lib/[^/]+/" | sed 's#lib/##;s#/##' | sort -u)
ABIS_ONE_LINE=$(echo "$ABIS" | tr '\n' ' ')
echo " 包含 ABI:$ABIS_ONE_LINE"
EXPECTED_ABIS=$(printf 'arm64-v8a\narmeabi-v7a')
if [ "$ABIS" = "$EXPECTED_ABIS" ]; then
echo "✓ release ABI 恰为 arm64-v8a + armeabi-v7a(真机 ABI)"
else
echo "✗ release ABI 与预期不符(预期 arm64-v8a / armeabi-v7a)"
FAILED=1
fi
if echo "$ABIS" | grep -qE "x86"; then
echo "✗ release APK 含模拟器 ABI,会导致包体翻倍且与 abiFilters 不符"
FAILED=1
fi
for abi in arm64-v8a armeabi-v7a; do
if unzip -l "$RELEASE_APK" | grep -q "lib/$abi/.*mediapipe"; then
echo "✓ lib/$abi 下有 MediaPipe native 库"
else
echo "✗ lib/$abi 下缺少 MediaPipe native 库(真机安装后必然初始化失败)"
FAILED=1
fi
done
# ---- T-AND-012:R8 不得裁掉 MediaPipe(seeds 由 -printseeds 输出) ----
SEEDS="app/build/outputs/mapping/release/seeds.txt"
if [ -f "$SEEDS" ]; then
MP_SEEDS=$(grep -c "^com.google.mediapipe" "$SEEDS" || true)
if [ "$MP_SEEDS" -gt 0 ]; then
echo "✓ R8 保留 MediaPipe 类 $MP_SEEDS 条(seeds.txt)"
else
echo "✗ R8 把 MediaPipe 整体裁掉了,release 包必然崩溃"
FAILED=1
fi
else
echo "✗ 未生成 $SEEDS(proguard-rules.pro 的 -printseeds 是否被移除?)"
FAILED=1
fi
# ---- T-AND-012:关键类必须真的在 dex 里 ----
if unzip -p "$RELEASE_APK" classes.dex 2>/dev/null | grep -qa "poselandmarker/PoseLandmarker"; then
echo "✓ dex 中存在 PoseLandmarker 类"
else
echo "✗ dex 中找不到 PoseLandmarker(R8/打包异常)"
FAILED=1
fi
fi
echo ""
if [ "$FAILED" -ne 0 ]; then
echo "============================================================"
echo " 验证失败"
echo "============================================================"
exit 1
fi
echo "============================================================"
echo " 验证全部通过"
echo "============================================================"