# T-AND-013:APK 产物版本元数据校验(+ 打包脚本 `die` 未定义修复) - **状态**: done - **依赖**: T-AND-009(构建)/ T-AND-010(版本号展示) - **文档**: v1.6 §11.1 / §11.2 - **契约**: 不涉及(打包元数据) ## 一、为什么需要(同类失效的跨端复核) 文档 §11.2 要求「版本号单一来源 + 可核对」。本仓库原有两条校验: 1. `app/build.gradle.kts` 的 `verifyReleaseVersion` 任务:比**构建配置** (`versionName` ↔ `project.yaml` ↔ 契约快照); 2. `README.md` / `project.yaml` 里的版本文本。 但版本号真正被**系统与用户看到**的地方是 **APK 的 `AndroidManifest`**: 安装界面、「应用信息」、`dumpsys package`、应用商店读的都是它。 构建缓存、复用旧 APK、打包时拷错文件,都会造成「配置全对、产物是旧版」—— 配置文件层面的校验抓不到。 这不是假设:同一批工作里,Windows 端 v0.2.1 的发布包就出现过**配置文件全一致、 `OpenDance.exe` 属性页显示 0.1.0**(`export_presets.cfg` 里的版本键没人管), 详见 `opendance-windows/tasks/017-pe-version.md`。因此本端也补一条**读产物本身**的校验。 ## 二、实现 | 文件 | 作用 | |--|--| | `build/check_apk_version.py`(新增) | 直接解析 APK 内 `AndroidManifest.xml`(二进制 AXML)的 `versionName` / `versionCode`;只依赖 Python 标准库,**不需要 Android SDK / aapt2** | | `build/check-apk-version.sh`(新增) | 薄包装:找 python3 → 带期望版本时同时校验 `versionCode == 10000*主 + 100*次 + 修订`(§11.1);期望版本缺省取 `project.yaml` | | `build/package-release.sh` | 打包前校验源 APK 的版本元数据,不一致即 `die` | | `build/verify.sh` | 步骤 3/3 新增「版本元数据」校验项(原来只 `aapt2 dump badging` 打印一行,不判失败) | AXML 解析要点:顶层 `ResChunk_header` → 字符串池(UTF-8 / UTF-16 两种编码都要处理) → `START_ELEMENT` 节点;`` 的属性名(`versionName` / `versionCode`) 本身就在字符串池里,按**属性名文本**匹配即可,无需解析资源映射表。 ## 三、验证 与 `aapt2 dump badging` 交叉验证,三个真实 APK 完全一致: | APK | aapt2 | 本脚本 | |--|--|--| | `dist-release/opendance-android-v0.1.0/app-release.apk` | `versionCode='1' versionName='0.1.0'` | versionCode 1 / versionName 0.1.0 ✓ | | `dist-release/opendance-android-v0.2.0/app-release.apk` | `versionCode='200' versionName='0.2.0'` | versionCode 200 / versionName 0.2.0 ✓ | | `app/build/outputs/apk/release/app-release.apk` | `versionCode='200' versionName='0.2.0'` | 同上 ✓ | **反向验证(用真实旧产物当负例)**: ``` $ bash build/check-apk-version.sh dist-release/opendance-android-v0.1.0/app-release.apk ✗ APK 的 versionName 是 '0.1.0',期望 '0.2.0'(安装界面与应用信息页会显示错版本) ✗ APK 的 versionCode 是 1,期望 200(§11.1:10000*主 + 100*次 + 修订) 退出码=1 ``` ## 四、顺带修复:`package-release.sh` 里 `die` 在定义之前被调用 `build/package-release.sh` 的第 31/33 行用 `die` 报版本不一致,但 `die()` 直到第 40 行 才定义。命中该分支时实际输出的是: ``` $ bash build/package-release.sh 9.9.9 build/package-release.sh: 行 31: die: 未找到命令 退出码=127 ``` 脚本确实中止了(`set -e` + 127),但用户看到的是 shell 报错,**完全看不出真实原因**。 现已把 `log()` / `die()` 的定义移到首次使用之前,修复后: ``` $ bash build/package-release.sh 9.9.9 [error] 版本号不一致:参数/versionName=9.9.9,project.yaml=0.2.0 退出码=1 ``` ## 五、验收 | 验收项 | 结果 | |--|--| | 能读出 APK 的 `versionName` / `versionCode` | ✅ 与 aapt2 在 3 个真实 APK 上逐项一致 | | 产物漂移会被拦住 | ✅ 反向验证:v0.1.0 的 APK 对 0.2.0 期望 → 退出码 1,并指出具体不符项 | | 打包与验证两条链路都接入 | ✅ `package-release.sh`(打包前 `die`)/ `verify.sh`(步骤 3/3 计分) | | 不新增 SDK 依赖 | ✅ 只用 Python 标准库;无 python3 时给出安装提示 |