一套HarmonyOS应用从业务开发进入版本交付时,最容易漏看的往往不是页面,而是散落在工程不同位置的配置。一个版本名已经调整,另一处构建产品仍沿用旧包名;某个模块改成了新入口,交付清单却记录着上一版的设备范围。只看界面无法回答“交付的究竟是哪一套配置”。我更倾向于把审查入口放在配置快照上:先收集事实,再按明确的规则检查,最后把问题定位到具体字段,而不是依赖人工逐个打开文件。

发布配置快照与关键字段

这套交付审查工作台与业务应用分开设计。它负责读取开发阶段已经确定的配置信息,生成审查记录,协助开发者发现包标识、版本号、产品与模块之间的矛盾;它不替代构建工具,也不会根据一张清单推断安装包已经符合发布条件。这样划分很重要,因为配置来源、编译时覆盖和最终产物是三个层次。把它们混在一个对象里,定位问题时很难知道错误来自哪一层。

配置到底从哪里取

Stage模型的工程里,AppScope/app.json5承载应用基本信息,工程级build-profile.json5负责构建产品等选项,entry/src/main/module.json5承载模块信息。以版本核对为例,bundleName、versionCode和versionName应该在快照中留下字段来源。某些构建产品可以覆盖全局配置,因此“app.json5里写对了”并不等于所选择产品的最终值也正确。检查时先确定当前目标产品,再比较其有效配置,最后才形成交付视图。

我把源数据拆成原始应用字段、产品选择与模块摘要三组。原始值保留给定位过程,经过构建配置优先级处理的值用于交付判断,字段路径则用来生成可操作的修改建议。为了让逻辑可以单独检查,核心对象只使用普通ArkTS类型,不引入不存在的系统配置读取接口。

export interface AppIdentity {
  bundleName: string
  versionCode: number
  versionName: string
}

export interface ProductSetting {
  name: string
  bundleName?: string
  compatibleSdkVersion: string
  moduleNames: string[]
}

export interface ReleaseSnapshot {
  productName: string
  effectiveBundle: string
  versionCode: number
  versionName: string
  compatibleSdkVersion: string
  includedModules: string[]
}

export function makeSnapshot(app: AppIdentity,
  product: ProductSetting): ReleaseSnapshot {
  return {
    productName: product.name,
    effectiveBundle: product.bundleName ?? app.bundleName,
    versionCode: app.versionCode,
    versionName: app.versionName.trim(),
    compatibleSdkVersion: product.compatibleSdkVersion,
    includedModules: [...product.moduleNames]
  }
}

这里的ProductSetting是工作台自己的归一化模型,不是build-profile.json5的原始数据结构。工程读取层负责把products与modules.targets整理成它,避免审查层依赖配置文件中每一种嵌套写法。同样,示例采用产品覆盖包名这一明确场景;更多版本字段如果由构建参数覆盖,也应在读取层归并,而不是在审核函数里偷偷补默认值。

从文件到可重现的输入

JSON5允许注释和尾随逗号,直接使用JSON.parse处理工程配置会出错。开发机脚本可借助经过项目锁定版本的JSON5解析库读取源文件,再以规范对象传给审查逻辑。脚本运行在开发机的Node.js环境,不是在HarmonyOS应用页面里访问工程目录。将两者分开,还可以避免把本地路径和签名材料打入业务页面。

import { readFile } from 'node:fs/promises'
import { parse } from 'json5'

async function readJson5(path) {
  const source = await readFile(path, 'utf8')
  return parse(source)
}

const appConfig = await readJson5('AppScope/app.json5')
const buildConfig = await readJson5('build-profile.json5')
const moduleConfig = await readJson5('entry/src/main/module.json5')
const product = buildConfig.app.products.find(p => p.name === 'release')
if (!product) throw new Error('未找到release构建产品')

const snapshotInput = {
  app: appConfig.app,
  product,
  moduleName: moduleConfig.module.name,
  moduleType: moduleConfig.module.type
}
console.info(JSON.stringify(snapshotInput, null, 2))

脚本使用的json5需要在开发机项目中安装并锁定依赖版本。这里没有让它自动写回配置,因为审查环节不宜悄悄改变待交付输入。对于产品名不存在、文件缺失、JSON5语法错误等情况,应立即给出文件路径和原因,停止生成该产品的快照。与其制造一份信息不全的清单,不如把缺失本身作为阻断项。

判断规则应当能解释

配置审查不需要一套神秘评分,而需要几条足够清晰的规则:有效包名不能为空;版本码应是正整数并且相对于已发布版本递增;版本名称不能为空;选择的产品必须有适用模块;模块引用必须与工程里的实际模块名匹配。至于兼容SDK版本与目标设备的关系,不能简单靠数字大小下结论,还要根据当前构建产品的实际目标和所采用接口的最低版本检查。

export interface ReviewIssue {
  field: string
  level: 'block' | 'review'
  message: string
}

export function inspectRelease(s: ReleaseSnapshot,
  previousCode: number, availableModules: string[]): ReviewIssue[] {
  const issues: ReviewIssue[] = []
  if (!/^[a-zA-Z][\w]*(\.[a-zA-Z][\w]*){2,}$/.test(s.effectiveBundle)) {
    issues.push({ field: 'bundleName', level: 'block', message: '包名格式需要核对' })
  }
  if (!Number.isInteger(s.versionCode) || s.versionCode <= previousCode) {
    issues.push({ field: 'versionCode', level: 'block', message: '版本码未高于上一发布版' })
  }
  if (s.versionName.length === 0) {
    issues.push({ field: 'versionName', level: 'block', message: '版本名称为空' })
  }
  if (s.includedModules.length === 0) {
    issues.push({ field: 'modules', level: 'block', message: '产品没有关联模块' })
  }
  s.includedModules.forEach(name => {
    if (!availableModules.includes(name)) {
      issues.push({ field: `modules.${name}`, level: 'block', message: '模块名无法解析' })
    }
  })
  return issues
}

这些规则里的包名正则用于工作台的基础检查,还需要以华为开发者关于包名长度、字符范围和相关服务的规定为准。previousCode必须来自已发布版本记录,而不是当前源码中的常量;否则开发者把源码退回旧分支时,很可能连比较基准一起退回。把基准值与配置快照分开保存,可以看清“候选版本”和“历史基线”的差异。

开发环境中的发布字段核对

界面呈现不能隐藏输入来源

我习惯让列表每一行对应一个字段和一条判断结果。点击问题后展开“有效值、来源文件、期望条件”,而不是只给一个红色图标。用户可以在切换产品时看到整份结果重新计算,但上一产品的错误不能遗留在新产品的列表里。因此页面的状态更新应以完整结果替换旧数组,计算过程尽量保持同步与纯函数特征。

@Entry
@Component
struct ReleaseReviewPage {
  @State productName: string = 'release'
  @State issues: ReviewIssue[] = []
  private app: AppIdentity = {
    bundleName: 'com.example.releaseaudit',
    versionCode: 18,
    versionName: '1.8.0'
  }

  private check(): void {
    const current: ProductSetting = {
      name: this.productName,
      compatibleSdkVersion: '26.0.0',
      moduleNames: ['entry']
    }
    this.issues = inspectRelease(makeSnapshot(this.app, current), 17, ['entry'])
  }

  aboutToAppear(): void {
    this.check()
  }

  build() {
    Column({ space: 16 }) {
      Text('交付审查').fontSize(25).fontWeight(FontWeight.Bold)
      Text(`构建产品:${this.productName}`)
      Button('重新核对').onClick(() => this.check())
      List({ space: 8 }) {
        ForEach(this.issues, (item: ReviewIssue) => {
          ListItem() {
            Column({ space: 5 }) {
              Text(item.field).fontWeight(FontWeight.Medium)
              Text(item.message).fontColor(Color.Red)
            }.alignItems(HorizontalAlign.Start).padding(12)
          }
        }, (item: ReviewIssue) => item.field + item.message)
      }.layoutWeight(1)
    }.padding(20).width('100%').height('100%')
  }
}

页面中将示例输入固定在组件内部,是为了把状态刷新方式讲清楚。接入真实工作台时,应把读取层提供的快照注入页面,并给每次异步加载附上产品标识;如果用户切换到另一个产品,旧请求返回的检查结果必须丢弃。审查结果为空也不应直接写“可发布”,更合适的表达是“当前规则未发现阻断项”,因为签名、包体和商店资料仍有独立检查步骤。

调试时把变更前后放在同一张表

较容易定位的一组输入是:源码包名为com.example.releaseaudit,构建产品覆盖为com.example.releaseaudit.pro,上一发布版本码为17,候选版本码为18。打开该产品后,界面应显示有效包名采用产品值,而不使用源码值;如果候选版本码调整为16,应出现版本码阻断项。另一个用例是删除entry模块引用,预期检查结果定位到modules.entry。这些是可以写成输入断言的规则,不需要依靠截图判断逻辑是否正确。

版本配置核对工作台

这里还要警惕缓存。工程配置被修改后,如果工作台继续显示旧快照,开发者可能以为问题已修复。读取层可记录文件内容摘要和产品名,内容变化时生成新的快照;UI通过快照标识更新所有字段。对于签名证书路径、密码等敏感构建资料,列表不应回显内容,最多显示配置项是否存在。审查文本应保持可复制和可追溯,但不能把秘密一起写进导出文件。

把这一步留给后面的检查

发布配置快照解决的是“交付目标是否明确”。它既不能代替权限与功能使用场景的核对,也不能判断第三方依赖的版本是否可复现,更不能替代最终包体与商店资料的比对。按照这个顺序推进,下一篇将以相同的产品快照为输入,把module.json5中的权限集合与页面上实际启用的功能逐一对应,找出缺少授权理由、已经废弃却未清理的权限,以及仅由系统选择器完成的能力是否被多报了权限。

版本字段相关资料可参考华为开发者的工程多产品与多目标构建指南、构建配置覆盖实践及AppGallery Connect发布证书说明。三者分别对应产品选择、配置优先级与后续发布阶段需要核对的签名类别。到这里,版本审查不再是抄录几个字符串,而是一份知道来源、基准和有效值的可复核配置记录。

Logo

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

更多推荐