灯光模拟HarmonyOS应用实战-95-versionCode、versionName与buildVersion都存在为何仍会发错包:用ReleaseVersionLedger绑定升级证据
灯光模拟HarmonyOS应用实战-95-versionCode、versionName与buildVersion都存在为何仍会发错包:用ReleaseVersionLedger绑定升级证据
app.json5 里已经写了 versionCode、versionName 和 buildVersion,并不等于版本治理已经完成。只要团队允许同一个 versionCode 对应两份不同字节的 APP,或构建产品覆盖了配置却没有在制品侧回读,应用市场、测试人员和开发者看到的“1.0.0”就可能不是同一个包。
The_kemusan/AppScope/app.json5 当前给出 versionCode=1000000、versionName=1.0.0、buildVersion=1;已有 pack.info 也记录了同一组三元值。静态一致只能说明这份配置与这份历史描述文件表面相符,不能证明它们对应今天要交付的源码,更不能证明 1000000 尚未在其他发布中使用。
本文把三个字段放进 ReleaseVersionLedger:分配、构建、包内回读、制品摘要与发布结论必须落到同一条不可变记录。第 94 篇关注“产物从哪里来”,本篇只解决“新旧版本如何排序、展示与追踪”。

一、先把当前三元组当成事实,不当成发布结论
当前应用级配置可以缩成下面这段:
{
"app": {
"bundleName": "com.example.the_kemusan",
"versionCode": 1000000,
"versionName": "1.0.0",
"buildVersion": "1"
}
}
根 build-profile.json5 的 products/default 没有为这三个字段提供覆盖值,所以按当前文件可以预期应用级值进入构建。但“预期”仍需在包内回读。构建工具、产品变体、流水线注入或之后的配置修改,都可能让最终制品与工作区文件不同。
本机历史 pack.info 记录的公开版本结构是:
{
"version": {
"code": 1000000,
"name": "1.0.0",
"build": "1"
}
}
它说明那份描述文件中的值一致,没有证明这组三元组可以再次发布。是否已在渠道占用,只能从发布账本和渠道记录确认。
二、三个字段分别回答三个问题
华为 app.json5 文档说明,versionCode 是用于判断版本新旧的数值,新版本必须使用更大的值;versionName 是面向用户展示的版本名称。buildVersion 是构建版本标识,近期工具链还允许在工程级产品配置中覆盖版本字段。华为 app.json5 配置 华为工程级 build-profile.json5
| 字段 | 主要问题 | 合法但危险的做法 | 账本约束 |
|---|---|---|---|
versionCode | 哪个版本更新 | 同一数值构建多份不同 APP | 已发布后不可复用 |
versionName | 用户看到什么 | 修复包仍显示旧名称 | 与发布说明绑定 |
buildVersion | 哪一次构建 | 多模块值不一致或无法追踪 | 同一发布单元保持一致 |
三者不应相互替代。把 versionName 从 1.0.0 改为 1.0.1,却不增加 versionCode,不能建立新的升级顺序;只增加 versionCode 而不登记构建摘要,也不能区分两次内部重打包。
三、先解析“有效版本”,再讨论是否可发布
版本值可能来自应用级配置,也可能被产品配置替换。建议把解析结果连同来源一起返回:
export interface VersionField<T> {
value: T;
source: 'APP_JSON5' | 'PRODUCT_OVERRIDE';
}
export interface EffectiveReleaseVersion {
versionCode: VersionField<number>;
versionName: VersionField<string>;
buildVersion: VersionField<string>;
}
export function resolveField<T>(baseValue: T,
overrideValue: T | undefined): VersionField<T> {
if (overrideValue !== undefined) {
return { value: overrideValue, source: 'PRODUCT_OVERRIDE' };
}
return { value: baseValue, source: 'APP_JSON5' };
}
解析器只负责说明值来自哪里,不负责批准发布。若字段缺失、类型错误或产品名未知,应返回结构化失败,不能偷偷使用上一轮流水线环境变量。
四、ReleaseVersionLedger 记录一次分配的完整身份
账本条目至少包含应用身份、三元组、源码身份、构建身份和制品摘要:
export interface ReleaseVersionEntry {
ledgerVersion: number;
bundleName: string;
channel: string;
product: string;
versionCode: number;
versionName: string;
buildVersion: string;
sourceRevision: string;
buildId: string;
appSha256: string;
status: 'ALLOCATED' | 'BUILT' | 'VERIFIED' | 'PUBLISHED' | 'RETIRED';
allocatedAtUtc: string;
}
ALLOCATED 表示编号已占用但还没有制品;BUILT 才允许写入 APP 摘要;VERIFIED 需要包内版本与计划完全一致;PUBLISHED 需要渠道回执。状态只能单向推进,失败构建也不应把已经分配的 versionCode 还给另一份源码。

五、同一 versionCode 不得对应不同制品
发布门禁的关键不是“版本号有没有增加”,而是历史中是否已经出现同一个应用、渠道和 versionCode。若存在,就比较摘要与状态:
export function guardVersionReuse(history: ReleaseVersionEntry[],
candidate: ReleaseVersionEntry): string[] {
const issues: string[] = [];
for (const item of history) {
if (item.bundleName !== candidate.bundleName ||
item.channel !== candidate.channel ||
item.versionCode !== candidate.versionCode) {
continue;
}
if (item.appSha256 !== candidate.appSha256) {
issues.push('VERSION_CODE_REUSED_WITH_DIFFERENT_ARTIFACT');
} else {
issues.push('VERSION_CODE_ALREADY_REGISTERED');
}
}
return issues;
}
即使摘要相同,也不应重新创建一条“新发布”记录;可以引用原条目继续补充缺失阶段。摘要不同则必须阻止,因为测试报告和线上问题将无法唯一指向某个二进制。
六、buildVersion 要在多模块发布单元中一致
当前 pack.info 列出 entry HAP 和 libraryHSP,目标 API 为 23。华为打包工具文档说明,从 API version 23 起,同一 APP 中所有 HAP/HSP 的 buildVersion 需要保持一致。这个规则说明 buildVersion 不是只写在应用级文件里供人阅读;多模块打包时还必须在包内逐个核对。华为打包工具
export interface ModuleVersionReceipt {
moduleName: string;
moduleType: string;
buildVersion: string;
}
export function findBuildVersionMismatch(
modules: ModuleVersionReceipt[]): string[] {
if (modules.length === 0) {
return ['NO_MODULE_VERSION_RECEIPT'];
}
const expected: string = modules[0].buildVersion;
return modules
.filter((item) => item.buildVersion !== expected)
.map((item) => `BUILD_VERSION_MISMATCH:${item.moduleName}`);
}
这段函数只比较解析后的受控字段。真正的包内信息应由拆包工具读取;不能遍历任意 JSON 后把未知字段原样打印,因为构建目录可能含有不适合公开的材料。
七、回滚也要形成新的版本记录
线上回滚常被误解成“把旧 APP 再发一次”。如果渠道要求 versionCode 单调增加,正确做法是以新 versionCode 构建一份恢复旧业务行为的新产物,同时记录它基于哪个历史版本和哪些补丁。
export interface RollbackPlan {
newVersionCode: number;
displayVersionName: string;
buildVersion: string;
behaviorBaselineVersionCode: number;
reasonCode: string;
}
export function validateRollback(plan: RollbackPlan,
latestPublishedCode: number): string[] {
const issues: string[] = [];
if (plan.newVersionCode <= latestPublishedCode) {
issues.push('ROLLBACK_VERSION_NOT_NEWER');
}
if (plan.reasonCode.length === 0) {
issues.push('ROLLBACK_REASON_MISSING');
}
return issues;
}
behaviorBaselineVersionCode 表示恢复哪一版业务行为,不表示复用那一版二进制。新制品仍需重新构建、签名、回归和发布,并拥有自己的摘要与阶段回执。
八、配置值与包内值必须双向对账
构建前先冻结计划版本;构建后从候选 APP 读取包内 pack.info。两者任一字段不同,都应停止交付:
export function compareVersion(plan: ReleaseVersionEntry,
packed: EffectiveReleaseVersion): string[] {
const issues: string[] = [];
if (plan.versionCode !== packed.versionCode.value) {
issues.push('PACKED_VERSION_CODE_MISMATCH');
}
if (plan.versionName !== packed.versionName.value) {
issues.push('PACKED_VERSION_NAME_MISMATCH');
}
if (plan.buildVersion !== packed.buildVersion.value) {
issues.push('PACKED_BUILD_VERSION_MISMATCH');
}
return issues;
}
对账方向是“账本计划 → 包内事实”。不能在发现包内值不同后自动修改账本去迁就制品;那会掩盖产品覆盖、缓存输出或流水线注入造成的偏差。

九、发布前验证矩阵
| 编号 | 条件 | 期望结果 | 证据 |
|---|---|---|---|
| V95-01 | 首次分配未使用的 versionCode | 创建 ALLOCATED 条目 | 账本唯一键 |
| V95-02 | 同 code、不同 APP 摘要 | 阻止 | 固定问题码 |
| V95-03 | versionName 改了、code 未增加 | 阻止新发布 | 最新渠道记录 |
| V95-04 | 产品覆盖版本 | 记录来源为 PRODUCT_OVERRIDE | 有效配置快照 |
| V95-05 | entry 与 HSP buildVersion 不同 | 阻止 APP | 包内模块回执 |
| V95-06 | 计划与 pack.info 完全一致 | 推进 VERIFIED | 三字段比较结果 |
| V95-07 | 需要回滚旧行为 | 新 code、新摘要 | RollbackPlan |
| V95-08 | 构建失败 | 保留已分配编号 | FAILED 构建记录 |
测试应同时准备正向与反向样本。只验证当前 1000000 / 1.0.0 / 1 能被解析,不能证明重复编号、覆盖值和多模块不一致会被门禁拦下。
十、常见版本错配与排查顺序
| 现象 | 先核对 | 常见原因 | 修复 |
|---|---|---|---|
| 市场认为不是新版本 | versionCode 与渠道最新值 | 只改了 versionName | 分配更大的 code |
| 设置页显示名称正确,测试包行为旧 | APP 摘要与源码修订 | 取到历史制品 | 重新生成来源回执 |
| 本地配置与 pack.info 不同 | 产品覆盖来源 | build-profile 覆盖或缓存 | 清理构建并回读有效值 |
| APP 打包时提示模块版本不一致 | 各 HAP/HSP buildVersion | 模块来自不同构建 | 同一 buildId 重新构建 |
| 回滚包无法覆盖安装 | 新旧 versionCode | 复用了旧二进制 | 用新编号重建回滚版本 |
| 同 code 有两份测试报告 | APP SHA-256 | 重打包未换编号 | 废弃冲突产物并登记原因 |
排查时先从渠道最新 versionCode、账本唯一键和 APP 摘要开始,再回到配置来源。仅比较展示名称,很容易把“用户看到的版本”错当成“系统判断的新旧顺序”。
十一、让每个版本只能指向一份可解释制品
三个版本字段不是三个相近的文案。versionCode 建立升级顺序,versionName 面向用户表达,buildVersion 追踪构建并约束多模块一致性;ReleaseVersionLedger 再把它们与源码、构建 ID、APP 摘要和渠道阶段绑定。这样一次发布失败、重试或回滚,都不会把同一编号解释成不同二进制。
本文只核对当前 app.json5、工程级产品配置与历史 pack.info 的公开版本字段,没有修改版本号,没有运行构建,没有生成或验签 APP/HAP,没有安装、升级、回滚或上传应用市场。账本模型、门禁与测试示例属于建议方案,接入时还需按目标 DevEco Studio、API 版本和渠道规则复核字段支持与发布流程。
更多推荐


所有评论(0)