mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-20 08:58:03 +00:00
docs+plugindev: README 同步子进程架构,scaffold 修 entry 与 go.sum 死路
三类问题,都会让新用户第一次上手就走错:
1. README 仍写 plugin.so
plugin.json 示例的 entry、entry 字段说明、.hmap 包格式三处描述的都是
已退场的 C ABI 产物。改为 plugin.bin,并补 bundle 模式下
plugin.bin.<goos>.<goarch> 的命名与安装时挑平台的行为。
2. cmd_init.go 的 entry := "plugin.so"
scaffold 出来的 plg.json 带着一个已退场的 entry 值。build 实际不看这个
值(只用它区分 Lua),但跟着模板走会误以为自己在做 C ABI 插件。
3. 生成的项目第一次 build 必定失败
go.mod 只 require 一个 gitcode 模块版本号且不生成 go.sum。gitcode 不在
proxy.golang.org 上,于是:
go build → missing go.sum entry
go mod tidy → 去公共 proxy 拉一个不存在的条目,超时
原来 cmdBuild 里那句 `go mod download <mod>` 走的正是这条死路,失败后
只打一行 warn 就继续编译,紧接着死在同一个错误上——用户看到两段无关报错。
修法分两处:
- 生成的 go.mod 直接写指向本机 SDK 的 replace(replace 到目录时 go 不
需要也不校验 go.sum)
- 新增 ensureSDKResolvable:三级策略(已有本地 replace → 探测本机 SDK
并写入 → 兜底 go mod tidy 带 -mod=mod,失败给可操作提示)。存量项目
go.mod 无 replace 时走第二级救回。
bin/ 5 个预编译二进制不再进仓库(改为 release 附件):
5 个平台各 26-28MB,每次重编都在 git 历史里再叠一份,而它们本质是可从源码
复现的产物。README 的下载说明同步改为 release 附件 URL + 从源码编译。
This commit is contained in:
@ -73,18 +73,9 @@ func cmdBuild(args []string) {
|
||||
// Ensure go.mod exists with correct SDK path
|
||||
sdkModule := ensureGoMod(plg, sdkPath)
|
||||
|
||||
// First build: fetch the SDK module (generates go.sum with zip hash)
|
||||
// 保证 SDK 模块可解析,否则编译必死在 "missing go.sum entry"。
|
||||
if sdkModule != "" {
|
||||
if _, err := os.Stat("go.sum"); os.IsNotExist(err) {
|
||||
dl := exec.Command("go", "mod", "download", sdkModule)
|
||||
dl.Env = os.Environ()
|
||||
dl.Stdout = os.Stdout
|
||||
dl.Stderr = os.Stderr
|
||||
fmt.Println(" downloading SDK module deps...")
|
||||
if err := dl.Run(); err != nil {
|
||||
fmt.Printf(" error: go mod download: %v\n", err)
|
||||
}
|
||||
}
|
||||
ensureSDKResolvable(plg, sdkModule, sdkPath)
|
||||
}
|
||||
|
||||
// Merge plg.json replaces + CLI overrides
|
||||
@ -343,6 +334,128 @@ func ensureGoMod(plg *PlgConfig, sdkPath string) string {
|
||||
return sdkModule
|
||||
}
|
||||
|
||||
// ensureSDKResolvable 保证 SDK 模块在编译前可解析。
|
||||
//
|
||||
// 为何需要这个函数:gitcode 的模块不在 proxy.golang.org 上。只要 go.mod
|
||||
// 里的 SDK 靠 require 版本号解析,而本地又没 go.sum 条目,go build 就报
|
||||
// "missing go.sum entry";而原来那句 `go mod download <mod>` 会去公共 proxy
|
||||
// 拉一个永远拉不到的条目,超时后只打一行 warn 就继继编译,紧接着死在
|
||||
// 同一个错误上——新用户拿到的是两段无关的报错。
|
||||
//
|
||||
// 三级策略,按代价递增:
|
||||
// 1. go.mod 已有指向本地目录的 replace —— 什么都不用做(replace 到目录时
|
||||
// go 不需要也不校验 go.sum)。
|
||||
// 2. 能定位到本机 SDK 源码 —— 写入 replace。这是存量项目(go.mod 旧、
|
||||
// 无 replace)的救场路径。
|
||||
// 3. 都不行 —— 跑 `go mod tidy`(带 -mod=mod)让它自己去试,失败则给
|
||||
// 可操作的提示而不是让用户去猜。
|
||||
func ensureSDKResolvable(plg *PlgConfig, sdkModule, sdkPath string) {
|
||||
data, err := os.ReadFile("go.mod")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 策略 1:已有指向本地目录的 replace。
|
||||
// replace 目标带 / 或 . 开头的才是路径;指向另一个模块的 replace 不算。
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "replace ") || !strings.Contains(line, sdkModule) {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 4 {
|
||||
continue
|
||||
}
|
||||
target := parts[3]
|
||||
if strings.HasPrefix(target, ".") || strings.HasPrefix(target, "/") ||
|
||||
strings.Contains(target, ":/") || strings.Contains(target, ":\\") {
|
||||
return // 已指向本地目录,无需 go.sum
|
||||
}
|
||||
}
|
||||
|
||||
// 策略 2:能定位到本机 SDK 就写 replace。
|
||||
// resolveSDKPath 失败会 os.Exit,所以只在能确定拿到路径时调用它背后的探测。
|
||||
if root := findLocalSDK(sdkPath); root != "" {
|
||||
if appendGoModReplace(sdkModule, root) {
|
||||
fmt.Printf(" SDK 指向本机源码(已写入 go.mod replace):%s\n", root)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 策略 3:交给 go mod tidy。
|
||||
if _, err := os.Stat("go.sum"); err == nil {
|
||||
return // 已有 go.sum,不插手
|
||||
}
|
||||
fmt.Println(" 解析 SDK 依赖(go mod tidy)...")
|
||||
tidy := exec.Command("go", "mod", "tidy")
|
||||
tidy.Env = append(os.Environ(), "GOFLAGS=-mod=mod")
|
||||
if out, err := tidy.CombinedOutput(); err != nil {
|
||||
fmt.Printf(" warn: go mod tidy 失败:%v\n", err)
|
||||
if len(out) > 0 {
|
||||
fmt.Printf(" %s\n", strings.TrimSpace(string(out)))
|
||||
}
|
||||
fmt.Printf(" 提示:%s 不在公共 proxy 上。用以下任一方式指向本机 SDK:\n", sdkModule)
|
||||
fmt.Printf(" plugindev sdk install latest # 装一份到 ~/.homeagent/plugindev/sdk\n")
|
||||
fmt.Printf(" plugindev build --sdk-path <路径> # 或直接指定源码目录\n")
|
||||
}
|
||||
}
|
||||
|
||||
// findLocalSDK 探测本机 SDK 源码根目录,找不到返回空串。
|
||||
//
|
||||
// 与 resolveSDKPath 的区别:后者找不到就 os.Exit,适合“必须有”的调用点;
|
||||
// 这里是“有则更好”的探测,不能把构建搞挂。
|
||||
func findLocalSDK(sdkPath string) string {
|
||||
candidates := []string{}
|
||||
if sdkPath != "" {
|
||||
if abs, err := filepath.Abs(sdkPath); err == nil {
|
||||
candidates = append(candidates, abs)
|
||||
}
|
||||
}
|
||||
// plugindev 自身所在位置往上三级(tools/plugindev/plugindev → SDK 根)
|
||||
if self, err := os.Executable(); err == nil {
|
||||
candidates = append(candidates, filepath.Dir(filepath.Dir(filepath.Dir(self))))
|
||||
}
|
||||
// plugindev sdk use 选定的版本
|
||||
store := os.Getenv("HOMEAGENT_SDK_DIR")
|
||||
if store == "" {
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
store = filepath.Join(home, ".homeagent", "plugindev", "sdk")
|
||||
}
|
||||
}
|
||||
if store != "" {
|
||||
if d, err := os.ReadFile(filepath.Join(store, "current")); err == nil {
|
||||
if ver := strings.TrimSpace(string(d)); ver != "" {
|
||||
candidates = append(candidates, filepath.Join(store, ver))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range candidates {
|
||||
if c == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(c, "sdk", "plugin.go")); err == nil {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// appendGoModReplace 向 go.mod 追加一条 replace,成功返回 true。
|
||||
func appendGoModReplace(module, localPath string) bool {
|
||||
data, err := os.ReadFile("go.mod")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
abs, err := filepath.Abs(localPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
abs = strings.ReplaceAll(abs, "\\", "/")
|
||||
s := strings.TrimRight(string(data), "\r\n")
|
||||
s += fmt.Sprintf("\n\nreplace %s => %s\n", module, abs)
|
||||
return os.WriteFile("go.mod", []byte(s), 0644) == nil
|
||||
}
|
||||
|
||||
func resolveSDKPath(sdkPath string) string {
|
||||
if sdkPath != "" {
|
||||
abs, _ := filepath.Abs(sdkPath)
|
||||
@ -465,8 +578,8 @@ func buildTarget(plg *PlgConfig, target, outDir, sdkPath string) {
|
||||
}
|
||||
|
||||
type binEntry struct {
|
||||
src string // 磁盘路径,如 build/plugin.so
|
||||
zip string // zip 中条目名,如 plugin.so
|
||||
src string // 磁盘路径,如 build/plugin.bin
|
||||
zip string // zip 中条目名,如 plugin.bin.linux.amd64
|
||||
}
|
||||
|
||||
// createBundleHmap 创建包含多平台二进制的 bundle .hmap 文件。
|
||||
|
||||
@ -59,6 +59,14 @@ type TemplateData struct {
|
||||
GoVersion string
|
||||
SDKModule string
|
||||
SDKVersion string
|
||||
|
||||
// SDKLocalPath 是本机 SDK 源码绝对路径,写入生成的 go.mod 作为 replace 目标。
|
||||
//
|
||||
// 为何必须写:gitcode 的模块不在 proxy.golang.org 上,只 require 一个
|
||||
// 版本号的 go.mod 配上缺失的 go.sum,新用户第一次 `plugindev build`
|
||||
// 必定死在 "missing go.sum entry",而 `go mod tidy` 又会去公共 proxy 拉
|
||||
// 一个不存在的条目。有了本地 replace,go 完全不需要 go.sum 条目。
|
||||
SDKLocalPath string
|
||||
}
|
||||
|
||||
func cmdInit(args []string) {
|
||||
@ -109,7 +117,12 @@ func cmdInit(args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
entry := "plugin.so"
|
||||
// Go 插件统一产出 plugin.bin(v1.0.0 子进程模式)。
|
||||
//
|
||||
// 此前这里写 "plugin.so",scaffold 出来的 plg.json 就带着一个已退场的
|
||||
// entry 值,新手跟着模板走会误以为自己在做 C ABI 插件。
|
||||
// build 实际不看这个值(只用它区分 Lua),但模板不应误导。
|
||||
entry := "plugin.bin"
|
||||
var targets string
|
||||
if isLua {
|
||||
entry = "main.lua"
|
||||
@ -137,14 +150,15 @@ func cmdInit(args []string) {
|
||||
}
|
||||
|
||||
// Detect SDK info for Go plugin go.mod.
|
||||
// 生成的 go.mod 只 require SDK 线上模块版本,不写本地路径 replace;
|
||||
// 本地调试请用 `plugindev build --sdk-path <path>` 或手动加 replace。
|
||||
// 生成的 go.mod 除 require 外还写一条指向本机 SDK 的 replace:
|
||||
// 否则 scaffold 出来的项目第一次 build 必定失败(详见 SDKLocalPath 注释)。
|
||||
if !isLua {
|
||||
sdkMod, goVer, _, sdkVer := detectSDKInfo()
|
||||
sdkMod, goVer, sdkRoot, sdkVer := detectSDKInfo()
|
||||
data.ModulePath = name
|
||||
data.GoVersion = goVer
|
||||
data.SDKModule = sdkMod
|
||||
data.SDKVersion = "v" + sdkVer
|
||||
data.SDKLocalPath = strings.ReplaceAll(sdkRoot, "\\", "/")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
|
||||
@ -19,7 +19,12 @@ const tmplGoMod = `module {{.ModulePath}}
|
||||
go {{.GoVersion}}
|
||||
|
||||
require {{.SDKModule}} {{.SDKVersion}}
|
||||
`
|
||||
{{if .SDKLocalPath}}
|
||||
// SDK 指向本机源码。gitcode 的模块不在 proxy.golang.org 上,
|
||||
// 没有这条 replace 就需要 go.sum 条目,而那个条目无处可拉。
|
||||
// 若你已有可访问的私有 proxy,可删掉本行。
|
||||
replace {{.SDKModule}} => {{.SDKLocalPath}}
|
||||
{{end}}`
|
||||
|
||||
const tmplPluginGo = `package main
|
||||
|
||||
|
||||
Reference in New Issue
Block a user