#!/usr/bin/env bash # 构建 homeagent-mail-bridge —— 用**明确指定的** SDK 源码,并记录"这次到底用了哪份代码"。 # # # 这份脚本被改过一轮,因为它的第一版判错了根因(pi 2026-09-14 晚逐条反驳) # # 第一版的错:把 `go version -m <内核>` 里的 # dep ... homeagent-sdk v0.9.2 / => ./third_party/homeagent-sdk (devel) # 当成"内核链的是 0.9.x 血脉",然后按候选根目录**第一个命中就算**去解析 SDK 源码, # 命中的是 `/root/ha-test/third_party/homeagent-sdk`(C-ABI 时代的老 checkout,无 plugin.bin 支持)。 # 而本机共有 6+ 份同名 checkout,内核自己用的是 1.3.0 那份。两个教训: # # ① **不要用"第一个存在的路径"当权威来源** —— 猜根目录会命中不相关的 checkout, # "两条血脉"于是成了脚本的产物,而不是事实; # ② **不要把模块版本字符串当身份** —— `replace => local (devel)` 时那个版本号 # 只是 require 行的文本残留,不代表实际编译的代码。 # # 所以现在:SDK 源码**只认显式指定**(`HOMEAGENT_SDK_DIR`)。 # 不猜、不试探、不"第一个命中"。要指定内核的构建目录就显式传进来。 # # # 不变量是什么 # # 不是"版本号相等"(那正是第一版想抛弃的东西),而是 **wire 协议 + 一次真实握手**: # 产物要能被内核 `protocol=2` 认下来。这个脚本能做的是"把身份记清楚 + 把明显不对的挡下来", # 真握手由内核启动时完成(所以部署后要回看一次日志,见末尾提示)。 set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SDK_MODULE="gitcode.com/JianFeeeee/homeagent-sdk" KERNEL_BIN="${HOMEAGENT_KERNEL_BIN:-/usr/local/bin/homed}" die() { echo "构建失败:$*" >&2; exit 1; } # cron/非登录 shell 里 HOME 可能没设,Go 会因此拒绝工作(曾经在这里栽过) export HOME="${HOME:-/root}" export GOPATH="${GOPATH:-$HOME/go}" export GOCACHE="${GOCACHE:-${TMPDIR:-/tmp}/go-build-agentmail}" mkdir -p "$GOCACHE" # ── 1. SDK 源码:只认显式指定 ─────────────────────────────────────── [[ -n "${HOMEAGENT_SDK_DIR:-}" ]] || die "必须显式指定 SDK 源码树:HOMEAGENT_SDK_DIR=/path/to/homeagent-sdk 为什么不让脚本自己猜:本机有 6+ 份同名 checkout(有的是 C-ABI 时代的老代码), 第一版按候选根目录'第一个命中就算',命中了 /root/ha-test 那份老 checkout, 于是把'内核链老血脉'这个**错误结论**当成了事实写进注释。猜路径 = 制造事实。" SDK_DIR="$(cd "$HOMEAGENT_SDK_DIR" && pwd)" [[ -f "$SDK_DIR/go.mod" ]] || die "$SDK_DIR 不是 Go 模块(没有 go.mod)" # 身份用 **realpath + 内容哈希**,不用版本字符串(版本号可能是 -ldflags 注入的默认值, # 也可能是注释里的示例 —— 后者我在 electron 侧的判据里刚踩过一次)。 SDK_HASH="$(find "$SDK_DIR" -type f -name '*.go' -print0 2>/dev/null | sort -z | xargs -0 cat 2>/dev/null | sha256sum | cut -c1-16)" SDK_VER="$(sed 's://.*::' "$SDK_DIR/meta/meta.go" 2>/dev/null | grep -m1 -oE 'Version = "[0-9]+\.[0-9]+\.[0-9]+"' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)" echo "[build] SDK 源码:$SDK_DIR" echo "[build] meta.Version(已剥注释)= ${SDK_VER:-未知} | 内容哈希 = ${SDK_HASH:-未知}" # 内核签名的**提示**(不是判据):按模块名精确联接,不按"输出里第一个 =>"。 # "两张表各缺一半时必须按 id 联接"—— 读 go version -m 的输出同理。 if [[ -x "$KERNEL_BIN" ]]; then KERNEL_INFO="$(go version -m "$KERNEL_BIN" 2>/dev/null || true)" KERNEL_SDK_LINE="$(awk -v m="$SDK_MODULE" '$1=="dep" && $2==m {print NR": "$0}' <<<"$KERNEL_INFO" | head -1)" if [[ -n "$KERNEL_SDK_LINE" ]]; then KERNEL_SDK_DEP="$(cut -d' ' -f3 <<<"${KERNEL_SDK_LINE#*: }")" # 只接受**紧跟在 SDK 那一行之后**的 => 行(按位置配对的唯一正确写法) KERNEL_SDK_REPL="$(awk -v m="$SDK_MODULE" ' $1=="dep" && $2==m {want=NR+1} want && NR==want && $1=="=>" {print $2; exit}' <<<"$KERNEL_INFO")" echo "[build] 内核 $KERNEL_BIN 的 dep 行:$KERNEL_SDK_DEP${KERNEL_SDK_REPL:+(本地替换 $KERNEL_SDK_REPL)}" echo "[build] 注意:这条只是**提示**,不是判据 —— replace 到本地目录时这个版本号只是 require 行的残留," echo "[build] 而且它指向的相对路径未必是本机这份源码。要对齐请显式传 HOMEAGENT_SDK_DIR。" fi fi # ── 2. 用临时 modfile 替换,不改动受版本管理的 go.mod ─────────────── cd "$HERE" TMP_MOD="$(mktemp -d)/go.local.mod" cp go.mod "$TMP_MOD" go mod edit -modfile="$TMP_MOD" -replace "$SDK_MODULE=$SDK_DIR" export GOFLAGS="${GOFLAGS:-} -modfile=$TMP_MOD" # 注意:这个插件**没有** `func main()`(入口由 hmapdev 生成/包装), # 所以 `go build ./...` 会报 "function main is undeclared" —— 那是正常的, # 不能用它来数 API 兼容性错误;真正的编译在 hmapdev 那一步。 hmapdev build || die "hmapdev build 失败(编译错误见上;它才是真正的编译入口)" # ── 3. 产物自报身份 ───────────────────────────────────────────────── BIN="build/plugin.bin" [[ -f "$BIN" ]] || die "没找到产物 $BIN" echo "[build] 产物:$BIN($(stat -c%s "$BIN") 字节)" echo "[build] 本次身份:SDK=$SDK_DIR hash=$SDK_HASH version=${SDK_VER:-未知}" cat > build/BUILD_INFO.txt <