diff options
| -rw-r--r-- | argv.go | 16 | ||||
| -rw-r--r-- | init.go | 32 | ||||
| -rw-r--r-- | plugin.go | 32 | ||||
| -rw-r--r-- | pluginCheck.go | 130 |
4 files changed, 176 insertions, 34 deletions
@@ -11,11 +11,12 @@ This struct can be used with the go-arg package. These are the generic default command line arguments for the 'GUI' package */ type ArgsGui struct { - NoGui bool `arg:"--no-gui" help:"ignore all these gui problems"` - GuiPlugin string `arg:"--gui" help:"Use this gui toolkit [andlabs,gocui,nocui,stdin]"` - GuiFile string `arg:"--gui-file" help:"Use a specific plugin.so file"` - GuiTest string `arg:"--gui-test" help:"test a specific plugin.so will load"` - GuiVerbose bool `arg:"--gui-verbose" help:"enable all logging"` + NoGui bool `arg:"--no-gui" help:"ignore all these gui problems"` + GuiPlugin string `arg:"--gui" help:"Use this gui toolkit [andlabs,gocui,nocui,stdin]"` + GuiFile string `arg:"--gui-file" help:"Use a specific plugin.so file"` + GuiTest string `arg:"--gui-test" help:"test a specific plugin.so will load"` + GuiVerbose bool `arg:"--gui-verbose" help:"enable all logging"` + GuiCheck string `arg:"--gui-check-plugin" help:"used to check if the plugin loads"` } /* @@ -32,6 +33,11 @@ func ArgToolkit() string { func InitArg() { arg.Register(&argGui) + + if argGui.GuiCheck != "" { + // does os.Exec() and does not return + TestPluginAndExit() + } } /* @@ -2,7 +2,6 @@ package gui import ( "errors" - "fmt" "os" "runtime/debug" @@ -38,7 +37,7 @@ func initNew() { me.guiChan = make(chan widget.Action, 1) version, err := getGuiVersion() - fmt.Println("GO GUI version", version, showVersion()+"<Bromeliaceae>", err) + log.Println("GO GUI version", version, showVersion()+"<Bromeliaceae>", err) if version == "" { log.Warn("Warning: compiled without version", err) log.Sleep(1) @@ -77,16 +76,16 @@ func getGuiVersion() (string, error) { if tmp == nil { return "", errors.New("compiled without go module support") } - // fmt.Println("mod.Path = ", tmp.Path) - // fmt.Println("mod.Main.Path = ", tmp.Main.Path) - // fmt.Println("mod.Main.Version = ", tmp.Main.Version) - // fmt.Println("mod.Main.Sum = ", tmp.Main.Sum) + // log.Println("mod.Path = ", tmp.Path) + // log.Println("mod.Main.Path = ", tmp.Main.Path) + // log.Println("mod.Main.Version = ", tmp.Main.Version) + // log.Println("mod.Main.Sum = ", tmp.Main.Sum) for _, value := range tmp.Deps { if value.Path == "go.wit.com/gui" { found = value.Version } - // fmt.Println("\tmod.Path = ", value.Path) - // fmt.Println("\tmod.Version = ", value.Version) + // log.Println("\tmod.Path = ", value.Path) + // log.Println("\tmod.Version = ", value.Version) } if found != "" { // log.Println("GUI build version:", found) @@ -282,7 +281,8 @@ func New() *Node { } */ if argGui.GuiTest != "" { - testPlugin() + // does os.Exec() and does not return + TestPluginAndExit() } initNew() @@ -296,6 +296,13 @@ func NoGui() bool { // try to load andlabs, if that doesn't work, fall back to the console func (n *Node) Default() *Node { var err error + // used to check if plugins load or not + if argGui.GuiCheck != "" { + // does os.Exec() and does not return + TestPluginAndExit() + os.Exit(0) + } + if argGui.NoGui { log.Log(WARN, "--no-gui chill Winston. I don't need no gui") return n @@ -415,12 +422,13 @@ func trapStdout() { origStderr = os.Stderr os.Stderr = guierrf + // maybe all this should be deleted now? println("TEST println") println("TEST println") println("TEST println") - fmt.Println("TEST fmt") - fmt.Println("TEST fmt") - fmt.Println("TEST fmt") + // maybe test fmt? or, maybe all this should be deleted now? + // fmt.Println("TEST fmt") + // fmt.Println("TEST fmt") log.Info("TEST log") log.Info("TEST log") log.Info("TEST log") @@ -8,6 +8,7 @@ package gui import ( "embed" "errors" + "fmt" "os" "path/filepath" "plugin" @@ -233,29 +234,26 @@ func searchPaths(name string) *aplug { return nil } -// tests the plugin file will load -func testPlugin() { - _, 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) -} - // load module // 1. open the shared object file to load the symbols func initToolkit(name string, filename string) *aplug { - if _, err := os.Stat(filename); err != nil { - if os.IsNotExist(err) { - log.Log(PLUG, "initToolkit() missing plugin", name, "as filename", filename) - return nil + /* + if _, err := os.Stat(filename); err != nil { + if os.IsNotExist(err) { + log.Log(PLUG, "initToolkit() missing plugin", name, "as filename", filename) + return nil + } } + log.Log(PLUG, "initToolkit() Found plugin", name, "as filename", filename) + + plug, err := plugin.Open(filename) + */ + if err := CheckPluginViaSubprocess(filename); err != nil { + fmt.Printf("initToolkit() subprocess load plugin failed: %v\n", err) + return nil } - log.Log(PLUG, "initToolkit() Found plugin", name, "as filename", filename) - plug, err := plugin.Open(filename) + plug, err := checkPlug(filename) if err != nil { // turn on PLUG debugging if something goes wrong PLUG.SetBool(true) diff --git a/pluginCheck.go b/pluginCheck.go new file mode 100644 index 0000000..b7657f6 --- /dev/null +++ b/pluginCheck.go @@ -0,0 +1,130 @@ +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 +} |
