github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/golib/goimports/goimports.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package goimports 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "go/ast" 11 "go/parser" 12 "go/token" 13 "io" 14 "log" 15 "os" 16 "os/exec" 17 "path" 18 "plugin" 19 "strings" 20 "text/template" 21 ) 22 23 const Supported = true 24 25 // LoadGoPackage builds a plugin for a package if necessary, compiles it and loads 26 // it. The value of the "Exports" symbol is returned if successful. 27 func LoadGoPackage(pkg string, pluginRoot string, forceBuild bool) (map[string]interface{}, error) { 28 pluginDir := path.Join(pluginRoot, pkg) 29 filename := path.Base(pluginDir) + ".so" 30 pluginPath := path.Join(pluginDir, filename) 31 var p *plugin.Plugin 32 var err error 33 if !forceBuild { 34 p, err = plugin.Open(pluginPath) 35 forceBuild = err != nil 36 } 37 if forceBuild { 38 err = buildPlugin(pkg, pluginPath) 39 if err != nil { 40 return nil, err 41 } 42 p, err = plugin.Open(pluginPath) 43 if err != nil { 44 return nil, err 45 } 46 } 47 exportsI, err := p.Lookup("Exports") 48 if err != nil { 49 return nil, err 50 } 51 exports, ok := exportsI.(*map[string]interface{}) 52 if !ok { 53 return nil, errors.New("Exports has incorrect type") 54 } 55 return *exports, nil 56 } 57 58 type libModel struct { 59 Package string 60 PackageName string 61 Funcs []string 62 Types []string 63 vars []string 64 consts []string 65 } 66 67 func getPackagePath(pkg string) (string, string, error) { 68 res, err := exec.Command("go", "list", "-f", "{{.Dir}} {{.Name}}", pkg).Output() 69 if err != nil { 70 return "", "", err 71 } 72 bits := strings.Split(strings.TrimSpace(string(res)), " ") 73 return bits[0], bits[1], nil 74 } 75 76 func buildPlugin(pkg string, pluginPath string) error { 77 pluginDir := path.Dir(pluginPath) 78 pluginFile := path.Base(pluginPath) 79 os.MkdirAll(pluginDir, 0777) 80 f, err := os.Create(path.Join(pluginDir, "plugin.go")) 81 if err != nil { 82 return err 83 } 84 if err := buildLib(pkg, f); err != nil { 85 return err 86 } 87 runner := cmdRunner{dir: pluginDir} 88 89 // Since go 1.16 we need a go.mod file 90 runner. 91 run("go", "mod", "init", "github.com/arnode/goluaplugins/"+pkg). 92 andRun("go", "mod", "tidy"). 93 andRun("go", "build", "-o", pluginFile, "-buildmode=plugin") 94 95 if runner.err != nil { 96 return errors.New(runner.errMsg()) 97 } 98 return nil 99 } 100 101 type cmdRunner struct { 102 dir string 103 err error 104 errBuf bytes.Buffer 105 } 106 107 func (r *cmdRunner) run(name string, args ...string) *cmdRunner { 108 cmd := exec.Command(name, args...) 109 cmd.Dir = r.dir 110 r.errBuf = bytes.Buffer{} 111 cmd.Stderr = &r.errBuf 112 r.err = cmd.Run() 113 return r 114 } 115 116 // Run if no previous error 117 func (r *cmdRunner) andRun(name string, args ...string) *cmdRunner { 118 if r.err != nil { 119 return r 120 } 121 return r.run(name, args...) 122 } 123 124 func (r *cmdRunner) errMsg() string { 125 return string(r.errBuf.Bytes()) 126 } 127 128 func buildLib(pkg string, out io.Writer) error { 129 pkgDir, pkgName, err := getPackagePath(pkg) 130 if err != nil { 131 return err 132 } 133 log.Printf("Creating lib for package %s at %s", pkgName, pkgDir) 134 fset := token.NewFileSet() 135 pkgs, err := parser.ParseDir(fset, pkgDir, nil, 0) 136 if err != nil { 137 return fmt.Errorf("Error parsing package: %s", err) 138 } 139 model := &libModel{ 140 Package: pkg, 141 PackageName: pkgName, 142 } 143 fillModel(model, pkgs[pkgName]) 144 getTemplate().Execute(out, model) 145 return nil 146 } 147 148 func fillModel(model *libModel, pkg *ast.Package) { 149 fset := token.NewFileSet() 150 files := make(map[string]*ast.File) 151 for fName, f := range pkg.Files { 152 if !strings.HasSuffix(fName, "_test.go") { 153 files[fName] = f 154 } 155 } 156 pkg, _ = ast.NewPackage(fset, files, nil, nil) 157 for _, obj := range pkg.Scope.Objects { 158 switch obj.Kind { 159 case ast.Fun: 160 fdecl := obj.Decl.(*ast.FuncDecl) 161 if !fdecl.Name.IsExported() { 162 continue 163 } 164 name := fdecl.Name.String() 165 model.Funcs = append(model.Funcs, name) 166 case ast.Var: 167 // fmt.Printf("Var: %s %+v\n", obj.Name, obj.Decl) 168 // case ast.Con: 169 case ast.Typ: 170 tdecl := obj.Decl.(*ast.TypeSpec) 171 if !tdecl.Name.IsExported() { 172 continue 173 } 174 model.Types = append(model.Types, tdecl.Name.String()) 175 default: 176 } 177 } 178 } 179 180 func getTemplate() *template.Template { 181 tpl, err := template.New("lib").Parse(templateStr) 182 if err != nil { 183 panic(err) 184 } 185 return tpl 186 } 187 188 const templateStr = ` 189 package main 190 {{ $pkgName := .PackageName }} 191 import "{{ .Package }}" 192 193 var Exports = map[string]interface{}{ 194 195 // Functions 196 {{ range .Funcs }} 197 "{{ . }}": {{ $pkgName }}.{{ . }}, 198 {{- end }} 199 200 // Types 201 {{ range .Types }} 202 "{{ . }}": func(x {{ $pkgName }}.{{ . }}) {{ $pkgName }}.{{ . }} { return x }, 203 "new{{ . }}": func() *{{ $pkgName }}.{{ . }} { return new({{ $pkgName }}.{{ . }}) }, 204 {{- end }} 205 } 206 `