package gui import ( "debug/buildinfo" "errors" "fmt" "os" "os/exec" "path/filepath" "plugin" "runtime/debug" "go.wit.com/log" ) // CheckPluginCompatibility verifies that the plugin .so file was built // with the same Go version and dependency versions as the host binary. func CheckPluginCompatibility(pluginPath string) error { pluginInfo, err := buildinfo.ReadFile(pluginPath) if err != nil { return log.Errorf("failed to read plugin build info: %w", err) } mainInfo, ok := debug.ReadBuildInfo() if !ok { log.Info("CHECK FAILED: failed to read main binary build info", pluginPath) log.Info("CHECK FAILED: failed to read main binary build info", pluginPath) return errors.New("CHECK FAILED: failed to read main binary build info") } if pluginInfo.GoVersion != mainInfo.GoVersion { return log.Errorf("Go version mismatch: plugin=%s, host=%s", pluginInfo.GoVersion, mainInfo.GoVersion) } log.Info("CHECK OK: binary build info:", pluginPath) // Create a map of main binary dependencies for quick lookup hostDeps := make(map[string]string) for _, dep := range mainInfo.Deps { hostDeps[dep.Path] = dep.Version } log.Info("CHECK OK: binary build info:", hostDeps) // Compare plugin dependencies for _, dep := range pluginInfo.Deps { hostVer, ok := hostDeps[dep.Path] if !ok { return log.Errorf("dependency %s not found in host binary", dep.Path) } if dep.Version != hostVer { return log.Errorf("dependency version mismatch for %s: plugin=%s, host=%s", dep.Path, dep.Version, hostVer) } log.Printf("DEP HASH: version %s: plugin=%s, host=%s\n", dep.Path, dep.Version, hostVer) } return nil } // tests the plugin file will load func testPluginOld() { _, err := plugin.Open(argGui.GuiTest) if err != nil { log.Log(PLUG, "plugin.Open() FAILED =", argGui.GuiTest, err) os.Exit(-1) } log.Log(PLUG, "plugin.Open() SUCCESS loading plugin =", argGui.GuiTest) os.Exit(0) } // loads the plugin, then exits with 0 or -1 func TestPluginAndExit() { log.Log(WARN, "TEST plugin START", argGui.GuiCheck) absPath, err := filepath.Abs(argGui.GuiCheck) if err != nil { log.Log(WARN, "TEST plugin FAILED", argGui.GuiCheck, absPath, err) os.Exit(-1) } log.Log(WARN, "TEST plugin START abs:", absPath, argGui.GuiCheck) plug, err := checkPlug(absPath) if plug == nil { log.Log(WARN, "TEST plugin failed (returned nil):", argGui.GuiCheck, absPath, err) os.Exit(-1) } if err == nil { log.Log(WARN, "TEST plugin probably worked", argGui.GuiCheck, absPath) os.Exit(0) } log.Log(WARN, "TEST plugin failed", argGui.GuiCheck, absPath, err) os.Exit(-1) } func CheckPluginViaSubprocess(path string) error { exe, err := os.Executable() if err != nil { return fmt.Errorf("failed to get executable path: %w", err) } resolved, err := filepath.EvalSymlinks(exe) if err != nil { return fmt.Errorf("failed to resolve executable symlink: %w", err) } cmd := exec.Command(resolved, "--gui-check-plugin", path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } func checkPlug(pluginPath string) (*plugin.Plugin, error) { // const pluginPath = "/tmp/gocui.so" // ../../../toolkits/gocui/gocui.so // /usr/lib/go-gui-toolkits/gocui.v0.22.46.so if err := CheckPluginCompatibility(pluginPath); err != nil { log.Printf("Plugin check failed: %v\n", err) return nil, err } p, err := plugin.Open(pluginPath) if err != nil { log.Printf("plugin.Open failed: %v\n", err) return nil, err } log.Println("Plugin loaded successfully.") return p, nil }