package gui import ( "debug/buildinfo" "errors" "os" "os/exec" "path/filepath" "plugin" "runtime/debug" "go.wit.com/log" ) // this doesn't work with GO111MODULE=off so it's mostly worthless // TODO: fix the GO binary so this works // 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) } log.Info("CHECK FINAL OK:", pluginPath) return nil } // tests the plugin file will load func testPluginOld() { _, err := plugin.Open(argGui.GuiPluginHack) if err != nil { log.Log(PLUG, "plugin.Open() FAILED =", argGui.GuiPluginHack, err) os.Exit(-1) } log.Log(PLUG, "plugin.Open() SUCCESS loading plugin =", argGui.GuiPluginHack) os.Exit(0) } func CheckPlugin() string { if argGui.GuiPluginHack != "" { // does os.Exec() and does not return testPluginAndExit() } return "boo" } // loads the plugin, then exits with 0 or -1 func testPluginAndExit() { log.Log(WARN, "TEST plugin START", argGui.GuiPluginHack) absPath, err := filepath.Abs(argGui.GuiPluginHack) if err != nil { log.Log(WARN, "TEST plugin FAILED", argGui.GuiPluginHack, absPath, err) os.Exit(-1) } log.Log(WARN, "TEST plugin START abs:", absPath, argGui.GuiPluginHack) plug, err := checkPlug(absPath) if plug == nil { log.Log(WARN, "TEST plugin failed (returned nil):", argGui.GuiPluginHack, absPath, err) log.Sleep(1) os.Exit(-1) } if err == nil { log.Log(WARN, "TEST plugin probably worked", argGui.GuiPluginHack, absPath) log.Log(WARN, "os.Exit(0)") os.Exit(0) } log.Log(WARN, "TEST plugin failed", argGui.GuiPluginHack, absPath, err) log.Sleep(1) os.Exit(-1) } func checkPluginViaSubprocess(path string) error { exe, err := os.Executable() if err != nil { return log.Errorf("failed to get executable path: %w", err) } resolved, err := filepath.EvalSymlinks(exe) if err != nil { return log.Errorf("failed to resolve executable symlink: %w", err) } argv := []string{resolved, "--gui-check-plugin", path, "--no-port"} log.Warn("RUNNING:", argv) // cmd := exec.Command(resolved, "--gui-check-plugin", path, "--no-port") cmd := exec.Command(argv[0], argv[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } // retuns true if func IsGoPluginTestHack() bool { if argGui.GuiPluginHack == "" { return false } return true } 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 }