Files
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

85 lines
4.2 KiB
Markdown
Raw Permalink 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.
# 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` 节点;`<manifest>` 的属性名(`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 时给出安装提示 |