mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-22 01:48:03 +00:00
plugindev: entry 语义收敛 + 删 C ABI 工具链 + Windows 共享内存适配(Part 6.1)
## entry 不再是通道开关 —— 外部插件零改动的关键 17 个存量插件的 plg.json 都写着 "entry": "plugin.so"。若把 entry 当通道 开关,迁移就得改 17 个文件,而「外部插件零改动」是本次迁移的硬约束。 改法:Go 插件一律产出 plugin.bin,不看 entry 值。isProcEntry 删除, resolveBuild 去掉 proc 参数。entry 现在只剩区分 Lua(main.lua)一个用途。 实测:weather 的 plg.json 一行不改(仍写 plugin.so),plugindev build 直接产出三平台 plugin.bin。 ## Windows 不再是能力退化的第三套实现(§9.2 的正解) C ABI 时代 Windows 是独立的第三套 ABI:dynamic_dll_windows.go 的 stage 只下发 3 个字段(raw_message/user_id/phase)且完全没有写回,sanitizer 这类改写型插件在 Windows 上静默失效,且无任何运行时警告。 现在 Windows 与 Unix 共用同一份 RPC 逻辑与同一份共享段布局。平台差异 收敛到三个挂载函数: - Unix(linux/darwin/freebsd):内核经 ExtraFiles 传继承 fd(3=StageContext 段,4=事件环段,5=eventfd/pipe) - Windows:没有 fd 继承语义(os/exec 的 ExtraFiles 在 Windows 不支持), 改用命名内核对象——父进程 CreateFileMapping/CreateEvent 建带名字的对象, 子进程 OpenFileMappingW/OpenEventW 按同名打开。名字经环境变量传入而非 硬编码:多个 homed 实例并存时不能撞名。 Windows 绑定用 syscall.NewLazyDLL 而非 golang.org/x/sys/windows: OpenFileMappingW/OpenEventW 未被标准库 syscall 导出,而引入 x/sys 会给 **每个插件的 go.mod** 加一个新依赖,违反「插件仅依赖公开 SDK」。 LazyDLL 属标准库,零新增依赖。 新增 evtWaiter 接口抽象等待语义:eventfd 是计数器(多事件合并成一次 唤醒),Windows Event 是二元信号。不影响正确性——消费者被唤醒后按 readSeq 追 writeSeq 批量 drain,一次唤醒能处理累积的全部事件。 模板拆成三个文件: proc_main.go.tmpl 平台无关(RPC + 共享段布局 + stage + 事件环消费) proc_shm_unix.go.tmpl 继承 fd 挂载 proc_shm_windows.go.tmpl 命名对象挂载 ## 删除 C ABI 工具链 templates.go 1296 → 516 行: - tmplBridge(Windows DLL bridge) -265 行 - tmplLinuxBridge(Linux c-shared) -457 行 - tmplPluginInitC(C 入口) -57 行 另删 generateBridge / detectWindowsCC(MinGW 探测)/ tmplCABIHeader / InitData.CABIVersion+CABIHeader。 交叉编译不再需要目标平台 C 工具链——这是 -buildmode=c-shared 退场的 连带收益(§3.1)。 ## 测试 15 项全过,新增 4 项守护迁移不变量: - AllPlatformsProduceBin:6 个 GOOS/GOARCH 组合统一产出 plugin.bin - LuaIsSeparatePath:Lua 仍走解释器路径 - UnsupportedOSErrors:不支持平台明确报错,不静默产出错误产物 - NoCABIResiduals:代码中不得再出现 c-shared / CGO_ENABLED=1 / detectWindowsCC / tmplLinuxBridge / tmplPluginInitC(注释除外) - IgnoresEntryForGoPlugins:isProcEntry 必须已删除 验证:go build/vet/test 全通过;三平台交叉编译产出 plugin.bin; git diff sdk/ 为空(接口冻结)。 Ref: docs/zh/架构迁移评估.md §3.1/§9.2、docs/zh/plugin-migration-plan.md Part 6
This commit is contained in:
@ -6,49 +6,86 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// 子进程插件运行时(外部插件多进程化,Part 3)。
|
||||
// 子进程插件运行时(外部插件多进程化)。
|
||||
//
|
||||
// 与旧 C ABI bridge 的差异:
|
||||
// - 模板改为**真实 .go 源文件**(templates/proc_main.go.tmpl)而非 raw string:
|
||||
// 900+ 行代码塞在字符串里,写错只能等生成插件时才炸;作为源文件可被
|
||||
// gofmt / go vet / parser 直接检查。
|
||||
// - 构建从 `-buildmode=c-shared` + CGO_ENABLED=1 改为普通 `go build` + CGO_ENABLED=0,
|
||||
// 交叉编译不再需要目标平台的 C 工具链(§3.1 连带消失项)。
|
||||
// 模板为何是**真实 .go 源文件** + //go:embed,而不是 raw string:
|
||||
// 1100+ 行代码塞在字符串里写错只能等生成插件时才炸;作为源文件可被
|
||||
// gofmt / go vet / go/parser 直接检查(proc_runtime_test.go 的 16 项
|
||||
// 静态检查就以此为前提)。
|
||||
//
|
||||
// 设计依据:docs/zh/架构迁移评估.md §3、docs/zh/plugin-migration-plan.md Part 3
|
||||
// 构建从 `-buildmode=c-shared` + CGO_ENABLED=1 变成普通 `go build` +
|
||||
// CGO_ENABLED=0,交叉编译不再需要目标平台的 C 工具链(§3.1 连带消失项)。
|
||||
//
|
||||
// 设计依据:docs/zh/架构迁移评估.md §3、docs/zh/plugin-migration-plan.md Part 3/6
|
||||
|
||||
//go:embed templates/proc_main.go.tmpl
|
||||
//go:embed templates/proc_shm_unix.go.tmpl
|
||||
//go:embed templates/proc_shm_windows.go.tmpl
|
||||
var procTemplates embed.FS
|
||||
|
||||
// procRuntimeFiles 列出生成到插件目录的运行时文件。
|
||||
//
|
||||
// 共享段与事件通知的**传递机制**按平台不同(Unix 继承 fd,
|
||||
// Windows 命名内核对象),故拆成带 build tag 的两个文件;
|
||||
// 共享段**布局**与 RPC 逻辑完全平台无关,全在 proc_main 里。
|
||||
//
|
||||
// 这正是三套独立 ABI 实现收敛为单一 RPC 实现的效果:
|
||||
// 平台差异从「整套 stage 下发/写回逻辑各写一份」缩到「三个挂载函数」。
|
||||
var procRuntimeFiles = []struct {
|
||||
tmpl string // 内嵌模板路径
|
||||
out string // 生成到插件目录的文件名
|
||||
}{
|
||||
{"templates/proc_main.go.tmpl", "z_proc_gen.go"},
|
||||
{"templates/proc_shm_unix.go.tmpl", "z_proc_shm_unix.go"},
|
||||
{"templates/proc_shm_windows.go.tmpl", "z_proc_shm_windows.go"},
|
||||
}
|
||||
|
||||
// procEntryFile 是子进程插件的入口二进制名(与内核 internal/plugin/dynamic.go 的 binEntry 一致)。
|
||||
//
|
||||
// 全平台同名:进程边界本身就是 ABI 边界,不存在平台特有的动态库扩展名
|
||||
// (对比 C ABI 时代的 .so/.dylib/.dll 三套产物 + 三套 ABI 实现)。
|
||||
const procEntryFile = "plugin.bin"
|
||||
|
||||
// procGenFile 是生成的运行时文件名。
|
||||
// 前缀 z_ 使其在目录列表中排在业务代码之后,且与旧 bridge 的 z_bridge_gen.go 风格一致。
|
||||
// luaEntryFile 是 Lua 插件的入口。Lua 走解释器,不经过 Go 编译。
|
||||
const luaEntryFile = "main.lua"
|
||||
|
||||
// procGenFile 是生成的主运行时文件名(兼容旧注释引用)。
|
||||
// 前缀 z_ 使其在目录列表中排在业务代码之后。
|
||||
const procGenFile = "z_proc_gen.go"
|
||||
|
||||
// generateProcRuntime 把子进程运行时写入插件目录,返回清理函数。
|
||||
//
|
||||
// 与 generateBridge 的差异:只写一个 .go 文件,不需要 C 入口(z_entry.c)。
|
||||
// generateProcRuntime 把子进程运行时(平台无关主体 + 两个平台挂载实现)
|
||||
// 写入插件目录,返回清理函数。
|
||||
func generateProcRuntime() (func(), error) {
|
||||
data, err := procTemplates.ReadFile("templates/proc_main.go.tmpl")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取内嵌模板: %w", err)
|
||||
}
|
||||
|
||||
// 清理可能残留的 C ABI 产物:同目录同时存在两套 main 会编译冲突。
|
||||
// 这也让 .so → .bin 的切换无需人工清理。
|
||||
// 清理历史 C ABI 产物:旧版 plugindev 生成过这两个文件,残留下来会与
|
||||
// 本模板的 main 冲突。无需人工清理就能从旧版升级。
|
||||
for _, stale := range []string{"z_bridge_gen.go", "z_entry.c"} {
|
||||
os.Remove(stale)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(procGenFile, data, 0644); err != nil {
|
||||
return nil, fmt.Errorf("写入 %s: %w", procGenFile, err)
|
||||
var written []string
|
||||
cleanup := func() {
|
||||
for _, f := range written {
|
||||
os.Remove(f)
|
||||
}
|
||||
}
|
||||
return func() { os.Remove(procGenFile) }, nil
|
||||
|
||||
for _, rf := range procRuntimeFiles {
|
||||
data, err := procTemplates.ReadFile(rf.tmpl)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, fmt.Errorf("读取内嵌模板 %s: %w", rf.tmpl, err)
|
||||
}
|
||||
if err := os.WriteFile(rf.out, data, 0644); err != nil {
|
||||
cleanup()
|
||||
return nil, fmt.Errorf("写入 %s: %w", rf.out, err)
|
||||
}
|
||||
written = append(written, rf.out)
|
||||
}
|
||||
return cleanup, nil
|
||||
}
|
||||
|
||||
// isProcEntry 判断 plg.json 的 entry 是否声明了子进程模式。
|
||||
func isProcEntry(entry string) bool {
|
||||
return entry == procEntryFile
|
||||
}
|
||||
// isProcEntry 已删除:Go 插件一律产出 plugin.bin,不再看 plg.json 的 entry 值。
|
||||
//
|
||||
// 为何忽略 entry:17 个存量插件的 plg.json 都写着 "plugin.so"。若把 entry 当作
|
||||
// 通道开关,迁移就得改 17 个文件——而「外部插件零改动」是本次迁移的硬约束。
|
||||
// entry 现在只用于区分 Lua(main.lua)与 Go 插件。
|
||||
|
||||
Reference in New Issue
Block a user