灯光模拟HarmonyOS应用实战-95-versionCode、versionName与buildVersion都存在为何仍会发错包:用ReleaseVersionLedger绑定升级证据

app.json5 里已经写了 versionCodeversionNamebuildVersion,并不等于版本治理已经完成。只要团队允许同一个 versionCode 对应两份不同字节的 APP,或构建产品覆盖了配置却没有在制品侧回读,应用市场、测试人员和开发者看到的“1.0.0”就可能不是同一个包。

The_kemusan/AppScope/app.json5 当前给出 versionCode=1000000versionName=1.0.0buildVersion=1;已有 pack.info 也记录了同一组三元值。静态一致只能说明这份配置与这份历史描述文件表面相符,不能证明它们对应今天要交付的源码,更不能证明 1000000 尚未在其他发布中使用。

本文把三个字段放进 ReleaseVersionLedger:分配、构建、包内回读、制品摘要与发布结论必须落到同一条不可变记录。第 94 篇关注“产物从哪里来”,本篇只解决“新旧版本如何排序、展示与追踪”。

三个版本字段如何对账

一、先把当前三元组当成事实,不当成发布结论

当前应用级配置可以缩成下面这段:

{
  "app": {
    "bundleName": "com.example.the_kemusan",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "buildVersion": "1"
  }
}

build-profile.json5products/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哪一次构建多模块值不一致或无法追踪同一发布单元保持一致

三者不应相互替代。把 versionName1.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-03versionName 改了、code 未增加阻止新发布最新渠道记录
V95-04产品覆盖版本记录来源为 PRODUCT_OVERRIDE有效配置快照
V95-05entry 与 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 版本和渠道规则复核字段支持与发布流程。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐