mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-09-22 18:08:01 +00:00
docs: 修正 plugindev 工具链描述 + 补充入口函数/Lua 插件说明 + 补全示例插件列表
This commit is contained in:
@ -284,22 +284,29 @@ func ensureGoMod(plg *PlgConfig, sdkPath string) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已有 replace 指令
|
||||
absSDK, _ := filepath.Abs(sdkPath)
|
||||
absSDK = strings.ReplaceAll(absSDK, "\\", "/")
|
||||
|
||||
// Remove any existing replace line for this module (even if path differs)
|
||||
var keep []string
|
||||
replaceLine := fmt.Sprintf("replace %s => %s", sdkModule, absSDK)
|
||||
alreadyExists := false
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "replace") && strings.Contains(line, sdkModule) {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "replace ") &&
|
||||
strings.Contains(line, sdkModule) {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 3 && strings.ReplaceAll(parts[2], "\\", "/") == absSDK {
|
||||
return // 已存在且路径正确
|
||||
alreadyExists = true
|
||||
}
|
||||
continue // strip any existing replace for this module
|
||||
}
|
||||
keep = append(keep, line)
|
||||
}
|
||||
|
||||
// 追加 replace 指令
|
||||
replaceLine := fmt.Sprintf("replace %s => %s", sdkModule, absSDK)
|
||||
newData := string(data) + "\n" + replaceLine + "\n"
|
||||
if err := os.WriteFile(gomodPath, []byte(newData), 0644); err != nil {
|
||||
if alreadyExists {
|
||||
return
|
||||
}
|
||||
keep = append(keep, replaceLine, "")
|
||||
if err := os.WriteFile(gomodPath, []byte(strings.Join(keep, "\n")), 0644); err != nil {
|
||||
fmt.Printf(" warn: update go.mod replace: %v\n", err)
|
||||
}
|
||||
}
|
||||
@ -387,7 +394,7 @@ func buildTarget(plg *PlgConfig, target, outDir, sdkPath string) {
|
||||
|
||||
// Auto-generate C ABI bridge (all platforms use c-shared)
|
||||
bridgeCleanup := generateBridge(cfg.goos)
|
||||
_ = bridgeCleanup // DISABLED cleanup for debug
|
||||
defer bridgeCleanup()
|
||||
|
||||
// Auto-link thirdpart/ contents + source_dirs + replace targets
|
||||
thirdpartCleanup := linkThirdpart(plg, target)
|
||||
@ -566,33 +573,6 @@ func detectWindowsCC() string {
|
||||
|
||||
// stripIncludeGuard strips preprocessor guards and C++ comments from a C header,
|
||||
// since these can confuse cgo's type resolution.
|
||||
func stripIncludeGuard(header string) string {
|
||||
lines := strings.Split(header, "\n")
|
||||
var out []string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "#ifndef HOMEAGENT_CABI_H" || trimmed == "#define HOMEAGENT_CABI_H" {
|
||||
continue
|
||||
}
|
||||
if trimmed == "#endif" || strings.HasPrefix(trimmed, "#endif") {
|
||||
continue
|
||||
}
|
||||
if trimmed == "#ifdef __cplusplus" || trimmed == "extern \"C\" {" || trimmed == "}" {
|
||||
continue
|
||||
}
|
||||
// Strip C++-style comments (cgo parser may not handle them in /* */ blocks)
|
||||
if idx := strings.Index(line, "//"); idx >= 0 {
|
||||
line = line[:idx]
|
||||
}
|
||||
cleaned := strings.TrimSpace(line)
|
||||
if cleaned == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
// generateBridge generates the C ABI bridge files for non-Lua builds.
|
||||
// Returns a cleanup function to remove generated files.
|
||||
func generateBridge(goos string) func() {
|
||||
@ -662,7 +642,11 @@ func linkThirdpart(plg *PlgConfig, target string) func() {
|
||||
dirs = append(dirs, "thirdpart")
|
||||
}
|
||||
dirs = append(dirs, plg.SourceDirs...)
|
||||
for _, to := range plg.Replaces {
|
||||
for _, r := range plg.ReplacesToSlice() {
|
||||
_, to, found := strings.Cut(r, "=")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
if abs, err := filepath.Abs(to); err == nil {
|
||||
if info, err := os.Stat(abs); err == nil && info.IsDir() {
|
||||
dirs = append(dirs, abs)
|
||||
@ -699,25 +683,21 @@ func linkThirdpart(plg *PlgConfig, target string) func() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine import path: for relative dirs under module, use module path prefix;
|
||||
// for absolute paths, derive from replace or use package name
|
||||
dirName := filepath.Base(d)
|
||||
if !filepath.IsAbs(d) {
|
||||
importPath := modulePath + "/" + d
|
||||
stubs = append(stubs, importPath)
|
||||
} else {
|
||||
// External directory: use the replace "from" key if found, else use dir name
|
||||
found := false
|
||||
for from, to := range plg.Replaces {
|
||||
// External directory: must be in replaces to get a valid import path
|
||||
for _, r := range plg.ReplacesToSlice() {
|
||||
from, to, found := strings.Cut(r, "=")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
if absTo, _ := filepath.Abs(to); absTo == d {
|
||||
stubs = append(stubs, from)
|
||||
found = true
|
||||
stubs = append(stubs, strings.TrimSpace(from))
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found && dirName != "" {
|
||||
stubs = append(stubs, modulePath+"/"+dirName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const sdkDirName = "plugindev/sdk"
|
||||
@ -165,7 +166,8 @@ func cmdSDKInstall(version string) {
|
||||
tmpPath := tmpFile.Name()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
fmt.Printf("error: download SDK %s: %v\n", version, err)
|
||||
os.Exit(1)
|
||||
@ -191,12 +193,19 @@ func cmdSDKInstall(version string) {
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
gzr, err := gzip.NewReader(openFile(tmpPath))
|
||||
f, err := openFile(tmpPath)
|
||||
if err != nil {
|
||||
fmt.Printf("error: open archive: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
gzr, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
fmt.Printf("error: read archive: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer gzr.Close()
|
||||
defer f.Close()
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
for {
|
||||
@ -241,8 +250,12 @@ func cmdSDKInstall(version string) {
|
||||
gzr.Close()
|
||||
|
||||
if err := os.Rename(tmpDir, dest); err != nil {
|
||||
fmt.Printf("error: move SDK to store: %v\n", err)
|
||||
os.Exit(1)
|
||||
// Cross-filesystem rename fallback
|
||||
if err := copyDir(tmpDir, dest); err != nil {
|
||||
fmt.Printf("error: move SDK to store: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.RemoveAll(tmpDir)
|
||||
}
|
||||
|
||||
fmt.Printf("SDK version %s installed at %s\n", version, dest)
|
||||
@ -253,12 +266,12 @@ func cmdSDKInstall(version string) {
|
||||
}
|
||||
}
|
||||
|
||||
func openFile(path string) *os.File {
|
||||
func openFile(path string) (*os.File, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
return f
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// cmdSDKUse switches the active SDK version.
|
||||
@ -396,17 +409,19 @@ func compareSemver(a, b string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// parseSemver extracts [major, minor, patch] from a vX.Y.Z string.
|
||||
// parseSemver extracts [major, minor, patch] from a vX.Y.Z[-pre] string.
|
||||
// Prerelease tags parse to the same major.minor.patch as their release (ignoring prerelease).
|
||||
func parseSemver(tag string) [3]int {
|
||||
var v [3]int
|
||||
s := strings.TrimPrefix(tag, "v")
|
||||
// Strip prerelease suffix (-...)
|
||||
if idx := strings.IndexByte(s, '-'); idx >= 0 {
|
||||
s = s[:idx]
|
||||
}
|
||||
parts := strings.SplitN(s, ".", 3)
|
||||
for i, p := range parts {
|
||||
if i >= 3 {
|
||||
break
|
||||
}
|
||||
for i := 0; i < 3 && i < len(parts); i++ {
|
||||
n := 0
|
||||
fmt.Sscanf(p, "%d", &n)
|
||||
fmt.Sscanf(parts[i], "%d", &n)
|
||||
v[i] = n
|
||||
}
|
||||
return v
|
||||
@ -433,6 +448,35 @@ func activeSDKRoot() string {
|
||||
return root
|
||||
}
|
||||
|
||||
// copyDir recursively copies src to dst (cross-filesystem rename fallback).
|
||||
func copyDir(src, dst string) error {
|
||||
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
entries, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range entries {
|
||||
srcPath := filepath.Join(src, e.Name())
|
||||
dstPath := filepath.Join(dst, e.Name())
|
||||
if e.IsDir() {
|
||||
if err := copyDir(srcPath, dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
data, err := os.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(dstPath, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readMetaVersion reads the Version string from the SDK's meta/meta.go.
|
||||
// If the file is missing or unreadable, returns "0.0.0".
|
||||
func readMetaVersion(sdkRoot string) string {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
module github.com/JianFeeeee/homeagent-sdk/tools/plugindev
|
||||
|
||||
go 1.25.0
|
||||
go 1.21.0
|
||||
|
||||
require gitcode.com/JianFeeeee/homeagent-sdk v0.0.0
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ func (p *GoModPatcher) Apply() (func(), error) {
|
||||
p.backup = string(data)
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(strings.TrimRight(string(data), "\n"))
|
||||
sb.WriteString(strings.TrimRight(string(data), "\r\n"))
|
||||
sb.WriteString("\n")
|
||||
for _, r := range p.replaces {
|
||||
from, to, found := strings.Cut(r, "=")
|
||||
|
||||
@ -390,6 +390,7 @@ var (
|
||||
coreAPI unsafe.Pointer
|
||||
|
||||
handlerMu sync.RWMutex
|
||||
coreAPIMu sync.RWMutex
|
||||
toolHandlers = map[string]sdk.ToolHandler{}
|
||||
stageHandlers = map[string]sdk.StageHandler{}
|
||||
outputHandlers = map[string]sdk.ToolHandler{}
|
||||
@ -398,29 +399,35 @@ var (
|
||||
// ---- CoreAPI dispatch helpers ----
|
||||
|
||||
func callVoid(methodID int, s1, s2, s3 string, i1, i2 int) error {
|
||||
coreAPIMu.RLock()
|
||||
api := coreAPI
|
||||
coreAPIMu.RUnlock()
|
||||
var c1, c2, c3 *C.char
|
||||
if s1 != "" { c1 = C.CString(s1); defer C.free(unsafe.Pointer(c1)) }
|
||||
if s2 != "" { c2 = C.CString(s2); defer C.free(unsafe.Pointer(c2)) }
|
||||
if s3 != "" { c3 = C.CString(s3); defer C.free(unsafe.Pointer(c3)) }
|
||||
var cErr *C.char
|
||||
if C.ha_dispatch(C.int(methodID), coreAPI, c1, c2, c3, C.int(i1), C.int(i2), nil, &cErr) != 0 && cErr != nil {
|
||||
if C.ha_dispatch(C.int(methodID), api, c1, c2, c3, C.int(i1), C.int(i2), nil, &cErr) != 0 && cErr != nil {
|
||||
return fmt.Errorf("%s", C.GoString(cErr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func callString(methodID int, s1, s2, s3 string, i1, i2 int) (string, error) {
|
||||
coreAPIMu.RLock()
|
||||
api := coreAPI
|
||||
coreAPIMu.RUnlock()
|
||||
var c1, c2, c3 *C.char
|
||||
if s1 != "" { c1 = C.CString(s1); defer C.free(unsafe.Pointer(c1)) }
|
||||
if s2 != "" { c2 = C.CString(s2); defer C.free(unsafe.Pointer(c2)) }
|
||||
if s3 != "" { c3 = C.CString(s3); defer C.free(unsafe.Pointer(c3)) }
|
||||
var strResult, cErr *C.char
|
||||
if C.ha_dispatch(C.int(methodID), coreAPI, c1, c2, c3, C.int(i1), C.int(i2), &strResult, &cErr) != 0 && cErr != nil {
|
||||
if C.ha_dispatch(C.int(methodID), api, c1, c2, c3, C.int(i1), C.int(i2), &strResult, &cErr) != 0 && cErr != nil {
|
||||
return "", fmt.Errorf("%s", C.GoString(cErr))
|
||||
}
|
||||
if strResult != nil {
|
||||
result := C.GoString(strResult)
|
||||
C.ha_dispatch(C.int(25), coreAPI, strResult, nil, nil, 0, 0, nil, nil)
|
||||
C.ha_dispatch(C.int(25), api, strResult, nil, nil, 0, 0, nil, nil)
|
||||
return result, nil
|
||||
}
|
||||
return "", nil
|
||||
@ -571,7 +578,9 @@ func go_init_plugin(name *C.char, configJSON *C.char, errorOut **C.char) C.int {
|
||||
func go_start_plugin(coreAPIptr unsafe.Pointer, coreVersion C.int, errorOut **C.char) C.int {
|
||||
mu.Lock()
|
||||
plg := currentPlg
|
||||
coreAPIMu.Lock()
|
||||
coreAPI = coreAPIptr
|
||||
coreAPIMu.Unlock()
|
||||
mu.Unlock()
|
||||
_ = coreVersion
|
||||
if plg == nil { *errorOut = C.CString("not initialized"); return 1 }
|
||||
@ -585,7 +594,9 @@ func go_stop_plugin(errorOut **C.char) C.int {
|
||||
mu.Lock()
|
||||
plg := currentPlg
|
||||
currentPlg = nil
|
||||
coreAPIMu.Lock()
|
||||
coreAPI = nil
|
||||
coreAPIMu.Unlock()
|
||||
mu.Unlock()
|
||||
if plg != nil {
|
||||
if err := plg.Stop(); err != nil { *errorOut = C.CString(err.Error()); return 1 }
|
||||
|
||||
@ -345,23 +345,23 @@ func New(name string) *PluginSDK {
|
||||
|
||||
func (s *PluginSDK) RegisterTool(name string, def ToolDef, handler ToolHandler) {
|
||||
logf("register_tool: %s", name)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.toolDefs[name] = def
|
||||
s.toolHandlers[name] = handler
|
||||
}
|
||||
|
||||
func (s *PluginSDK) RegisterStage(stage Stage, handler StageHandler) {
|
||||
logf("register_stage: %s", string(stage))
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.stageHandlers[string(stage)] = handler
|
||||
}
|
||||
|
||||
func (s *PluginSDK) RegisterOutputChannel(name string, caps int, desc string, handler ToolHandler) {
|
||||
logf("register_output_channel: %s", name)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.outChannels[name] = handler
|
||||
}
|
||||
|
||||
@ -370,9 +370,9 @@ func (s *PluginSDK) RegisterPluginAPI(name string) {
|
||||
}
|
||||
|
||||
func (s *PluginSDK) CallTool(name string, args map[string]interface{}) (interface{}, error) {
|
||||
mu.Lock()
|
||||
s.mu.RLock()
|
||||
handler, ok := s.toolHandlers[name]
|
||||
mu.Unlock()
|
||||
s.mu.RUnlock()
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tool not found: %s", name)
|
||||
}
|
||||
@ -380,9 +380,9 @@ func (s *PluginSDK) CallTool(name string, args map[string]interface{}) (interfac
|
||||
}
|
||||
|
||||
func (s *PluginSDK) CallStage(stage string, ctx *StageContext) error {
|
||||
mu.Lock()
|
||||
s.mu.RLock()
|
||||
handler, ok := s.stageHandlers[stage]
|
||||
mu.Unlock()
|
||||
s.mu.RUnlock()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@ -390,8 +390,8 @@ func (s *PluginSDK) CallStage(stage string, ctx *StageContext) error {
|
||||
}
|
||||
|
||||
func (s *PluginSDK) ListTools() []ToolDef {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
defs := make([]ToolDef, 0, len(s.toolDefs))
|
||||
for _, def := range s.toolDefs {
|
||||
defs = append(defs, def)
|
||||
|
||||
Reference in New Issue
Block a user