Files
opendance-windows/tasks/017-pe-version.md
T
zijunchiang ba988f6ca6 fix(packaging): 发布包 exe 的 PE 版本资源必须等于发布版本(v0.2.2)
缺陷(v0.2.1 实测):把 opendance-windows-v0.2.1.zip 解开、在 Windows 上
「右键 → 属性 → 详细信息」,显示的是

    文件版本    0.1.0
    产品版本    0.1.0

而产物名、Git tag、README 全是 v0.2.1 —— 正是文档 §11.2 要禁止的
「不可核对的发布」。

根因:export_presets.cfg 的 application/file_version /
application/product_version 被硬编码在 0.1.0,而它们**不会**跟随
project.godot 的 application/config/version 自动更新;check-version.sh 当时
只比 project.godot ↔ project.yaml(两处一致),所以静态校验全绿、产物却是错的。
漂移发生在没人看的第三个位置 —— 校验位置少一个,等于没校验。

证据:
  strings -a -el OpenDance.exe  ->  FileVersion 0.1.0 / ProductVersion 0.1.0
  VS_FIXEDFILEINFO @0x67b976c   ->  FileVersion 0.1.0.0 / ProductVersion 0.0.0.0
对照组:官方模板 windows_release_x86_64.exe 的 FileVersion = 4.7.2,
说明本机 rcedit 改写是生效的,问题纯粹在预设的值本身。

修复:
  * build/sync-export-version.sh(新增):导出前把版本号写进导出预设,
    幂等、拒绝非 X.Y.Z;
  * build/check-pe-version.sh(新增):直接读 exe 的 PE 版本资源 ——
    VS_FIXEDFILEINFO(签名 0xFEEF04BD 后 16 字节,grep -abo 取偏移约 40 ms)
    与 StringFileInfo(strings -el,资源管理器属性页显示的就是它)。
    两处都查:实测固定信息里的 ProductVersion 恒为 0.0.0.0;
  * build/check-version.sh:新增导出预设两个键的校验;
  * build/export-windows.sh:导出前同步、导出后直接复核产物;
  * build/package-release.sh:打包前校验源 exe、解包后再校验包内 exe;
  * build/verify.sh:新增步骤 1/5「产物 PE 版本资源」(未导出则跳过);
  * tests/test_about.gd:新增 test_export_preset_version_matches_project_version。

反向验证:把预设 file_version 改回 0.1.0 →
  check-version.sh 退出码 1(报出两处不一致);
  无头测试 753 通过 / 1 失败,失败项正是
  「导出预设的 file_version 与版本号唯一来源一致」。

实测 v0.2.2 产物:PE FileVersion 0.2.2.0 / StringFileInfo 0.2.2,
版本号在 project.godot / project.yaml / export_presets.cfg / exe 元数据 /
产物名 / tag 六处一致。verify.sh 全绿(754 通过 / 0 失败 / 5 跳过)。

同时把 LZMA 压缩的取舍写清楚:deflate 包 38,248,818 B 仍超 IIS
30,000,000 B 上限,故 Release 资产仍是 LZMA 整包(26,628,529 B,不拆分卷);
若上限放开或改由 Web /static 分发,用 ZIP_METHOD=deflate 重打包即可得到
资源管理器可直接双击解压的包。
2026-09-17 04:22:57 +08:00

93 lines
4.2 KiB
Markdown
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.
# T-WIN-017:发布包 exe 的 PE 版本资源必须等于发布版本
- **状态**: done
- **依赖**: T-WIN-014(打包流程)
- **文档**: v1.6 §5.3 / §5.7 / §11.2
- **契约**: 不涉及(纯打包元数据)
## 缺陷(v0.2.1 实测)
发布包 `opendance-windows-v0.2.1.zip` 里的 `OpenDance.exe`,在 Windows 上
「右键 → 属性 → 详细信息」显示的是:
```
文件版本 0.1.0
产品版本 0.1.0
```
而产物名、Git tag、README 全都是 **v0.2.1** —— 正是文档 §11.2 要禁止的
「不可核对的发布」。
### 根因
`export_presets.cfg` 里这两个键被**硬编码在 0.1.0**,且**没有任何环节读写或校验**:
```ini
application/file_version="0.1.0"
application/product_version="0.1.0"
```
Godot 导出时用它们(经 rcedit)写进 exe 的 PE `VERSIONINFO` 资源。
它们**不会**跟随 `project.godot` 的 `application/config/version` 自动更新 ——
于是每次只是改 `project.godot` + `project.yaml` 发版,二进制元数据一直停在 0.1.0。
`build/check-version.sh` 当时只比 `project.godot` ↔ `project.yaml`,两处一致,
所以**静态校验全绿、产物却是错的**:漂移发生在没人看的第三个位置。
### 证据
```
$ strings -a -el opendance-windows-v0.2.1/OpenDance.exe | sed -n '3147,3151p'
FileVersion
0.1.0
ProductVersion
0.1.0
$ bash build/check-pe-version.sh <exe> 0.2.1
PE 版本资源(…/OpenDance.exe):
VS_FIXEDFILEINFO FileVersion = 0.1.0.0
VS_FIXEDFILEINFO ProductVersion = 0.0.0.0
StringFileInfo FileVersion = 0.1.0
StringFileInfo ProductVersion = 0.1.0
✗ PE FileVersion 为 0.1.0.0,期望 0.2.1.x(Windows 属性页会显示错版本)
✗ 资源字符串 FileVersion 为 '0.1.0',期望 '0.2.1'
✗ 资源字符串 ProductVersion 为 '0.1.0',期望 '0.2.1'
```
(对照组:官方模板 `windows_release_x86_64.exe` 的 `FileVersion = 4.7.2`,
说明该机上的 rcedit 改写**是生效的**,问题纯粹出在预设的值本身。)
## 修复
| 环节 | 文件 | 做法 |
|--|--|--|
| 同步 | `build/sync-export-version.sh`(新增) | 把版本号写进 `export_presets.cfg` 的 `application/file_version` / `application/product_version`;缺行则补行;幂等;拒绝非 `X.Y.Z` |
| 导出 | `build/export-windows.sh` | **导出前**先同步;**导出后**用 `check-pe-version.sh` 直接读 exe 复核 |
| 静态防线 | `build/check-version.sh` | 新增对 `export_presets.cfg` 两个键的校验(不等于项目版本即失败) |
| 产物防线 | `build/check-pe-version.sh`(新增) | 直接读 exe 的 PE 版本资源:`VS_FIXEDFILEINFO`(签名 `0xFEEF04BD` 之后 16 字节,`grep -abo` 取偏移约 40 ms)+ `StringFileInfo`(`strings -el`,资源管理器属性页显示的就是这两项) |
| 打包 | `build/package-release.sh` | 打包前校验源 exe、解包后再校验包内 exe(确认打包/解包没换错文件) |
| 一键验证 | `build/verify.sh` | 新增步骤 1/5:已导出 exe 的 PE 版本资源;未导出则跳过并提示(打包时会强制) |
| 单元测试 | `tests/test_about.gd` | 新增 `test_export_preset_version_matches_project_version`:预设两个键必须等于版本号唯一来源 |
> 只校验 `VS_FIXEDFILEINFO` 是不够的:本机实测该结构里的
> `ProductVersion` 恒为 `0.0.0.0`(rcedit 只填了字符串形式),而属性页显示的是
> `StringFileInfo` 的文本值。因此两处都读、都对。
## 验收
| 验收项 | 结果 |
|--|--|
| 产物 exe 的 PE 版本资源 = 发布版本 | ✅ `check-pe-version.sh` 读 v0.2.2 产物通过(FileVersion 0.2.2.0 / 字符串 0.2.2) |
| 预设漂移能被拦住 | ✅ 反向验证:把预设改回 `0.1.0`,`check-version.sh` 与 `test_about.gd` 均失败 |
| 打包前/解包后都校验 | ✅ `package-release.sh` 两处 `check-pe-version.sh`,失败即 `die` |
| 不新增外部依赖 | ✅ 只用 `grep -abo` + `od` + `strings`(binutils),无 python/node 依赖 |
## 复现命令
```bash
cd opendance-windows
bash build/check-pe-version.sh build/windows/OpenDance.exe 0.2.2 # 产物侧
bash build/check-version.sh 0.2.2 # 配置侧
bash build/sync-export-version.sh 0.2.2 # 需要修时
```