项目:LicenseDesk · LC-204
写作方式:代码评审随笔 / 合规元数据边界说明
说明:文中包名、许可字段和报告数字均为教学构造数据;任何RECORDED都不代表法律结论。

真实工程的麻烦,往往不是安装失败

HarmonyOS项目接入第三方库以后,最先被检查的一般是编译能否通过、包体是否增长、版本有没有冲突。等到准备移交给测试或发布团队时,另一个问题才冒出来:今天交付的这些三方依赖,分别声明了什么许可证?有没有某个包只留下名字和版本,连许可字段都没有?维护人员要反复打开依赖页面找信息,最后往往拼出一份没人愿意维护的表格。

ohpm本身负责依赖管理,不能把“包能正常安装”当作“许可已经审查”。从华为官方oh-package.json5文档可以确认,模块描述包含name、version以及示例里的license字段,也能分清dependencies、devDependencies、dynamicDependencies用途。文档还提醒不要直接用自建分析工具读取oh-package-lock.json5内部内容。因此本文不解析锁文件,不宣称能自动识别所有传递依赖,而是设计一个可复核的人工取证清单 + Node.js静态门禁。

示例项目叫LicenseDesk,复核批次LC-204。清单有四个虚构包,其中三个记录了非空许可声明,一个@demo/visionfx没有记录,结果应为RECORDED 3 / REVIEW 1,总状态REVIEW。这里的RECORDED只表示“元数据字段已经采集”,绝不等于“这个依赖一定可以用于商业项目”,更不能当成上架审核已通过的证据。

先分清工具负责的三层事实

第一层是依赖事实:项目的模块配置写了什么,当前安装了哪些包。第二层是元数据事实:对应版本在包描述里声明了什么许可。第三层才是法律事实:该许可证文本、通知义务、源码提供义务和业务分发方式能否匹配。Node脚本可以机械地比对前两层的记录是否齐整,但第三层必须由具备资质的人员结合真正的许可文本判断。把这三层混起来,风险比漏掉一个字段更大。

团队执行时先在工程目录用官方ohpm list命令查看安装的依赖层级,再和模块oh-package.json5中的生产、开发和动态依赖声明对照。以下命令是辅助人工盘点,并不承诺所有版本的输出JSON结构完全一致:

ohpm list --depth 4
# 把输出留档,并人工核对各个需要纳入发布评估的依赖。
# 不要直接修改、解析或根据猜测重构 oh-package-lock.json5。

接下来由维护者根据本次交付内容填写license-input.json。采用普通JSON而不是直接读取不稳定的安装树,是为了让来源和审核范围透明;其中四个@demo/...名称只是教学用假数据,并不对应实际登记的三方库:

{
  "taskId": "LC-204",
  "items": [
    {"name":"@demo/matrix",   "version":"1.2.0", "license":"MIT"},
    {"name":"@demo/netkit",   "version":"2.0.1", "license":"Apache-2.0"},
    {"name":"@demo/colorize", "version":"0.9.4", "license":"BSD-3-Clause"},
    {"name":"@demo/visionfx", "version":"1.1.0", "license":""}
  ]
}

请注意,手工抄写也会有错。所以生产实践里建议附上“查询日期、锁定版本、包元数据原始来源链接、经手人”字段,让复核者能追溯;但不要在公开报告里暴露企业内部仓库的访问令牌。若一项依赖是本地HAR、源码目录或动态HSP,仍要单独确认其实际许可资料,不能因为它不出现在某一次命令的默认显示层级就自动忽略。

一个只做元数据分级、不假装懂法律的脚本

核心脚本放在examples/audit-license.mjs,直接读取上述人工清单,对空值、常见未知占位符与同名同版本重复记录进行检查。它不会网络抓取许可证,也不会擅自给GPL、Apache之类标签做法律判断。对于缺许可的项目,输出REVIEW并留下明确原因;对于有记录的项目,输出RECORDED。这样命名是为了防止运营同学把PASS误读成合规结论。

import { readFile, writeFile, rename } from 'node:fs/promises';
import { dirname, join } from 'node:path';
const input = process.argv[2];
if (!input) throw new Error('usage: node audit-license.mjs <license-input.json>');
const raw = JSON.parse(await readFile(input, 'utf8'));
if (raw.taskId !== 'LC-204' || !Array.isArray(raw.items)) throw new Error('invalid inventory');
const unknown = /^(UNKNOWN|NOASSERTION|UNLICENSED|N\/A|\?)$/i;
const seen = new Set();
const items = raw.items.map(p => {
  const key = `${p.name}@${p.version}`;
  const license = typeof p.license === 'string' ? p.license.trim() : '';
  const reason = !p.name || !p.version ? 'INVALID_ID'
    : seen.has(key) ? 'DUPLICATE_ENTRY'
    : (!license || unknown.test(license)) ? 'MISSING_LICENSE' : '';
  seen.add(key);
  return { name: p.name, version: p.version, license,
    status: reason ? 'REVIEW' : 'RECORDED', reason };
});
const recorded = items.filter(p => p.status === 'RECORDED').length;
const review = items.length - recorded;
const report = { taskId: raw.taskId, scanned: items.length, recorded,
  review, status: review ? 'REVIEW' : 'NEEDS_LEGAL_CHECK', items };
const target = join(dirname(input), 'license-report.json');
await writeFile(target + '.tmp', JSON.stringify(report, null, 2) + '\n');
await rename(target + '.tmp', target);
console.log(`[LC-204] ${report.status} scanned=${items.length} recorded=${recorded} review=${review}`);
process.exitCode = review ? 2 : 0;

NEEDS_LEGAL_CHECK也不是批准状态。即便本次没有空字段,负责人还需要看许可正文和分发方式。脚本故意把这一层留在业务流程之外,以免在发布流水线里出现“全绿即合法”的错觉。对于@demo/visionfx,报告列出MISSING_LICENSE,而不是猜一个看起来常见的MIT补上。这样才有可执行的补齐动作:回到包的原始发布资料确认,再让负责人记录最终判断。

把机器报告变成人看得懂的复核卡片

静态审计本身运行在开发机Node.js上;它不是移动端ohpm服务。为了示范交接时怎样展示摘要,可以在ArkUI中使用一份脱敏且事先确定的样例报告。下段代码只渲染硬编码的示例状态,实际工程若要加载本地JSON,还应再实现合法的文件读取与错误处理,不能把演示截图写成真实联机报告。

@Entry
@Component
struct LicenseReportPage {
  @State taskId: string = 'LC-204';
  @State recorded: number = 3;
  @State review: number = 1;
  @State status: string = 'REVIEW';
  build() {
    Column({ space: 16 }) {
      Text('三方库许可复核单').fontSize(24).fontWeight(FontWeight.Bold)
      Text(`任务 ${this.taskId}`)
      Text(`RECORDED ${this.recorded} / REVIEW ${this.review}`)
      Text(this.status).fontColor(Color.Orange).fontSize(22)
      Text('@demo/visionfx · MISSING_LICENSE')
      Text('字段采集完整性检查,不构成法律结论')
        .fontSize(12).fontColor(Color.Gray)
      Button('导出复核记录').onClick(() => {
        console.info('[LC-204] export requested (demo only)');
      })
    }.padding(24).width('100%')
  }
}

这张开发图展示的是预设流程:在工程树里有LicenseReportPage.ets,中央编辑器包含LC-204和RECORDED 3 / REVIEW 1,右侧手机模拟器标记REVIEW,底部日志显示MISSING_LICENSE。这些内容和上面的数据一一对应,却没有对外宣称经过真实DevEco编译或已经跑完CI。标注为示意是必要的,因为审核资料必须能区分“设计说明”和“执行证据”。

怎样调试一份静态复核表

首先使用上述四项教学数据运行脚本,应该得到[LC-204] REVIEW scanned=4 recorded=3 review=1,退出码2。这属于事先构造的本地样例结果,不是对某个真实库的调查。再补齐第四项的license,脚本会返回NEEDS_LEGAL_CHECK,退出码0;也不能因此跳过人工法律复核。接着故意重复同名同版本,验证DUPLICATE_ENTRY会被挑出来。最后把license写成NOASSERTION,确保这种占位符不会被算成已经采集完毕的许可结论。

还应特别测试范围漂移。比如今天只收录生产依赖,第二天构建参数切换到动态模块或替换为本地HAR,这份四项清单可能马上过时。工程可以在发布清单中记录模块名、目标配置和手工盘点日期,必要时把ohpm list的命令输出留作内部附件。不要私自解析锁文件的隐藏结构来凑齐数量;华为官方文档明确提示不建议这么做。若确实需要自动依赖清单,应采用受支持的工具输出及其文档范围,并为不同版本补测试。

这个示意手机屏幕故意保留了黄色的REVIEW提示,而不是诱人的绿色“审核通过”。它有助于产品、研发和法务使用相同的词汇。产品看到“缺字段”知道要补材料;研发知道脚本只检测快照;法务知道还有许可文本、版权通知和再分发条款尚待判断。一个小工具如果把职责说得清楚,反而比夸大成“自动合规平台”更可维护。

安全、维护和发布的边界

这份脚本不会连接外部网络,不会凭空读取设备上的安装记录,也不会替代软件成分分析平台。手工输入清单可能漏掉传递依赖,所以生产流程必须把“清单覆盖范围”作为明确的审核事项。license字段也可能填写自定义短语,不能把所有非空值都等同于标准SPDX标识;此外许可证声明未必涵盖代码中带来的字体、图片、模型等其他素材。若某个包的源码、二进制构件和分发渠道来源不同,单看包描述仍然不足以作出完整判断。

对于多仓库、多产品形态,建议把报告与构建版本关联起来,避免上一次的结论被原样复制到下一次。输出文件用同目录临时文件重命名,是为了减轻文件写一半就被消费的问题;并行运行时要给不同任务分目录,否则两个审计过程可能竞争同一个临时名字。对外发布报告前,还应移除机器本地路径与内部包仓库地址。任何“审核已通过”的表述都必须依据真实审核过程,而不是根据脚本返回码推导。

结尾:让复核单诚实地承认未知

LicenseDesk最有价值的不是识别四种许可证名称,而是把RECORDED与REVIEW的含义固定住:一个是记录到元数据,另一个是需要继续查证。LC-204的示例结果是扫描4条、已记录3条、待复核1条,具体缺口为@demo/visionfx的MISSING_LICENSE。这篇文章不涉及ohpm的安装算法、override决策或上架审核政策,而是给三方库管理增加一道可读、可追溯、又不会越权下结论的人工复核入口。

官方资料核对: 华为开发者联盟oh-package.json5说明(2026-10-08更新)https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-hmos-oh-package-json5;ohpm list命令说明(2026-07-28更新)https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-ohpm-list;三方依赖配置指南https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/ide-hmos-hvigor-dependencies。本文使用的Node.js脚本是项目辅助工具,不属于华为官方SDK接口。

Logo

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

更多推荐