构建按钮点过了,工程目录里也出现了新文件,为什么发布前评审仍然会问“这个包究竟是哪个模式打出来的”?因为构建成功、能够安装和可以提交分发,是三件需要分别证明的事情。前两篇已经为设备点检助手确定了 com.example.fieldinspector 这个示例应用身份,约定了 versionCode 的递增规则,也把证书、Profile 与签名配置之间的责任关系理清。本篇把目光移到真正产生文件的阶段:用同一份源码分别构建 Debug 与 Release,解释 product、target、buildMode 的边界,再把最终的 APP 候选包纳入可追溯的交付流程。

构建模式与发布产物技术封面

构建时,别把三个下拉选项理解成同一回事

在 DevEco Studio 里看到 default、debug、release 等字样时,最容易犯的错误是根据名字判断性质。实际上,product 说明要交付哪种产品组合,target 说明模块采用哪套目标配置,buildMode 才说明采取什么编译行为。对于只有一个入口模块的点检助手,完全可以让 product 和 target 都取 default,再分别切换 debug 与 release 构建模式。这样既保持应用身份稳定,又能在不复制工程的情况下调整调试信息、代码优化等编译行为。

HAP 与 APP 也不是“Debug 包”和“Release 包”的别称。HAP 是应用模块的打包形态,APP 则是面向应用分发的应用包形态;构建任务决定产物形态,构建模式决定编译选项,签名配置决定凭据关系。一个 Debug 模式构建出的 APP 仍然是 APP 文件,但它不是应该直接发送审核的正式候选包。反过来,一个叫 release 的目录也不自动保证内容不可调试,因为实际是否携带调试特性,仍要看合并后的 debuggable 等编译配置。官方文档明确指出:仅选择模式名称,并不足以替代对最终编译配置的核查。

product、target与buildMode的职责映射

下面这张职责图可以作为评审时的语言统一工具:问“给谁发布”,核对产品配置;问“哪个模块打进来”,核对 target 映射;问“如何编译”,看 buildMode 与 buildOption;问“最终交付什么”,再确认 assembleApp 等实际构建任务。把所有问题混成一句“用 Release 编译一下”,后续复现就只能靠猜。

用一套可审阅配置管理 Debug 和 Release

工程级 build-profile.json5 负责声明产品和模块映射。下面展示最小的结构关系:沿用前文示例工程的 entry 模块,不在文件里放任何生产密钥。真实工程的产品项若引用 signingConfig,需要指向已存在且由授权人员管理的配置,不能把演示内容当作有效签名凭据。

{
  "app": {
    "signingConfigs": [],
    "products": [
      {
        "name": "default",
        "compatibleSdkVersion": "26.0.0",
        "runtimeOS": "HarmonyOS"
      }
    ],
    "buildModeSet": [
      { "name": "debug" },
      { "name": "release" }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        { "name": "default", "applyToProducts": ["default"] }
      ]
    }
  ]
}

要把两种模式的差异落到 entry/build-profile.json5,可以按华为官方示例使用 buildOptionSet 与 buildModeBinder。其中,调试模式要求可调试,发布模式则启用混淆;混淆规则文件必须由工程真实维护,不能只写一个路径却不提供对应文件。以下代码是模块级片段的完整最小绑定结构,适合与工程其他配置合并,而不是覆盖掉已有模块配置。

{
  "apiType": "stageMode",
  "buildOptionSet": [
    {
      "name": "debug",
      "debuggable": true,
      "arkOptions": {
        "obfuscation": { "ruleOptions": { "enable": false } }
      }
    },
    {
      "name": "release",
      "debuggable": false,
      "arkOptions": {
        "obfuscation": {
          "ruleOptions": {
            "enable": true,
            "files": ["./obfuscation-rules.txt"]
          }
        }
      }
    }
  ],
  "buildModeBinder": [
    {
      "buildModeName": "debug",
      "mappings": [
        { "targetName": "default", "buildOptionName": "debug" }
      ]
    },
    {
      "buildModeName": "release",
      "mappings": [
        { "targetName": "default", "buildOptionName": "release" }
      ]
    }
  ],
  "targets": [{ "name": "default" }]
}

这里故意没有另造一个 release product:同一个 default 产品就能通过模式切换编译。日后确实需要国内版、海外版等不同产品组合,再单独引入 product 差异;不要仅为了让名字看上去像正式包,就复制一套可能漂移的应用配置。也不要仅凭“启用了混淆”就断言应用安全,网络通信、权限和敏感数据处理都有各自的安全边界,混淆属于编译层面的一项控制。

从一条构建命令走到一份候选包

对于支持该构建命令的 DevEco Studio/Hvigor 工程,在项目根目录调用包装脚本 hvigorw,可以明确指定同一产品的两种构建模式。下面的示例来自官方构建模式指南,Windows 环境应使用工程对应的包装脚本调用方式,且前提是本地 SDK、依赖与签名配置已就绪。

# 验证调试模式的 APP 构建路径
./hvigorw --mode project -p product=default -p buildMode=debug assembleApp

# 生成供发布流程进一步校验的 Release APP 候选包
./hvigorw --mode project -p product=default -p buildMode=release assembleApp

# 如果只需要入口模块的 HAP,则选择模块级构建任务
./hvigorw --mode module -p product=default -p module=entry@default \
  -p buildMode=release assembleHap

这里的 assembleApp 和 assembleHap 做的是不同的事,绝不是换个文件后缀。构建日志里能够看到任务执行,并不等于文件的签名、分发资料都已经核验。要区分“任务成功执行”和“产物达到发布要求”,至少再做三层检查:是否选择了预期的 product/target/buildMode,是否使用了获授权的 Release 签名材料,最后的 APP 文件是否与这次构建的版本和源码快照一致。

为避免把旧构建误当新文件,建议为每次候选发布建立单独的只读交付目录:先定位本次构建产物,再复制文件、计算 SHA-256,并把计算结果和源码提交号、bundleName、versionCode 一起登记。哈希用于验证传输与存档文件未发生变化,但不能替代数字签名验证。候选包的 Profile、证书、签名有效期和权限覆盖范围仍须通过受控工具及发布流程检查。

下面是一段独立的 Node.js 校验脚本,它不修改 DevEco 工程,只对已经人工确认位置的候选包进行文件完整性登记。Node.js 需要支持 node: 内置模块导入,运行时把真实的 .app 文件路径作为参数传入。

// tools/check-app.cjs(放在正式发布产物目录以外)
const fs = require('node:fs');
const crypto = require('node:crypto');
const path = require('node:path');

const packagePath = process.argv[2];
if (!packagePath || path.extname(packagePath).toLowerCase() !== '.app') {
  throw new Error('请传入本次构建的 .app 文件路径');
}
const file = path.resolve(packagePath);
const stat = fs.statSync(file);
if (!stat.isFile() || stat.size === 0) {
  throw new Error('APP 候选包不存在或为空');
}
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(file);
stream.on('data', chunk => hash.update(chunk));
stream.on('error', err => { throw err; });
stream.on('end', () => {
  console.log(JSON.stringify({
    file: path.basename(file),
    bytes: stat.size,
    sha256: hash.digest('hex')
  }, null, 2));
});

把这段结果复制到发布候选记录,只能说明某个确定路径下文件的哈希与体积。它不能检查包内应用身份,也不能证明签名合法;那些信息还要经官方提供的检验工具与分发平台流程确认。对需要审计的团队而言,这种“只做自己能证明的检查”的工具,比输出一个笼统的“发布通过”更可靠。

团队还可以为发布流水线约定一份最小的候选包信息表,不必把它塞进应用源码:候选版本号、产物文件名、构建模式、依赖锁文件版本、源代码提交号、签名材料责任人和校验哈希都可以成为字段。这里的“签名责任人”记录的是授权角色,不是明文密码。信息表应该随候选产物存放在受控交付空间,不能因为临时修改了输出目录就把验证记录留在旧位置。对于重复构建相同源码的情况,产物哈希未必必然一致,因此不能反向凭哈希变化判断业务代码发生了改变。质量检查应分别关注构建输入的可追溯性与交付文件的一致性。

用失败路径反推构建治理到底缺了什么

一个很常见的返工场景是:开发分支已经将 versionCode 设为新值,发布人员却从旧目录拿了上次的 APP。若只凭目录名识别,会误以为本次产物已经更新。避免这种问题,不必引入复杂平台,先要求每份候选包都有可对应的源码版本、编译参数、签名责任人和 SHA-256,不能满足就退回重新核对。另一个场景是有人将 debuggable 写成 true,却仍选择 release 模式,名称和行为冲突;这时应排查模块级选项、target 覆写以及命令行参数的合并优先级,而不是再新建一个 release2。

发布前构建核查与错误隔离

还需要留意几类边界:临时调整的编译常量不能泄漏到发布版;调试日志和密钥不能因为“构建得出来”就进入包体;混淆开启后依赖反射或字符串名称的模块要提前验证兼容;应用身份与版本要沿用前两篇定下的基线。对于未执行设备测试的方案,应该给出可执行的验证条件和失败时的排查路径,而不是凭空填写性能提升百分比或安装成功截图。

建立到这里,设备点检助手的发布链路已经形成了三个相互独立又能互相核对的环节:应用身份与版本、证书 Profile 与签名授权、构建模式与 APP 交付文件。最后一篇将围绕上架资料、权限隐私、质量自查与升级发布建立收尾检查,把“能够打包”推进为“有依据地申请发布”。

官方参考:Hvigor 构建模式与编译选项、Debug/Release 构建配置实践、定制多目标与多产品构建。

Logo

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

更多推荐