大家好,我是[晚风依旧似温柔],新人一枚,欢迎大家关注~

前言

HarmonyOS 工程里有一个很容易让人产生疑问的地方:项目根目录有 build-profile.json5,进入 entry 模块后,又能看到一个 build-profile.json5。

两个文件都和构建有关,甚至都能出现 buildOption。那么问题来了:同一个配置项在工程级和模块级都写了,到底听谁的?如果两边写的是不同配置,又会不会丢失?

官方对这个问题给出的规则其实很明确:工程级和模块级的 buildOption 会参与合并;从完整的编译选项继承覆写关系看,模块级 buildOption 的优先级高于工程级 products 中的 buildOption。换成更直观的话说,就是相同字段由更高优先级配置覆写,不同字段则可以沿继承关系保留下来。

这篇文章不扩展到复杂的多产品、多 Target 场景,而是做一个最小实验:利用 buildProfileFields 同时设置“同名字段”和“不同字段”,再观察最终构建配置。这样比单纯背优先级更容易理解。

【建议插图1:DevEco Studio Project 视图中根目录 build-profile.json5 与 entry/build-profile.json5 的位置对比】

一、先弄清楚:两个 build-profile.json5 管的不是同一层

先看一个典型工程结构:

MyApplication
├── AppScope
├── entry
│   ├── src
│   ├── build-profile.json5    ← 模块级
│   └── oh-package.json5
├── build-profile.json5        ← 工程级
├── oh-package.json5
└── hvigor

根目录的 build-profile.json5 是工程级构建配置。在这里可以描述应用的 products、模块列表、构建模式等;模块目录下的 build-profile.json5 则描述当前模块的 Target、模块构建选项等。华为官方分别提供了工程级和模块级 build-profile.json5 的字段说明。

这也是为什么两个文件不能简单理解成“重复配置”。

工程级配置更适合表达整个 Product 或构建模式的公共要求;模块级配置则可以继续针对某个具体模块细化。Hvigor 最终会按照规定的继承和覆写规则整合这些配置。

如果进一步把 buildOptionSet、Target 和命令行也算进来,官方给出的整体继承覆写顺序是:

命令行配置
    ↓
targets 中的 buildOption
    ↓
模块级 buildOptionSet
    ↓
模块级 buildOption
    ↓
工程级 products 中的 buildOption
    ↓
工程级 buildModeSet 中的 buildOption

这里真正需要记住的不是一长串名字,而是:离具体构建目标越近的配置,可以在规定的继承链上覆写更通用的配置。

所以本文只比较“工程级 Product”与“模块级公共 buildOption”时,结论就是:

模块级 buildOption
        >
工程级 products.buildOption

需要注意,这个结论有明确的比较范围。如果模块的 targets[].config.buildOption 或 buildOptionSet 又配置了相同字段,还需要继续按照完整优先级判断,不能简单套一句“模块级永远最高”。

二、HarmonyOS 7 下需要关注什么版本问题

截至本文整理时,华为官方已经发布 HarmonyOS 7,对应 API 版本为 26.0.0。官方升级适配指南明确说明,HarmonyOS 7.0 对应 API 26.0.0,并建议升级到相应开发套件进行适配。

不过,这篇文章讨论的是 DevEco Hvigor 编译构建配置,不是某个运行时系统 API。

因此这里没有需要申请的系统权限,也不涉及某个 Kit、ArkUI 组件或 Phone/Foldable/Tablet 专属能力。不要为了文章以 HarmonyOS 7 为背景,就给 build-profile.json5 生硬套一个“API 26 才支持”的结论。

本文实验可以放在 HarmonyOS 7 / API 26.0.0 工程中进行,但真正应该核对的是当前 DevEco Studio、Hvigor 及配置文件字段是否匹配,而不是去 module.json5 里申请权限。

另外,从 26.0.0 开始,HarmonyOS 开发套件 API 版本采用 X.Y.Z 的语义化版本格式,这也是新工程中可能看到 26.0.0 而不是旧式版本表示的原因。

三、为什么用 buildProfileFields 做这个实验

直接找一个打包、Native 或混淆配置来验证优先级当然也可以,但会引入很多额外变量。

这里选择 buildProfileFields,原因是它本身就是官方提供的自定义编译参数能力:开发者可以在 build-profile.json5 中增加自定义字段,并通过生成的 BuildProfile 获取构建参数。官方资料同时给出了工程级、模块级自定义参数的使用方式。

我们设计三个字段:

字段工程级模块级用途
sameKey"fromProject""fromModule"验证同名字段谁覆盖谁
projectOnly"projectValue"不配置验证工程级字段能否被继承
moduleOnly不配置"moduleValue"验证模块自己的字段能否加入最终配置

这样只需要观察三个值,就能把“覆写”和“继承”分开。

四、搭一个最小实验

假设工程只有一个 entry 模块,Product 和 Target 都使用默认的 default。

这里不要直接拿下面代码覆盖自己项目里的整个配置文件,因为实际工程通常还包含签名、模块列表、SDK 版本等内容。只需要把实验涉及的 buildOption 合并到现有对应位置。

1. 工程级 build-profile.json5

在根目录 build-profile.json5 的 app.products 中找到当前使用的 default Product,在它的 buildOption 中增加:

{
  "app": {
    "products": [
      {
        "name": "default",

        // 这里保留工程原有的 signingConfig、
        // compatibleSdkVersion、runtimeOS 等配置。

        "buildOption": {
          "arkOptions": {
            "buildProfileFields": {
              "sameKey": "fromProject",
              "projectOnly": "projectValue"
            }
          }
        }
      }
    ]
  }

  // modules 等原有配置继续保留
}

这一步定义两个工程级参数。

sameKey 故意准备与模块级发生冲突;projectOnly 则只在工程级出现。

【建议插图2:根目录 build-profile.json5 中 default Product 的 buildProfileFields 配置】

2. entry 模块级 build-profile.json5

然后进入:

entry/build-profile.json5

在模块级 buildOption 中配置:

{
  "apiType": "stageMode",

  "buildOption": {
    "arkOptions": {
      "buildProfileFields": {
        "sameKey": "fromModule",
        "moduleOnly": "moduleValue"
      }
    }
  }

  // targets 等模块原有配置继续保留
}

这里也放两个参数。

其中:

sameKey = fromModule

和工程级的:

sameKey = fromProject

发生冲突。

而:

moduleOnly = moduleValue

只存在于模块级。

现在实验条件就准备好了。

五、最终应该看到什么

按照官方的继承覆写规则,对于本文限定的 Product buildOption 与模块级公共 buildOption 两层配置,预期合并结果应当是:

sameKey     = fromModule
projectOnly = projectValue
moduleOnly  = moduleValue

也就是:

参数最终值原因
sameKeyfromModule同名配置发生覆写,模块级优先级更高
projectOnlyprojectValue模块级没有重新定义,工程级配置被继承
moduleOnlymoduleValue模块级新增字段正常进入最终配置

注意这里说的是依据官方规则得到的预期结果,不是声称本文已经在某台设备上完成真机测试。

而且这个实验本身不需要真机,因为我们验证的是编译配置合并,不是设备运行行为。

六、怎么查看最终配置,而不是靠猜

这里有两种很实用的观察方法。

方法一:生成 BuildProfile

官方的自定义编译参数文档说明,构建过程中可以生成 BuildProfile 类,buildProfileFields 中的自定义参数会进入对应构建配置。

可以在 DevEco Studio 中选择相应模块后使用生成 Build Profile 的功能,也可以按照官方文档提供的方式执行:

hvigorw GenerateBuildProfile

然后检查对应模块生成目录中的 BuildProfile.ets。

我们真正关心的不是文件中其他自动生成字段,而是搜索:

sameKey
projectOnly
moduleOnly

如果当前工程配置与本文实验一致,按照官方规则,三个字段应分别对应:

fromModule
projectValue
moduleValue

【建议插图3:生成的 BuildProfile.ets 中 sameKey、projectOnly、moduleOnly 三个字段】

方法二:使用 buildInfo 查看构建配置

较新的 Hvigor 还提供了 buildInfo 任务,用来打印工程级或模块级 build-profile.json5 配置信息;官方命令行文档说明该能力从 hvigorw 5.18.4 开始支持。

因此,使用前先确认本机 hvigorw 版本。满足版本条件时,可以针对模块查看 buildOption,例如:

hvigorw buildInfo -p module=entry -p buildOption

需要机器可读结果时,还可以结合官方提供的 JSON 输出参数。

这种方式很适合排查复杂工程:不用仅凭肉眼在两个甚至多个配置层之间来回寻找,而是直接观察 Hvigor 整理后的构建信息。

【建议插图4:Terminal 中执行 buildInfo 后查看 entry 模块 buildOption 的输出】

七、这个实验真正说明了什么

1. “模块级优先”不是说工程级配置失效

这是最容易出现的理解偏差。

比如工程级:

"buildProfileFields": {
  "sameKey": "fromProject",
  "projectOnly": "projectValue"
}

模块级:

"buildProfileFields": {
  "sameKey": "fromModule",
  "moduleOnly": "moduleValue"
}

最终不是简单把工程级整个对象扔掉。

需要区分“同一个配置项发生覆写”和“没有冲突的配置继续继承”。因此理解 build-profile.json5 时,用“继承 + 覆写”比“谁把谁全部替换掉”更准确。

2. 工程级适合放公共配置,模块级适合做差异化

如果一个配置希望多个模块共享,可以优先考虑工程级配置;某个模块确实需要不同值,再在模块级进行覆盖。

官方 FAQ 里也有一个很有代表性的场景:当 HSP 使用了 HAP 未定义的自定义 BuildProfileFields 时,可以在 HAP 中配置相同参数,也可以将相同自定义参数配置到工程级 build-profile.json5,从而让其全局生效。

这说明工程级配置并不是“没什么用的默认文件”,它承担的恰恰是更公共的一层构建配置。

3. 不要只记“工程级 < 模块级”

一旦工程复杂起来,还有:

buildModeSet
products
模块 buildOption
buildOptionSet
targets.config.buildOption
命令行

官方当前的构建模式实践中也明确区分了工程级与模块级配置,并展示了 Product、buildMode、模块 buildOptionSet 等配置位置。

所以遇到“明明修改了模块 buildOption,为什么还是不是这个值”时,下一步就应该检查 buildOptionSet、Target 或命令行有没有更高优先级配置,而不是继续修改工程级文件。

4. build-profile.json5 和 module.json5 不要混在一起

build-profile.json5 主要属于编译构建配置。

权限、Ability 等应用/模块声明则有自己的配置位置。本文实验完全不需要增加 module.json5 权限。

看到一个功能“不生效”就到 module.json5 增加字段,是配置类问题里很危险的一种排查方式:字段属于哪个配置文件,应当先以官方配置文件说明为准。

八、实际项目中建议这样排查

遇到构建参数与预期不一致,可以按照下面的顺序缩小范围:

  1. 确认当前 Product、Target 和 Build Mode。 不同 Product、Target、构建模式可能进入不同的配置分支。
  2. 检查工程级 products[].buildOption。 先确认公共配置从哪里进入。
  3. 检查模块级 buildOption。 看相同字段是否已经在模块中覆写。
  4. 继续检查 buildOptionSet 和 targets[].config.buildOption。 它们的优先级还高于模块级公共 buildOption。
  5. 检查命令行参数。 官方规则中命令行处于更高优先级,某些参数可以对已经整合的配置进行定点覆写。
  6. 不要只靠肉眼判断。 能使用 BuildProfile 或当前 Hvigor 支持的 buildInfo 时,直接查看最终构建信息。

如果使用 buildProfileFields 时出现:

Property xxx does not exist on type 'typeof BuildProfile'

也可以优先检查当前模块 build-profile.json5 中是否真正配置了对应自定义字段。华为官方的编译构建 FAQ 对这类问题给出了专门的排查说明。

开发经验总结

两个 build-profile.json5 并不是重复文件,而是 Hvigor 构建配置的不同层级。

对本文最小场景,可以把规则压缩成一句话:

工程级给公共值,模块级做差异化;
同名字段按优先级覆写,不冲突字段沿继承关系保留。

但到了真实项目,不建议只背“模块级大于工程级”。完整的构建配置还可能受到 buildModeSet、Product、模块 buildOption、buildOptionSet、Target 以及命令行影响。真正可靠的办法,是先确认当前构建组合,再沿官方规定的继承覆写关系逐层检查。

这个小实验还有一个很适合继续扩展的方向:把 sameKey 再分别放进 buildOptionSet 和 targets[].config.buildOption,然后继续观察最终值。这样就能把一张抽象的“配置优先级图”,变成自己工程里可以逐层复现的构建实验。

参考资料

  1. 《模块级build-profile.json5文件》
    华为开发者联盟
    链接:HarmonyOS 官方文档:模块级 build-profile.json5

  2. 《工程级build-profile.json5文件》
    华为开发者联盟
    链接:HarmonyOS 官方文档:工程级 build-profile.json5

  3. 《能力说明——灵活定制编译选项》
    华为开发者联盟
    链接:HarmonyOS 官方文档:灵活定制编译选项

  4. 《获取自定义编译参数》
    华为开发者联盟
    链接:HarmonyOS 官方文档:获取自定义编译参数

  5. 《实践说明——灵活定制编译选项》
    华为开发者联盟
    链接:HarmonyOS 官方文档:编译选项实践说明

  6. 《应用升级适配指导——向26.0.0升级》
    华为开发者联盟
    链接:HarmonyOS 7 / API 26.0.0 升级适配官方指南

  7. 《版本号格式调整说明》
    华为开发者联盟
    链接:HarmonyOS API 版本号格式官方说明

  8. 《编译构建常见问题》
    华为开发者联盟
    链接:HarmonyOS 官方编译构建 FAQ

如果觉得有帮助,别忘了点个赞+关注支持一下~
喜欢记得关注,别让好内容被埋没~

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐