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 发布进入发布包。
This commit is contained in:
2026-09-17 04:29:52 +08:00
parent ad18427928
commit 31b6ddd0fa
8 changed files with 407 additions and 5 deletions
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
# ============================================================
# OpenDance Studio —— APK 版本元数据校验(读产物本身)
#
# 文档 §11.2 要求「版本号单一来源 + 可核对」。`app/build.gradle.kts` 里的
# `verifyReleaseVersion` 任务只比**构建配置**(versionName ↔ project.yaml);
# 而版本号真正被系统与用户看到的地方是 **APK 的 AndroidManifest** ——
# 安装界面、应用信息页、`dumpsys package` 读的都是它。
# 构建缓存、复用旧 APK、打包时拷错文件都会让「配置全对、产物是旧版」;
# Windows 端 v0.2.1 就踩过同类的坑(配置文件全一致,exe 属性页显示 0.1.0,
# 见 opendance-windows/tasks/017-pe-version.md)。
#
# 本脚本直接读产物,因此与构建配置无关也能发现漂移。
#
# 用法:
# bash build/check-apk-version.sh <apk> [期望版本 X.Y.Z]
# 期望版本缺省时取 project.yaml 的 version;此时同时校验
# versionCode == 10000*主 + 100*次 + 修订(文档 §11.1)。
# ============================================================
set -uo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$PROJECT_DIR"
APK="${1:-}"
EXPECTED="${2:-$(sed -n 's/^version:[[:space:]]*//p' project.yaml | head -1)}"
if [ -z "$APK" ]; then
echo "用法:bash build/check-apk-version.sh <apk> [期望版本 X.Y.Z]" >&2
exit 2
fi
if [ ! -f "$APK" ]; then
echo "✗ 找不到文件:$APK" >&2
exit 2
fi
# 解析器只用 Python 标准库读二进制 AXML(不依赖 Android SDK / aapt2)。
# 已用 aapt2 dump badging 在 v0.1.0 与 v0.2.0 两个真实 APK 上交叉验证一致。
PYTHON="${PYTHON_BIN:-}"
if [ -z "$PYTHON" ]; then
for candidate in python3 python; do
if command -v "$candidate" >/dev/null 2>&1; then PYTHON="$candidate"; break; fi
done
fi
if [ -z "$PYTHON" ]; then
echo "✗ 未找到 python3(用于解析 APK 内的二进制 AndroidManifest.xml)" >&2
echo " Debian/Ubuntu: apt-get install -y python3" >&2
exit 2
fi
if [ -n "$EXPECTED" ]; then
"$PYTHON" "$PROJECT_DIR/build/check_apk_version.py" "$APK" --expect-version "$EXPECTED"
else
"$PYTHON" "$PROJECT_DIR/build/check_apk_version.py" "$APK"
fi