Files
homeagent-sdk/scripts/build_plugin_bundles.sh
JianFeeeee 953fbb2f50 feat(scripts): 批量打插件包(.hmap)供 release 发布
## 问题

release 此前**只发 homed/waiter 二进制与 hmapdev 工具链,不发插件包**(主仓 20 个
release、本仓的 release 都核实过,0 个 .hmap)。用户要用任何一个插件,都得:

1. 装 Go 1.25 + 网络拉依赖
2. 装 hmapdev 工具链
3. 进 example/<插件>/ 逐个 `hmapdev build`

而 `hmapdev build` **不是可选项**:5 个 example(deepsearch / luademo / vanblog /
vikunja / weather)连 `main.go` 都没有,直接 `go build` 会死在
「function main is undeclared in the main package」——入口是 hmapdev 现生成的。

所以「开箱可用」名不副实。本脚本把这一步前置到发布流程里。

## 用法

```bash
./scripts/build_plugin_bundles.sh                # 全部 21 个
./scripts/build_plugin_bundles.sh weather qq     # 指定
OUT=../dist/plugins ./scripts/build_plugin_bundles.sh
```

产出 `.hmap` + `SHA256SUMS.plugins`,可直接作为 release 附件。

## 实现要点(三处是踩过才写对的)

1. **产物有三种形态**,不能只认 `_bundle.hmap`:
   - `<name>_bundle.hmap` 多平台 bundle(`plg.json` 的 `bundle: true`)
   - `<name>_<os>_<arch>.hmap` 单平台(**qq** 的 plg.json 是 `bundle: false`,
     与其余 20 个不一致)
   - `<name>_lua.hmap` Lua 插件(**luademo**,不编译 Go)
   脚本按形态标注,发布时能一眼看出是哪种。

2. **`set -e` 下不能用 `[ -z "$x" ] && x=$(ls ...)` 做兜底**:
   一次 `ls` 无匹配(退出码 2)就会让整个子 shell 直接退出,后面的兜底根本走不到。
   实测表现是「只有 qq 和 luademo 失败」——因为正是它俩没有 `_bundle.hmap`。
   改成 `for` 循环 + `|| true`。

3. **参数拼错要报错,不能静默跳过**(否则以为打了实际没打)。

## 验证

- 21/21 构建成功,合计约 150 MB(bundle 含 linux/amd64 + darwin/amd64;
  不含 windows 是**策略**——homed 已放弃 Windows 原生,源码注释有说明)
- 每个包内部结构核对:含 plugin.json、含内核认得的入口、当前平台可得
2026-09-20 09:02:31 +08:00

131 lines
4.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 为 SDK 仓的 example 插件批量打 .hmap 包,产出可直接随 release 发布的插件包。
#
# 背景release 此前只发 homed/waiter 二进制与 hmapdev 工具链,**不发插件包**。
# 用户要用某个插件,得自己装 Go 1.25、拉依赖、装 hmapdev、逐个 build —— 这是
# 「开箱即用」名不副实的根源。本脚本把这一步前置到发布流程里。
#
# 用法:
# ./build_plugin_bundles.sh # 全部 example
# ./build_plugin_bundles.sh weather qq memo # 指定插件
# OUT=../dist/plugins ./build_plugin_bundles.sh
#
# 环境:
# HMAPDEV hmapdev 可执行文件(默认取 PATH 上的 hmapdev
# OUT 产物目录(默认 <script>/../../dist/plugins
# JOBS 并行度(默认 CPU 核数)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EX_DIR="$(cd "$SCRIPT_DIR/../example" && pwd)"
SDK_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
OUT="${OUT:-$(cd "$SDK_DIR/.." && pwd)/dist/plugins}"
HMAPDEV="${HMAPDEV:-hmapdev}"
command -v "$HMAPDEV" >/dev/null 2>&1 || {
echo "error: 找不到 hmapdev设 HMAPDEV=/path/to/hmapdev 或用 'hmapdev sdk install' 装)" >&2
exit 1
}
mkdir -p "$OUT"
# 收集候选插件:有 plg.json 才可构建
all=()
for d in "$EX_DIR"/*/; do
n="$(basename "$d")"
[ -f "$d/plg.json" ] || continue
all+=("$n")
done
# 参数指定则取交集(并校验名字有效,避免拼错静默跳过)
if [ "$#" -gt 0 ]; then
want=("$@")
sel=()
for w in "${want[@]}"; do
found=""
for n in "${all[@]}"; do [ "$n" = "$w" ] && found=1 && break; done
[ -n "$found" ] || { echo "error: 未知插件 '$w'(可用: ${all[*]}" >&2; exit 1; }
sel+=("$w")
done
all=("${sel[@]}")
fi
echo "=== 打包 ${#all[@]} 个插件 → $OUT ==="
echo " hmapdev: $("$HMAPDEV" --version 2>/dev/null | head -1 || echo "$HMAPDEV")"
build_one() {
local name="$1"
local dir="$EX_DIR/$name"
local log="$OUT/.$name.log"
# hmapdev build 必须在插件目录内跑(它读当前目录的 plg.json
if ! (cd "$dir" && "$HMAPDEV" build >"$log" 2>&1); then
echo "$name 构建失败(见 $log"
return 1
fi
# 产物有三种形态,不能只认 _bundle.hmap
# 1) <name>_bundle.hmap 多平台 bundleplg.json 里 bundle: true
# 2) <name>_<os>_<arch>.hmap 单平台bundle 关掉时,如 qq
# 3) <name>_lua.hmap Lua 插件(不编译 Go如 luademo
#
# 注意用 if 而非 `[ -z ] && found=$(ls...)`:在 set -e 下,
# 一次 ls 无匹配就会让整个子 shell 直接退出,根本走不到后面的兜底。
local found=""
local cand
for pat in "$dir"/dist/*_bundle.hmap "$dir"/dist/*.hmap "$dir"/*_bundle.hmap; do
if [ -z "$found" ]; then
cand="$(ls -t $pat 2>/dev/null | head -1 || true)"
[ -n "$cand" ] && found="$cand"
fi
done
if [ -z "$found" ]; then
echo "$name 未产出 .hmap$log"
return 1
fi
cp -f "$found" "$OUT/"
local sz bn
bn="$(basename "$found")"
sz="$(stat -c%s "$OUT/$bn" 2>/dev/null || stat -f%z "$OUT/$bn")"
# 标注形态:单平台/Lua 包与多平台 bundle 不同,发布时要能一眼看出
local tag=""
case "$bn" in
*_bundle.hmap) tag="bundle" ;;
*_lua.hmap) tag="lua " ;;
*) tag="单平台" ;;
esac
printf " ✓ %-16s %7.1f MB %s\n" "$name" "$(echo "$sz" | awk '{print $1/1048576}')" "$tag"
rm -f "$log"
}
fail=0
pids=()
for n in "${all[@]}"; do
# 有 nproc 就限并发,没有就串行
if command -v nproc >/dev/null 2>&1; then
while [ "$(jobs -rp | wc -l)" -ge "${JOBS:-$(nproc)}" ]; do wait -n 2>/dev/null || true; done
fi
( build_one "$n" ) &
pids+=($!)
done
for p in "${pids[@]}"; do wait "$p" || fail=$((fail+1)); done
echo
echo "=== 产出 ==="
ls -la "$OUT"/*.hmap 2>/dev/null | awk '{printf " %-46s %8.1f MB\n", $9, $5/1048576}' || echo " (无)"
# 汇总校验和,便于随 release 一起发布与验证
if ls "$OUT"/*.hmap >/dev/null 2>&1; then
( cd "$OUT" && sha256sum ./*.hmap > SHA256SUMS.plugins )
echo
echo "=== 校验和 → $OUT/SHA256SUMS.plugins ==="
cat "$OUT/SHA256SUMS.plugins" | sed 's/^/ /'
fi
if [ "$fail" -gt 0 ]; then
echo
echo "error: $fail 个插件构建失败" >&2
exit 1
fi