茶器艺科智造HarmonyOS应用实战-18-watch漏掉libraryhar和libraryhsp,默认部署又遍历所有设备:给调试脚本加变更域与目标门禁

先做三个由当前脚本分支推导出的独立风险演练。开发者修改 libraryhar/src/main/ets/model/CupLatheSilhouette.ets,watch 终端不会因这次保存启动;修改 entry 后,watcher 才会调用 Shell chaqi-auto-debug.sh,未设置 HDC_TARGET 时由 hdc 自己选择默认目标,安装使用 install -r,不会执行卸载。另一次独立操作中,开发者若在 Windows 执行 debug.ps1 run 且没有设置 HDC_TARGET,脚本才会遍历全部连接并逐台卸载、安装、启动。Pad 包装脚本则有第三种风险:它从 loopback 连接里取第一条,再把该值传给 Shell 自动调试。这三条调用链没有互相调用,本文把它们并列审查,而不伪造成同一次事故。

我核对的源码修订为 f671fcd8de277973bd7518a3c462f68384575f1e。当前 chaqi-watch-debug.sh 只监听 entry 的 ets/resources、AppScope 和部分根/entry 配置,没有 libraryhsp、libraryhar;Windows debug.ps1 在没有 HDC_TARGET 时读取全部 hdc list targets 并逐台 uninstall/install/start;Pad 脚本从所有 127.0.0.1:端口 中取第一条。本文只给出脚本设计与安全验证方案,没有修改脚本,没有启动 watcher、构建包、连接设备或执行卸载安装。

调试变更域与目标门禁封面

本文会解决:

  1. 用项目依赖图确定 watcher 应覆盖哪些源码和配置。
  2. 把文件变化映射为明确构建域,而不是任何变化都盲目 run。
  3. 让安装、卸载、启动必须绑定一个可回读目标。
  4. 把多设备部署从默认行为改成显式批次。
  5. 避免 Pad 脚本把第一个 loopback 连接误认成目标实例。
  6. 用 dry-run 和失败注入证明门禁有效。

一、先把三个入口的调用链分开

依据当前脚本分支,真实调用关系应画成三类入口、四个执行分支:

A. 修改 libraryhar
   → chaqi-watch-debug.sh 范围未命中
   → 本次保存不触发构建或部署

B. 修改 entry
   → chaqi-watch-debug.sh 命中
   → chaqi-auto-debug.sh
   → chaqi-build.sh → chaqi-install-hap.sh → chaqi-launch-app.sh
   → 未设置 HDC_TARGET 时交给 hdc 默认目标
   → install -r,不执行 uninstall

C. 开发者另行执行 Windows debug.ps1 run
   → 未设置 HDC_TARGET
   → Resolve-HdcTargets 返回全部连接
   → foreach 逐台 uninstall/install/start

D. 开发者另行执行 chaqi-auto-debug-pad.sh
   → 从 loopback 候选取第一条
   → 导出 HDC_TARGET
   → 调用 chaqi-auto-debug.sh

这里有三个可以分别复现的问题:watcher 的变更域不完整;Windows run 把空目标解释为全部连接;Pad 包装脚本把列表顺序当成设备身份。它们共同说明调试入口缺少统一的变更域与目标契约,但 watcher 不会调用 Windows debug.ps1,Pad 包装脚本也不是 Windows 多设备遍历的前置步骤。

静态源码能证明这些分支存在,却不能证明某次真实运行已经删除了数据或选错设备。只有对应入口的命令日志、设备列表与目标回读才能确认具体影响,因此本文把它们作为应当分别防住的风险,而非已经发生的事故记录。

二、现有 watcher 只看到产品入口的一部分

当前 chaqi-watch-debug.sh 的核心列表是:

fswatch -1 -r -l "$LAT"   --exclude '/build/'   --exclude '/\.hvigor/'   --exclude '/oh_modules/'   --exclude '/node_modules/'   "$ROOT/entry/src/main/ets"   "$ROOT/entry/src/main/resources"   "$ROOT/AppScope"   "$ROOT/build-profile.json5"   "$ROOT/entry/build-profile.json5"   "$ROOT/oh-package.json5"   "$ROOT/entry/oh-package.json5"

它遗漏了至少这些直接影响运行结果的路径:

  • libraryhsp/src/main/ets:主页面和组件。
  • libraryhsp/src/main/resources:启动叠层、rawfile Three.js、字体。
  • libraryhsp/build-profile.json5oh-package.json5
  • libraryhar/src/main/ets:路由、Preferences、杯体轮廓、切片估算。
  • libraryhar/build-profile.json5oh-package.json5
  • 三个模块的 hvigorfile.ts,以及根 hvigorfile.ts。

libraryhar 当前没有 resources 目录,不能因此永远排除;watcher 可以只加入存在路径,未来新增资源时自动覆盖。

从文件事件到单目标部署的安全流程

三、监听清单应从模块图生成

build-profile.json5 已声明 entry、libraryhsp、libraryhar,模块 oh-package 又给出依赖关系。建议 watcher 启动时生成路径清单,而不是长期手写一部分。

提议的 Bash 函数如下,尚未落地:

build_watch_paths() {
  WATCH_PATHS=(
    "$ROOT/AppScope"
    "$ROOT/build-profile.json5"
    "$ROOT/hvigorfile.ts"
  )

  for module in entry libraryhsp libraryhar; do
    for path in       "$ROOT/$module/src/main"       "$ROOT/$module/build-profile.json5"       "$ROOT/$module/oh-package.json5"       "$ROOT/$module/hvigorfile.ts"
    do
      test -e "$path" && WATCH_PATHS+=("$path")
    done
  done
}

这不是动态解析 JSON5 的最终实现,但已经把三个已登记模块纳入同一规则。进一步可以从根配置读取 modules,避免新增模块后再次漏改脚本;解析 JSON5 需要使用仓库已有工具或受控解析器,不能用脆弱正则假装完整语法。

排除项仍应保留 build、.hvigor、oh_modules、node_modules,避免构建输出反过来触发无限循环。日志、截图和生成文档目录也应根据仓库实际位置排除。

四、文件变化要先归类,再选择构建计划

监听到变化不意味着立即部署。建议建立变更域:

变更域典型路径最小构建意图是否默认部署
entry-codeentry/src/main/etsentry 与依赖一致的调试包
entry-resourceentry/src/main/resources重建 entry
hsp-codelibraryhsp/src/main/ets重建 HSP/应用集合
hsp-resourcelibraryhsp/src/main/resources重建 HSP/应用集合
har-codelibraryhar/src/main/ets重建所有消费 HAR 的产物
app-configAppScope、根 build-profilefull-app先 dry-run
module-config任一模块配置full-app先 dry-run
unknown其他不自动部署只报告

HAR 同时被 entry 和 HSP 依赖,变更后不能只更新其中一侧。文章 17 已讨论 full-app/module-set 产物语义;这里的 watcher 只负责选计划,不重新定义安装集合。

可以给事件批次一个结构:

interface ChangeBatch {
  id: string;
  paths: string[];
  domains: string[];
  firstEventAtMs: number;
  lastEventAtMs: number;
  buildMode: 'full-app' | 'module-set' | 'none';
  requiresDryRun: boolean;
}

防抖应聚合同一保存动作的多条事件,随后根据所有 domain 取最宽构建范围。未知路径不能默认升级成“全设备 run”,应停止并告诉开发者为何未部署。

五、当前 Windows 默认把所有连接都当目标

Resolve-HdcTargets 在 HDC_TARGET 为空时调用 Get-HdcTargetsList,再原样返回全部目标。安装和启动阶段分别 foreach:

$targets = @(Resolve-HdcTargets $HdcExe)

foreach ($t in $targets) {
  & $HdcExe -t $t uninstall $BundleName
  & $HdcExe -t $t install @packagesToInstall
}

foreach ($t in $targets) {
  & $HdcExe -t $t shell aa force-stop $BundleName
  & $HdcExe -t $t shell aa start -a $AbilityName -b $BundleName
}

脚本注释把它描述为便利功能,但 uninstall 是会改变设备数据的动作,不能以“没设置变量”作为多设备授权。更稳的默认规则是:

  • targets/list 可以无目标运行。
  • build 不需要设备。
  • log/start/install/run 必须解析为恰好一个目标。
  • 多设备操作必须显式使用 --all--targets a,b,并回显完整计划。
  • clean/uninstall 再加一层明确模式,不能混在普通 run。

六、用 exactly-one 门禁替代隐式全选

PowerShell 提议实现:

function Resolve-ExactlyOneTarget([string]$Hdc, [string]$Requested) {
  if ([string]::IsNullOrWhiteSpace($Requested)) {
    $connected = @(Get-HdcTargetsList $Hdc)
    if ($connected.Count -eq 1) {
      return $connected[0]
    }
    throw "Set HDC_TARGET; connected count=$($connected.Count)"
  }

  $parts = @($Requested.Split(',') |
    ForEach-Object { $_.Trim() } |
    Where-Object { $_ -ne '' })

  if ($parts.Count -ne 1) {
    throw "Exactly one HDC_TARGET is required"
  }
  return $parts[0]
}

还要确认请求目标确实出现在当前连接列表中,避免拼错后把 hdc 默认行为带回来。所有有副作用的命令都必须带 -t $target,并在执行前打印 bundle、artifact hash、target、installPolicy。

显式批量模式则使用另一函数和另一命令名,不复用 exactly-one 的返回类型。类型层面分开能减少“数组意外进入单目标函数”。

七、Pad 脚本不能取第一个 127 目标

当前选择函数是:

pick_emulator_hdc_target() {
  hdc_list_raw |
    grep -E '127\.0\.0\.1:[0-9]+' |
    head -1 |
    awk '{ print $1 }' |
    tr -d '[:space:]'
}

如果手机模拟器和 Pad 模拟器都在本地端口连接,第一条只反映 hdc 输出顺序,不反映 CHAQI_PAD_EMULATOR_NAME。更安全的策略按优先级处理:

  1. 用户显式提供 CHAQI_PAD_HDC_TARGET 时,核对该目标在线后使用。
  2. 脚本启动新 Pad 前记录已有 loopback 集合;启动后取集合差集。
  3. 差集中恰好一条才自动选择。
  4. 若实例早已运行或候选多于一条,停止并打印候选,要求明确目标。
  5. 选择后回读设备类型/窗口能力,确认符合 Pad 测试意图。

差集算法草案:

before="$(list_loopback_targets)"
start_named_emulator "$NAME"
wait_for_loopback_change
after="$(list_loopback_targets)"
candidate="$(set_difference "$after" "$before")"

test "$(line_count "$candidate")" -eq 1 ||
  fail_with_candidates "$after"
export HDC_TARGET="$candidate"

这些辅助函数当前不存在。若无法可靠把 emulator name 映射到 hdc target,宁可停止,也不要用 head -1 猜。

Watcher、变更域、构建计划和设备门禁结构

八、watch 循环还需要并发与失败边界

fswatch -1 每轮只等一批事件,随后同步执行 auto-debug,因此构建期间发生的后续修改可能没有被当前监听进程记录。建议明确状态机:

IDLE
  └─文件事件→ DEBOUNCING
DEBOUNCING
  └─归类完成→ PLANNED
PLANNED
  ├─门禁失败→ BLOCKED → IDLE
  └─门禁通过→ BUILDING
BUILDING
  ├─构建失败→ FAILED → IDLE
  └─构建成功→ READY_TO_INSTALL
READY_TO_INSTALL
  ├─目标失联→ BLOCKED → IDLE
  └─安装启动→ VERIFYING → IDLE

实现可以在构建期间另开轻量事件记录器,把新变化标为 dirty;当前轮结束后若 dirty,再生成新批次。不要并发启动两个 hvigor 和两个安装进程,也不要丢掉构建期间保存的文件。

失败后应回到监听,但保留 batchId、失败阶段和变更路径。当前脚本用 “本轮失败(见上)” 可以继续循环,不过缺少可机器读取的失败回执。

九、安全验证先证明“不会做错设备”

下表是方案实现后的待执行矩阵,不代表已有运行结果:

场景期望变更域目标门禁期望副作用
改 libraryhar 模型har-code单目标触发覆盖所有消费者的构建
改 libraryhsp 页面hsp-code单目标重建应用集合
改 rawfile Three.jshsp-resource单目标触发,不再漏掉
build 输出变化不进入不递归触发
未知文档变化unknown不解析目标只报告
零设备任意可部署域阻断不安装、不启动
一台设备未设变量任意可部署域可选择唯一在线目标或要求显式值规则需固定
两台设备未设变量任意阻断不卸载任何设备
显式单 target任意通过所有 hdc 都带 -t
显式 --all任意二次计划确认逐台回执
两个 loopback Pad 候选任意阻断不用第一条猜
构建期间再保存新 batch当前轮不并发完成后再跑一轮

可先做命令桩测试,把 hdc 替换为只记录参数的 fake:

fake hdc list targets → [phone-1, pad-2]
run without HDC_TARGET
assert exit != 0
assert uninstall call count == 0
assert install call count == 0
assert start call count == 0

随后再用 --dry-run 对真实设备列表检查计划。只有无副作用用例全部通过,才进入单台测试设备;任何真实卸载都应由显式 clean 模式触发。

十、排错表与结论边界

现象首查位置根因候选修复方向
改 HAR 无反应WATCH_PATHS没监听 libraryhar从模块图生成路径
改 HSP rawfile 无反应hsp resources只监听 entry加入整个 src/main
保存一次跑多轮防抖与输出排除构建产物反触发排除输出并聚合事件
构建时保存被漏watcher 状态同步构建期间无人记录dirty 标记后补一轮
两台设备都被卸载Resolve-HdcTargets空变量等同全选exactly-one 门禁
指定 target 仍跑错命令参数某条 hdc 漏 -t统一命令包装器
Pad 打到手机模拟器head -1loopback 顺序不代表实例差集或显式目标
unknown 文件触发全量 run域映射默认分支过度兜底unknown 只报告
watch 失败后无上下文日志结构只有“见上”batch 回执记录阶段
dry-run 与真实执行不一致两套计划代码执行阶段重新解析同一不可变 Plan 驱动

当前源码能确认 watcher 漏掉 libraryhar/libraryhsp,Windows 空 HDC_TARGET 会遍历全部连接,Pad 脚本取第一条 loopback。本文提出的模块图清单、变更域、exactly-one、显式批量和 Pad 候选差集均未写入仓库,也没有执行 watcher、构建或设备操作。只有脚本桩用例证明门禁在多设备时零副作用,再由单目标真实回执验证,才能说自动调试既覆盖了正确变化,也只影响了正确设备。

Logo

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

更多推荐