缺陷(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 重打包即可得到
资源管理器可直接双击解压的包。
81 lines
3.7 KiB
GDScript
81 lines
3.7 KiB
GDScript
extends TestCase
|
||
|
||
## T-WIN-012:版本号展示(文档 §11)
|
||
##
|
||
## 硬性要求是「单一来源 + 可核对」:
|
||
## 版本号只允许有一处权威定义(project.godot 的 application/config/version),
|
||
## 界面从该来源读取,且必须与 project.yaml / 产物文件名 / Git tag 一致。
|
||
## 因此这里同时核对 project.godot、project.yaml、README 三处。
|
||
|
||
const AboutScreen = preload("res://src/ui/about_screen.gd")
|
||
const Credits = preload("res://src/ui/credits.gd")
|
||
|
||
|
||
func _read_text(path: String) -> String:
|
||
if not FileAccess.file_exists(path):
|
||
return ""
|
||
var file := FileAccess.open(path, FileAccess.READ)
|
||
if file == null:
|
||
return ""
|
||
var text := file.get_as_text()
|
||
file.close()
|
||
return text
|
||
|
||
|
||
func test_version_is_semver_and_matches_project_yaml() -> void:
|
||
var version := AboutScreen.app_version()
|
||
var parts := version.split(".")
|
||
eq(parts.size(), 3, "版本号形如 X.Y.Z(实际 %s)" % version)
|
||
for part in parts:
|
||
ok(part.is_valid_int(), "版本号各段为整数(%s)" % part)
|
||
|
||
var yaml := _read_text("res://project.yaml")
|
||
ne(yaml, "", "可读取 project.yaml")
|
||
contains(yaml, "version: %s" % version, "project.yaml 的 version 与 project.godot 一致")
|
||
|
||
|
||
func test_about_manifest_lists_version() -> void:
|
||
var manifest := AboutScreen.about_manifest()
|
||
var found := {}
|
||
for item in manifest:
|
||
found[str(item["label"])] = str(item["value"])
|
||
|
||
ok(found.has("客户端版本"), "关于清单含「客户端版本」一项")
|
||
eq(found.get("客户端版本", ""), "v%s" % AboutScreen.app_version(), "清单版本号与唯一来源一致")
|
||
ok(found.has("契约版本"), "关于清单含「契约版本」")
|
||
for key in Credits.CONTRACT_VERSIONS:
|
||
contains(str(found.get("契约版本", "")), "%s v%s" % [key, Credits.CONTRACT_VERSIONS[key]], "清单含 %s 契约版本" % key)
|
||
ok(str(found.get("隐私说明", "")).contains("不存储"), "关于清单声明不存储视频画面")
|
||
|
||
|
||
func test_version_not_hardcoded_in_ui_sources() -> void:
|
||
# 反例防护:界面代码里不允许再手写一份版本号(文档 §11.2 第 1 条)
|
||
var version := AboutScreen.app_version()
|
||
for path in ["res://src/ui/about_screen.gd", "res://src/ui/main_menu.gd", "res://src/ui/game_screen.gd"]:
|
||
var text := _read_text(path)
|
||
ne(text, "", "可读取 %s" % path)
|
||
is_false(text.contains("\"%s\"" % version), "%s 未硬编码版本号字面量" % path.get_file())
|
||
|
||
|
||
func test_entry_scene_version_is_exported_into_build() -> void:
|
||
# 导出产物名必须能由版本号推导(Documentation §11.2 第 2 条)
|
||
var version := AboutScreen.app_version()
|
||
contains("opendance-windows-v%s.zip" % version, version, "产物文件名包含版本号")
|
||
ok(Credits.entry_count() >= 10, "开源声明条目仍齐备")
|
||
|
||
|
||
func test_export_preset_version_matches_project_version() -> void:
|
||
# 缺陷回归:export_presets.cfg 的 application/file_version 与 product_version
|
||
# 决定导出后 exe 的 PE VERSIONINFO 资源,也就是 Windows「右键 → 属性 →
|
||
# 详细信息」里显示的文件版本/产品版本。这两个键**不会**跟随 project.godot 的
|
||
# application/config/version 自动更新,v0.2.1 的发布包因此显示的是 0.1.0
|
||
# (产物名与 tag 都是 v0.2.1,二进制元数据却是 0.1.0)。
|
||
# 产物侧由 build/check-pe-version.sh 直接读 exe 复核,这里做静态防线。
|
||
var version := AboutScreen.app_version()
|
||
var preset := _read_text("res://export_presets.cfg")
|
||
ne(preset, "", "可读取 export_presets.cfg")
|
||
contains(preset, "application/file_version=\"%s\"" % version,
|
||
"导出预设的 file_version 与版本号唯一来源一致")
|
||
contains(preset, "application/product_version=\"%s\"" % version,
|
||
"导出预设的 product_version 与版本号唯一来源一致")
|