在大型 HarmonyOS 工程里,三方库的问题不总是“某个库装不上”。更常见的是模块搬迁、重命名或者替换成本地源码以后,entry/oh-package.json5 的依赖键还沿用旧名字,而被引用目录中的包声明已经改了。这类配置在某些安装条件下可能先给出警告,到了更严格的构建配置里却变成错误。把这个问题留给正式发布流水线,会让本来可提前发现的变更拖慢交付。

本文采用 DepNameGate 演示工程,构造检查批次 DEP-510。入口模块声明四个依赖,其中有两个 file: 本地目录依赖;示例配置中 scene-widget 指向一个实际包名为 scene-core 的目录,另一个 photo-core 声明正确。示例的扫描摘要固定为 declared=4 / local=2 / mismatch=1 / BLOCKED。这些数据仅用于讲清静态门禁,不是一次真实 ohpm install 或应用上架记录。

一、配置差异不是一个“安装失败”这么简单

官方 oh-package.json5 文档明确区分项目级和模块级配置:项目根目录适合放依赖覆盖等全局规则,模块级文件用于声明该模块名称、版本和依赖。dependencies、devDependencies 与 dynamicDependencies 的用途不同,不应该为了让工具少报错就把全部包统一挪到一个节点。官方还说明,本地依赖声明键与依赖包自身的 name 不一致时可能出现警告;在指定更严格的约束后,这种差异可以被升级为报错。

这意味着发布前检查不需要伪装成 ohpm 本身,而应该提早发现团队明确约定的“不允许别名漂移”。本文选了最能做静态解释的 file: 目录依赖:从声明文件所在路径出发,解析被指向目录里的 oh-package.json5,比较当前依赖键与目标 name。远程 semver 解析、本地 .har 二进制元数据和动态 HSP 安装行为,仍交给 ohpm 官方工具执行,避免小脚本越权做出错误结论。

特别说明:官方不建议开发者或外部脚本直接读取 oh-package-lock.json5 进行分析。本方案不会解析锁文件,不会依据它猜测实际安装版本,也不提供“锁文件已核验一致”之类不真实的承诺。锁文件应该继续由 ohpm 维护并提交版本控制,真正安装时仍使用官方命令。

二、先把真实路径和包名摆在一起

为了让失配可复现,构造项目路径 DepNameGate/entry/oh-package.json5,旁边还有 scene-core/oh-package.json5 与 photo-core/oh-package.json5。下面把三份示例配置放在同一个代码段,是为了突出声明值与被引用包内的 name 的区别。它们应分别放在各自的文件中,不能把三份对象粘进同一 JSON5 文件。

// entry/oh-package.json5 (演示四项声明)
{
  "name": "entry", "version": "1.0.0",
  "dependencies": {
    "scene-widget": "file:../scene-core",
    "photo-core": "file:../photo-core",
    "@ohos/lottie": "^2.0.0",
    "route-helper": "^1.0.0"
  }
}
// scene-core/oh-package.json5(另一个独立文件)
{ "name": "scene-core", "version": "1.0.0" }
// photo-core/oh-package.json5(第三个独立文件)
{ "name": "photo-core", "version": "1.0.0" }

第一项的键是 scene-widget,目标模块却声明 scene-core;第二项两边都是 photo-core。扫描器应把第一项判为 NAME_MISMATCH,把第二项判为 MATCHED。其余两项是示例远程依赖,脚本不假定本机网络可访问,更不会编造它们已经下载成功。示例中的版本字符串仅是演示配置,不代表当前仓库里这些第三方包一定存在或推荐使用该版本。

需要重视路径基准:file:../scene-core 是相对声明它的模块目录进行解析,不是相对当前终端所在目录拼接。脚本在 CI 服务器中如果依赖 process.cwd() 去拼每个 file: 目标,工作目录一变化就容易把合法依赖误判成缺失。保留声明文件的实际路径,才能让本地与 CI 用同一个判断规则。

三、扫描器只做它确实知道的事情

第二段代码是主要的 Node.js 脚本。oh-package.json5 支持 JSON5 语法,不能直接用 JSON.parse 读取含注释或尾随逗号的配置。这里通过单独的开发工具依赖 json5 解析;使用前在仓库的 Node.js 脚本工具目录执行 npm install --save-dev json5 并将安装配置纳入构建环境,不能假设它是 HarmonyOS SDK 自带的 API。使用 node:fs 与 node:path 属于 Node.js 工具链,不会被打进 ArkTS 设备运行侧。

// scripts/check-local-names.mjs(Node.js 工具代码)
import fs from 'node:fs';
import path from 'node:path';
import JSON5 from 'json5';

function readPackage(file) {
  return JSON5.parse(fs.readFileSync(file, 'utf8'));
}
export function scanLocalNames(moduleFile) {
  const pkg = readPackage(moduleFile);
  const issues = [];
  let local = 0;
  const declarations = { ...pkg.dependencies, ...pkg.devDependencies };
  for (const [declared, locator] of Object.entries(declarations)) {
    if (typeof locator !== 'string' || !locator.startsWith('file:')) continue;
    const ref = locator.slice('file:'.length);
    if (ref.endsWith('.har') || ref.endsWith('.hsp') || ref.endsWith('.tgz')) continue;
    local++;
    const target = path.resolve(path.dirname(moduleFile), ref, 'oh-package.json5');
    if (!fs.existsSync(target)) {
      issues.push({ declared, actual: '(missing)', reason: 'PACKAGE_NOT_FOUND' });
      continue;
    }
    const actual = String(readPackage(target).name ?? '');
    if (actual !== declared) issues.push({ declared, actual, reason: 'NAME_MISMATCH' });
  }
  return { declared: Object.keys(declarations).length, local, issues };
}

readPackage 负责严格解析 JSON5;local 统计脚本明确支持的本地目录引用;issues 是能落实到声明键和实际包名的差异记录。这里没有去猜 @module: 的完整解析规则,也没有把 .har 当作目录强行读取。对于不能识别的链接类型,应留给官方工具而不是打上虚假的 MATCHED。如果实际工程还使用了多模块共享依赖或参数化映射,需要在另一个明确约束的扫描器中扩展,而不能悄悄扩大当前规则的适用面。

这个片段也有要补强的生产细节。第一,正式使用时应做仓库根目录的路径边界检查,防止错误配置把读取范围引到不相关目录;第二,需要处理重复依赖键和解析异常,避免脚本中断后没有明确错误描述;第三,要确认团队在 devDependencies 中的本地路径也采用同一命名规范;第四,受控环境下可对每个依赖输出包名、解析路径以及本轮校验时间,便于定位回归。

四、让不一致在打包前变成清晰的失败码

检测脚本的价值在于尽早、可解释地阻断,而不是生成一张很漂亮的统计图。下面的第三段代码读取 entry/oh-package.json5,输出固定格式的摘要,并在存在问题时设置非零退出码。CI 可采用 node scripts/run-gate.mjs && ohpm install --all 串联流程,只有看到退出码 0 才继续执行后续安装或构建。这里不真正调用平台打包,也不会把 process.exitCode=1 解释成上架系统的审核结果。

// scripts/run-gate.mjs
import path from 'node:path';
import { scanLocalNames } from './check-local-names.mjs';

const result = scanLocalNames(path.resolve('entry/oh-package.json5'));
const blocked = result.issues.length > 0;
const state = blocked ? 'BLOCKED' : 'READY';
console.log(`DEP-510 declared=${result.declared} local=${result.local}`);
for (const issue of result.issues) {
  console.error(`DEP-510 ${issue.reason} ${issue.declared} -> ${issue.actual}`);
}
console.log(`DEP-510 mismatch=${result.issues.length} phase=${state}`);
process.exitCode = blocked ? 1 : 0;

依照前面的构造配置,这段代码预期产生 DEP-510 declared=4 local=2、DEP-510 NAME_MISMATCH scene-widget -> scene-core、DEP-510 mismatch=1 phase=BLOCKED。如果脚本缺少 json5 依赖或者配置文件根本不存在,进程也会失败,但这是环境错误,不能与命名失配混淆。更完善的版本应加显式 try/catch,使用不同的诊断码区分 PARSE_ERROR、MISSING_PACKAGE 和业务规则违例。

白色 DevEco Studio 风格配图展示的也是模拟开发场景:左侧目录分别可见 entry 和 scene-core,中间是检查脚本,右侧是一个展示规则结果的概念 UI,底部是 DEP-510 的示例日志。右侧状态页不是 DevEco Studio 内置的真实“ohpm 审核器”,只是为文章设计的诊断看板。若要做实际 CI 验收,应查看终端退出码、构建日志和官方 ohpm 的安装输出。

五、别把自定义规则误写成平台政策

官方文档存在一个关键边界:依赖键和目标包名不一致,并非在所有场景下都会立即被拒绝;某些配置只警告,另外一些严格模式配置会报错。本例的 BLOCKED 是团队自己设定的发布前门禁,不是官方应用市场对所有项目的统一政策。文中 scene-widget 和 scene-core 这样的示例名称也不对应某个已发布三方库的真实审核结论。

如果团队是有意用别名兼容旧调用,也不能偷偷把校验关掉。应增加有理由、有到期日的例外清单,例如迁移窗口内准许 scene-widget 映射到 scene-core;到期以后再把它提升成阻断。这样规则既能阻止无意漂移,也能支持有计划的重构。尤其对本地 HAR、HSP 和动态模块,依赖名与构建产物、模块映射之间的关系比目录依赖复杂,不能套用本例的一条 name 比较逻辑。

测试上至少准备三组输入:正确匹配的本地目录、命名不一致的本地目录、目标 oh-package.json5 丢失的目录。再补两组边界:注释与尾随逗号能被 JSON5 正确解析、远程版本范围应被跳过而非误判。CI 最后还要执行真正的安装及构建,它们才是依赖图、版本选择和平台兼容性的一手反馈。静态门禁减少低级失误,不能替代官方工具的最终决策。

手机运行配图用概念诊断页显示 DEP-510 / declared 4 / local 2 / mismatch 1 / BLOCKED,让团队成员不读脚本也能理解被阻断的是哪条声明。图中页面属于演示应用,绝不是手机系统自带的包依赖管理功能;实际 Node.js 扫描应运行在开发机或 CI 主机。文章把演示日志与真实安装结果严格分开:本文没有执行 ohpm install,没有打出 .hap,没有提交应用市场审核。

六、收束为一条可维护的边界

最容易失控的不是一条错别名,而是工具越做越大:先比较本地目录包名,随后擅自解析锁文件,再根据未知字段推断依赖解析器的行为。这样写出来的“发布通过率”很可能没有平台依据。一个小型脚本应清楚声明自己检查什么、忽略什么、什么时候退出,以及后面必须再交给哪个官方工具确认。

对于 DepNameGate,判断很明确:两个受支持的本地目录声明里,photo-core 一致,scene-widget 与 scene-core 不一致,于是规则输出 mismatch=1 并阻断下游构建。真实项目接入时,要先审查依赖表的历史使用方式,再决定是否启用更严格的 .ohpmrc 配置,避免将存量兼容路径误杀。

参考资料:华为官方《oh-package.json5》https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-hmos-oh-package-json5;华为官方《添加依赖项》https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-hmos-hvigor-dependencies;华为官方《ohpm install 错误码》https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-hmos-ohpm-install-errorcode 。

Logo

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

更多推荐