//go:build ignore // gen_plugins generates plugins_gen.go from: // 1. plugins.json — built-in plugins (in cyrene-plugins) // 2. ../plugins/*/plugin.json — user plugins (auto-discovered) // // Usage: go run gen_plugins.go package main import ( "encoding/json" "fmt" "os" "path/filepath" "sort" "strings" ) type PluginEntry struct { Name string `json:"name"` Import string `json:"import,omitempty"` // built-in: explicit import Struct string `json:"struct"` Constructor string `json:"constructor,omitempty"` } type PluginManifest struct { Name string `json:"name"` Struct string `json:"struct"` } // ── load built-in plugins from plugins.json ── func loadBuiltinPlugins() ([]PluginEntry, error) { data, err := os.ReadFile("../plugins.json") if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("read plugins.json: %w", err) } var cfg struct { Version string `json:"version"` Plugins []PluginEntry `json:"plugins"` } if err := json.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parse plugins.json: %w", err) } return cfg.Plugins, nil } // ── auto-discover user plugins from ../plugins/ ── func discoverUserPlugins() ([]PluginEntry, error) { pluginsDir := filepath.Join("..", "..", "plugins") entries, err := os.ReadDir(pluginsDir) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("read plugins dir: %w", err) } var result []PluginEntry for _, e := range entries { if !e.IsDir() { continue } dir := filepath.Join(pluginsDir, e.Name()) // 必须有 plugin.json manifestPath := filepath.Join(dir, "plugin.json") manifestData, err := os.ReadFile(manifestPath) if err != nil { continue } var m PluginManifest if err := json.Unmarshal(manifestData, &m); err != nil { fmt.Fprintf(os.Stderr, "⚠ skip %s: bad plugin.json: %v\n", e.Name(), err) continue } if m.Name == "" { m.Name = e.Name() } // 从 go.mod 读取模块路径作为 import modPath := filepath.Join(dir, "go.mod") modData, err := os.ReadFile(modPath) if err != nil { fmt.Fprintf(os.Stderr, "⚠ skip %s: no go.mod: %v\n", e.Name(), err) continue } importPath := parseModule(modData) if importPath == "" { fmt.Fprintf(os.Stderr, "⚠ skip %s: cannot parse module from go.mod\n", e.Name()) continue } result = append(result, PluginEntry{ Name: m.Name, Import: importPath, Struct: m.Struct, }) fmt.Printf(" ✓ discovered %s → %s\n", m.Name, importPath) } sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name }) return result, nil } func parseModule(data []byte) string { lines := strings.Split(string(data), "\n") for _, line := range lines { line = strings.TrimSpace(line) if strings.HasPrefix(line, "module ") { return strings.TrimSpace(strings.TrimPrefix(line, "module ")) } } return "" } // ── generate plugins_gen.go ── func generate(plugins []PluginEntry) error { var sb strings.Builder sb.WriteString("// Code generated by gen_plugins.go; DO NOT EDIT.\n\n") sb.WriteString("package main\n\n") sb.WriteString("import (\n") sb.WriteString("\tplgSDK \"git.yeij.top/AskaEth/Cyrene-Plugins/sdk\"\n") aliases := make(map[string]string) imported := make(map[string]bool) for _, p := range plugins { pkgAlias := p.Name + "pkg" aliases[p.Name] = pkgAlias if !imported[p.Import] { sb.WriteString(fmt.Sprintf("\t%s \"%s\"\n", pkgAlias, p.Import)) imported[p.Import] = true } } sb.WriteString(")\n\n") sb.WriteString("func registerPlugins(registry interface{ Register(plgSDK.Tool) error }) {\n") for _, p := range plugins { alias := aliases[p.Name] if p.Constructor != "" { if p.Name == "file_ops" || p.Name == "http_request" { sb.WriteString(fmt.Sprintf("\tfor _, t := range %s.%s(nil).Tools() {\n", alias, p.Constructor)) } else { sb.WriteString(fmt.Sprintf("\tfor _, t := range %s.%s().Tools() {\n", alias, p.Constructor)) } } else { sb.WriteString(fmt.Sprintf("\tfor _, t := range (&%s.%s{}).Tools() {\n", alias, p.Struct)) } sb.WriteString("\t\tregistry.Register(t)\n") sb.WriteString("\t}\n") } sb.WriteString("}\n") outPath := "plugins_gen.go" if err := os.WriteFile(outPath, []byte(sb.String()), 0644); err != nil { return fmt.Errorf("write %s: %w", outPath, err) } fmt.Printf("Generated %s with %d plugins\n", outPath, len(plugins)) return nil } // ── auto-add require + replace directives to go.mod ── func ensureGoMod(userPlugins []PluginEntry) error { modPath := filepath.Join("..", "go.mod") data, err := os.ReadFile(modPath) if err != nil { return fmt.Errorf("read go.mod: %w", err) } content := string(data) pluginsDir := filepath.Join("..", "..", "plugins") entries, _ := os.ReadDir(pluginsDir) for _, e := range entries { if !e.IsDir() { continue } dir := filepath.Join(pluginsDir, e.Name()) modData, err := os.ReadFile(filepath.Join(dir, "go.mod")) if err != nil { continue } mod := parseModule(modData) if mod == "" { continue } relPath, _ := filepath.Rel(filepath.Join(".."), dir) relPath = strings.ReplaceAll(relPath, "\\", "/") // ensure require reqLine := fmt.Sprintf("\t%s v0.0.0\n", mod) if !strings.Contains(content, reqLine) { reqBlock := strings.Index(content, "require (") if reqBlock < 0 { continue } closeIdx := strings.Index(content[reqBlock:], ")") if closeIdx < 0 { continue } insertAt := reqBlock + closeIdx content = content[:insertAt] + reqLine + content[insertAt:] } // ensure replace replaceLine := fmt.Sprintf("\t%s => %s\n", mod, relPath) if !strings.Contains(content, replaceLine) { replaceBlock := strings.Index(content, "replace (") if replaceBlock < 0 { content += fmt.Sprintf("\nreplace (\n%s)\n", replaceLine) } else { closeIdx := strings.Index(content[replaceBlock:], ")") if closeIdx < 0 { continue } insertAt := replaceBlock + closeIdx content = content[:insertAt] + replaceLine + content[insertAt:] } } } return os.WriteFile(modPath, []byte(content), 0644) } func main() { builtins, err := loadBuiltinPlugins() if err != nil { fmt.Fprintf(os.Stderr, "load builtins: %v\n", err) os.Exit(1) } fmt.Printf("Built-in plugins: %d\n", len(builtins)) fmt.Println("Discovering user plugins...") userPlugins, err := discoverUserPlugins() if err != nil { fmt.Fprintf(os.Stderr, "discover user plugins: %v\n", err) os.Exit(1) } // 自动补 go.mod replace 指令 if len(userPlugins) > 0 { if err := ensureGoMod(userPlugins); err != nil { fmt.Fprintf(os.Stderr, "ensure go.mod: %v\n", err) } } all := append(builtins, userPlugins...) if err := generate(all); err != nil { fmt.Fprintf(os.Stderr, "generate: %v\n", err) os.Exit(1) } }