崩溃循环的根因不是"忘了重编",是**把 SDK 版本钉死**:
- `plugin.json`/`plg.json` 写死 `"sdk": "1.3.0"`(本机已装 21 个插件里只有 3 个声明该字段);
- `go.mod` 的 replace 指向 `/root/.homeagent/hmapdev/sdk/v1.3.0`(绝对路径 + 具体版本目录)。
于是"照原样重编"只会再造一个装上去就崩的包。实测本机内核链的是**另一条 SDK 血脉**:
$ go version -m /usr/local/bin/homed
dep gitcode.com/JianFeeeee/homeagent-sdk v0.9.2
=> ./third_party/homeagent-sdk (devel)
所以判据改成**两个二进制的依赖表一致**,而不是"版本号看起来像":
- `build.sh`:读内核的 SDK 依赖(`go version -m`)→ 据此解析 SDK 源码目录 →
用 `-modfile` 临时替换(不改动受版本管理的 go.mod)→ 构建 → **回头验产物**:
产物与内核链的 SDK 版本不一致就报错退出,绝不产出"装上去就崩"的包。
- `manifest_sdk_test.go`:① 清单不许写死 `sdk`;② go.mod 的钉子不得偏离构建机指针
(取不到基准就红,不许默默放过);③ `build.sh` 必须在(它是与内核对齐的硬校验点)。
三条都做过变异验证(写死 sdk / 钉子漂到 v1.2.0 / 删掉 build.sh → 各自红)。
顺带查出一个比"重编"更根本的事实:本机 `build.sh` 跑到编译就失败 ——
./plugin.go:217:18: sett.DataDir undefined (type sdk.SettingsAPI has no field or method DataDir)
即本插件源码用的是 **1.3.0 SDK 的 API**,而本机内核链的是 0.9.x 血脉:**重编解决不了**,
要么内核改成链 1.3.x 的 SDK,要么把插件移植到内核那份 SDK(这是 HomeAgent 侧的决定)。
95 lines
4.0 KiB
Go
95 lines
4.0 KiB
Go
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"os"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
/*
|
||
* 判据:**插件清单里不许写死 sdk 版本**。
|
||
*
|
||
* 2026-09-14 的事故与它当天的复发,根因都不是"忘了重编",而是把 SDK 版本钉在源码里:
|
||
*
|
||
* · 08:09 那次:内核 v1.3.12,插件二进制还是 sdk=1.2.0 → 每次启动 SIGSEGV,
|
||
* 内核 5 分钟内崩溃 4 次后熔断("停止自动重启,需人工介入");
|
||
* · 当天稍晚复发:内核升到 1.4.0,而 `go.mod` 里 replace 指向
|
||
* `/root/.homeagent/hmapdev/sdk/v1.3.0` —— **「照原样重编」只会再造一个 1.3.0**。
|
||
*
|
||
* 写死的那份版本号,和「内核实际用的版本」没有任何机制保证一致,
|
||
* 而失配的后果是启动即崩、且要人介入才能恢复。所以:
|
||
*
|
||
* 1. `plugin.json` / `plg.json` 不得出现 `sdk` 字段 —— 它必须由构建工具
|
||
* 按**本次实际编译用的 SDK** 写入(见 `build.sh`:解析本机 current 指针 + 构建后校验);
|
||
* 2. `go.mod` 的 `replace` 不得指向带版本号的绝对路径(同样是与内核脱钩的钉子)。
|
||
*
|
||
* 这两条都是**来源**判据(验形态,不验行为):真正的一致性校验在 `build.sh` 里,
|
||
* 它拿内核内嵌的版本号跟本次构建用的比,不一致就拒绝出包。
|
||
*/
|
||
func TestManifestMustNotPinSDKVersion(t *testing.T) {
|
||
for _, name := range []string{"plugin.json", "plg.json"} {
|
||
raw, err := os.ReadFile(name)
|
||
if err != nil {
|
||
t.Fatalf("读不到 %s: %v", name, err)
|
||
}
|
||
var m map[string]any
|
||
if err := json.Unmarshal(raw, &m); err != nil {
|
||
t.Fatalf("%s 不是合法 JSON: %v", name, err)
|
||
}
|
||
if v, ok := m["sdk"]; ok {
|
||
t.Errorf("%s 里写死了 sdk=%v —— 这个字段要由构建工具按实际编译用的 SDK 写入,"+
|
||
"手写等于把插件钉在某个内核版本上:内核一升就握手失败、启动即崩、"+
|
||
"而且重编也修不好(2026-09-14 的事故与同日复发都是这个形状)。"+
|
||
"删掉它,用 build.sh 构建(它会解析本机 SDK 并与内核版本比对)。", name, v)
|
||
}
|
||
if _, ok := m["version"]; !ok {
|
||
t.Errorf("%s 缺少 version 字段", name)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSDKPinMatchesBuildMachinePointer(t *testing.T) {
|
||
raw, err := os.ReadFile("go.mod")
|
||
if err != nil {
|
||
t.Fatalf("读不到 go.mod: %v", err)
|
||
}
|
||
pin := ""
|
||
for _, line := range strings.Split(string(raw), "\n") {
|
||
if strings.Contains(line, "replace") && strings.Contains(line, "homeagent-sdk") {
|
||
parts := strings.Split(line, "=>")
|
||
if len(parts) == 2 {
|
||
pin = strings.TrimSpace(parts[1])
|
||
}
|
||
}
|
||
}
|
||
if pin == "" {
|
||
t.Fatal("go.mod 里没有指向本机 SDK 的 replace —— 离线环境下 go test/go build 会直接失败," +
|
||
"请恢复它(路径取 <SDK_ROOT>/current 指向的目录)")
|
||
}
|
||
|
||
// 基准:构建机的 SDK 指针。取不到就是"判据读不懂",不许默默放过。
|
||
root := os.Getenv("HOMEAGENT_SDK_ROOT")
|
||
if root == "" {
|
||
root = "/root/.homeagent/hmapdev/sdk"
|
||
}
|
||
curRaw, err := os.ReadFile(root + "/current")
|
||
if err != nil {
|
||
t.Fatalf("取不到基准:读不到 %s/current(%v)。"+
|
||
"这条判据判的是「钉子有没有偏离构建机的 SDK 指针」,没有基准就无法判断 —— "+
|
||
"用 HOMEAGENT_SDK_ROOT 指定 SDK 根目录。", root, err)
|
||
}
|
||
cur := strings.TrimSpace(string(curRaw))
|
||
if !strings.HasSuffix(pin, "/"+cur) {
|
||
t.Errorf("go.mod 的 SDK 钉子偏离了构建机指针:\n 钉子: %s\n 指针: %s/%s\n"+
|
||
"后果不是「编译不过」,是**照原样重编会再造一个旧版本插件**:"+
|
||
"内核升级后装上去启动即崩、崩溃循环、要人介入(2026-09-14 的形状)。\n"+
|
||
"改法:把钉子改成指针指向的目录;内核与 SDK 是否真的对齐由 build.sh 硬校验。",
|
||
pin, root, cur)
|
||
}
|
||
if _, err := os.Stat("build.sh"); err != nil {
|
||
t.Error("找不到 build.sh —— 它是「与内核版本一致性」的硬校验点。" +
|
||
"只有钉子、没有构建期校验的话,「current 与内核不一致」这种状态就没人抓了。")
|
||
}
|
||
}
|