HarmonyOS 二开前先查本地依赖闭环:一个零依赖 oh-package 审计器
HarmonyOS 二开前先查本地依赖闭环:一个零依赖 oh-package 审计器
问题从哪里来
HarmonyOS 工程常用 oh-package.json5 里的 file: 依赖拆分公共模块和业务模块,例如:
{
"dependencies": {
"common": "file:../common",
"routedesign": "file:../feature/RouteDesign"
}
}
这种写法在单仓库开发时很方便,但它把一组隐式契约留给了构建阶段:路径必须存在、目标目录必须有包清单、依赖别名要和目标 name 一致、配置了 main 时入口文件也不能缺。任意一项断裂,真正的问题往往被后续的依赖解析错误、模块找不到或构建中断掩盖。
本次用鸿蒙开发实践中心的公开案例仓库做验证:
- 案例:https://harmonybp.developer.huaweicloud.com/cases/c5f78fbd5a5e48d98857520b3c8f6565
- 仓库:https://gitcode.com/harmony-practice-center/HarmonyOS-APPTMPL-bustravel
它不是用肉眼逐项核对,而是把“本地依赖闭环”变成一个可重复、可进 CI 的检查。
审计器检查什么
脚本只用 Python 标准库,递归读取工程内所有 oh-package.json5,跳过 node_modules 和 oh_modules。对每一条 file: 依赖执行四项检查:
- 目标目录是否存在;
- 目标目录中是否存在
oh-package.json5; - 依赖别名是否与目标包的
name一致; - 若目标包声明了非空
main,对应文件是否存在。
远程依赖(例如 @ohos/axios)不参与本地路径检查,实际构建仍负责它们的下载与版本解析。
核心实现
解析 JSON5 时不能直接用 json.loads(),因为真实工程常见单引号、注释和尾逗号。这里采用范围有限的扫描策略:先屏蔽注释,再定位 dependencies 对应的对象,最后只提取该对象内的字符串键值对。
def _object_after_field(text: str, field_name: str) -> str | None:
masked = _mask_comments(text)
pattern = re.compile(
QUOTED_OR_BARE_FIELD.format(re.escape(field_name))
)
match = pattern.search(masked)
if not match:
return None
brace = masked.find("{", match.end())
depth = 0
quote = None
escaped = False
for i in range(brace, len(masked)):
ch = masked[i]
if quote:
if escaped:
escaped = False
elif ch == "\\":
escaped = True
elif ch == quote:
quote = None
continue
if ch in {'"', "'"}:
quote = ch
elif ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return masked[brace + 1:i]
return None
真正解析路径时,以当前包清单所在的目录为基准,而不是以执行命令的目录为基准:
target = (package_file.parent / raw_target).resolve()
if not target.is_dir():
error("DEP002", "目标目录不存在")
target_manifest = target / "oh-package.json5"
if not target_manifest.is_file():
error("DEP003", "目标目录缺少 oh-package.json5")
这样无论从工程根目录、IDE 终端还是 CI 工作目录启动脚本,判断结果都一致。
失败信息要能直接定位
脚本给每类问题一个稳定编号:
| 编号 | 含义 |
|---|---|
DEP001 |
file: 后没有路径 |
DEP002 |
目标目录不存在 |
DEP003 |
目标目录缺少 oh-package.json5 |
DEP004 |
目标包缺少 name |
DEP005 |
依赖别名与目标包 name 不一致 |
DEP006 |
目标包的 main 文件不存在 |
有错误时退出码为 1,可以直接作为 CI 或提交前检查的一步:
python3 harmony_dep_audit.py .
需要接入其他工具时输出 JSON:
python3 harmony_dep_audit.py . --json
本次实测
项目包含 5 个单元测试,覆盖有效依赖、目录缺失、清单缺失、别名不一致和远程依赖忽略,运行结果:
test_alias_must_match_target_name ... ok
test_missing_target_directory ... ok
test_remote_dependencies_are_ignored ... ok
test_target_without_package_manifest ... ok
test_valid_local_dependency ... ok
Ran 5 tests in 0.010s
OK
对公开案例仓库执行审计:
[PASS] HarmonyOS local dependency audit
root=/private/tmp/bustravel.u2diep
packages=9 local_edges=12
这说明该快照中的 12 条 file: 依赖均能解析到同名目标包;它不证明整个 HarmonyOS 工程可以编译,也不替代 DevEco Studio、SDK、签名和真机/模拟器验证。它只回答一个更窄但很容易被忽略的问题:本地模块引用关系是否闭环。
适用的接入方式
- 在模板仓库克隆完成后先运行一次,尽早暴露空目录、漏提交模块和错误相对路径。
- 在 CI 中作为快速静态检查,先于耗时的 HAP 构建执行。
- 在二次开发拆分模块后运行,防止依赖别名和包名逐渐漂移。
- 将 JSON 输出接入看板,统计新增或未修复的依赖错误。
边界与后续
当前实现刻意不完整解析整个 JSON5 语法,只提取 dependencies 中的字符串键值对,因此不处理复杂表达式或非字符串依赖声明。它也不会执行 ohpm install、构建 HAP 或判断业务功能是否正确。
下一步可以增加:
- 检测循环本地依赖;
- 把
main入口指向的目录与模块类型交叉校验; - 为历史错误建立基线,只阻止新增问题;
- 输出 Graphviz 或 Mermaid 依赖图。
这类检查不会提高业务代码的复杂度,却能减少“代码看着都在,构建却找不到模块”的排查成本。对 HarmonyOS 模板二开来说,先让依赖图自洽,再进入真实构建和设备验证,顺序更可靠。
- 点赞
- 收藏
- 关注作者
评论(0)