github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/cmd/runner/runner.go (about) 1 package runner 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "go/build" 8 "go/scanner" 9 "go/token" 10 "go/types" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path" 16 "strings" 17 "unsafe" 18 19 "github.com/yunabe/lgo/cmd/install" 20 "github.com/yunabe/lgo/converter" 21 "github.com/yunabe/lgo/core" 22 ) 23 24 /* 25 #cgo linux LDFLAGS: -ldl 26 #include <dlfcn.h> 27 */ 28 import "C" 29 30 func loadShared(ctx core.LgoContext, buildPkgDir, pkgPath string) error { 31 // This code is implemented based on https://golang.org/src/plugin/plugin_dlopen.go 32 sofile := "lib" + strings.Replace(pkgPath, "/", "-", -1) + ".so" 33 handle := C.dlopen(C.CString(path.Join(buildPkgDir, sofile)), C.RTLD_NOW|C.RTLD_GLOBAL) 34 if handle == nil { 35 panic("Failed to open shared object.") 36 } 37 38 // Initialize freshly loaded modules 39 // c.f. plugin_lastmoduleinit in https://golang.org/src/runtime/plugin.go 40 modulesinit() 41 typelinksinit() 42 itabsinit() 43 44 // Don't forget to call init. 45 // TODO: Write unit tests to confirm this. 46 initFuncPC := C.dlsym(handle, C.CString(pkgPath+".init")) 47 if initFuncPC != nil { 48 // Note: init does not exist if the library does not use external libraries. 49 initFuncP := &initFuncPC 50 initFunc := *(*func())(unsafe.Pointer(&initFuncP)) 51 initFunc() 52 } 53 54 lgoInitFuncPC := C.dlsym(handle, C.CString(pkgPath+".lgo_init")) 55 if lgoInitFuncPC == nil { 56 // lgo_init does not exist if lgo source includes only declarations. 57 return nil 58 } 59 lgoInitFuncP := &lgoInitFuncPC 60 lgoInitFunc := *(*func())(unsafe.Pointer(&lgoInitFuncP)) 61 return core.ExecLgoEntryPoint(ctx, func() { 62 lgoInitFunc() 63 }) 64 } 65 66 func loadSharedInternal(buildPkgDir, pkgPath string) { 67 } 68 69 type LgoRunner struct { 70 lgopath string 71 sessID *SessionID 72 execCount int64 73 vars map[string]types.Object 74 imports map[string]*types.PkgName 75 } 76 77 func NewLgoRunner(lgopath string, sessID *SessionID) *LgoRunner { 78 return &LgoRunner{ 79 lgopath: lgopath, 80 sessID: sessID, 81 vars: make(map[string]types.Object), 82 imports: make(map[string]*types.PkgName), 83 } 84 } 85 86 func (rn *LgoRunner) ExecCount() int64 { 87 return rn.execCount 88 } 89 90 func (rn *LgoRunner) cleanFiles(pkgPath string) { 91 // Delete src files 92 os.RemoveAll(path.Join(build.Default.GOPATH, "src", pkgPath)) 93 libname := "lib" + strings.Replace(pkgPath, "/", "-", -1) + ".so" 94 os.RemoveAll(path.Join(rn.lgopath, "pkg", libname)) 95 os.RemoveAll(path.Join(rn.lgopath, "pkg", pkgPath)) 96 } 97 98 func (rn *LgoRunner) isCtxDone(ctx context.Context) bool { 99 select { 100 case <-ctx.Done(): 101 return true 102 default: 103 return false 104 } 105 } 106 107 const maxErrLines = 5 108 109 // PrintError prints err to w. 110 // If err is scanner.ErrorList or convert.ErrorList, it expands internal errors. 111 func PrintError(w io.Writer, err error) { 112 var length int 113 var get func(int) error 114 if lst, ok := err.(scanner.ErrorList); ok { 115 length = len(lst) 116 get = func(i int) error { return lst[i] } 117 } else if lst, ok := err.(converter.ErrorList); ok { 118 length = len(lst) 119 get = func(i int) error { return lst[i] } 120 } else { 121 fmt.Fprintln(w, err.Error()) 122 return 123 } 124 for i := 0; i < maxErrLines && i < length; i++ { 125 msg := get(i).Error() 126 if i == maxErrLines-1 && i != length-1 { 127 msg += fmt.Sprintf(" (and %d more errors)", length-1-i) 128 } 129 fmt.Fprintln(w, msg) 130 } 131 } 132 133 // installDeps installs .so files for dependencies if .so files are not installed in $LGOPATH. 134 func (rn *LgoRunner) installDeps(deps []string) error { 135 var need []string 136 for _, path := range deps { 137 if path == "C" || install.IsStdPkg(path) { 138 continue 139 } 140 if !install.IsSOInstalled(rn.lgopath, path) { 141 need = append(need, path) 142 } 143 } 144 if len(need) == 0 { 145 return nil 146 } 147 fmt.Fprintf(os.Stderr, "found packages not installed in LGOPATH: %v\n", need) 148 return install.NewSOInstaller(rn.lgopath).Install(need...) 149 } 150 151 const lgoExportPrefix = "LgoExport_" 152 153 func (rn *LgoRunner) Run(ctx core.LgoContext, src string) error { 154 rn.execCount++ 155 sessDir := "github.com/yunabe/lgo/" + rn.sessID.Marshal() 156 pkgPath := path.Join(sessDir, fmt.Sprintf("exec%d", rn.execCount)) 157 var olds []types.Object 158 for _, obj := range rn.vars { 159 olds = append(olds, obj) 160 } 161 var oldImports []*types.PkgName 162 for _, im := range rn.imports { 163 oldImports = append(oldImports, im) 164 } 165 result := converter.Convert(src, &converter.Config{ 166 Olds: olds, 167 OldImports: oldImports, 168 DefPrefix: lgoExportPrefix, 169 RefPrefix: lgoExportPrefix, 170 LgoPkgPath: pkgPath, 171 AutoExitCode: true, 172 RegisterVars: true, 173 }) 174 // converted, pkg, _, err 175 if result.Err != nil { 176 return result.Err 177 } 178 for _, name := range result.Pkg.Scope().Names() { 179 rn.vars[name] = result.Pkg.Scope().Lookup(name) 180 } 181 for _, im := range result.Imports { 182 rn.imports[im.Name()] = im 183 } 184 if len(result.Src) == 0 { 185 // No declarations or expressions in the original source (e.g. only import statements). 186 return nil 187 } 188 pkgDir := path.Join(build.Default.GOPATH, "src", pkgPath) 189 if err := os.MkdirAll(pkgDir, 0766); err != nil { 190 return err 191 } 192 filePath := path.Join(pkgDir, "src.go") 193 err := ioutil.WriteFile(filePath, []byte(result.Src), 0666) 194 if err != nil { 195 return err 196 } 197 if err := rn.installDeps(result.FinalDeps); err != nil { 198 return err 199 } 200 201 buildPkgDir := path.Join(rn.lgopath, "pkg") 202 cmd := exec.CommandContext(ctx, "go", "install", "-buildmode=shared", "-linkshared", "-pkgdir", buildPkgDir, pkgPath) 203 cmd.Stderr = os.Stderr 204 cmd.Stdout = os.Stdout 205 err = cmd.Run() 206 if err != nil { 207 return fmt.Errorf("Failed to build a shared library of %s: %v", pkgPath, err) 208 } 209 return loadShared(ctx, buildPkgDir, pkgPath) 210 } 211 212 func (rn *LgoRunner) Complete(ctx context.Context, src string, index int) (matches []string, start, end int) { 213 var olds []types.Object 214 // TODO: Protect rn.vars and rn.imports with locks to make them goroutine safe. 215 for _, obj := range rn.vars { 216 olds = append(olds, obj) 217 } 218 var oldImports []*types.PkgName 219 for _, im := range rn.imports { 220 oldImports = append(oldImports, im) 221 } 222 matches, start, end = converter.Complete(src, token.Pos(index+1), &converter.Config{ 223 Olds: olds, 224 OldImports: oldImports, 225 DefPrefix: lgoExportPrefix, 226 RefPrefix: lgoExportPrefix, 227 }) 228 return 229 } 230 231 // Inspect analyzes src and returns the document of an identifier at index (0-based). 232 func (rn *LgoRunner) Inspect(ctx context.Context, src string, index int) (string, error) { 233 var olds []types.Object 234 // TODO: Protect rn.vars and rn.imports with locks to make them goroutine safe. 235 for _, obj := range rn.vars { 236 olds = append(olds, obj) 237 } 238 var oldImports []*types.PkgName 239 for _, im := range rn.imports { 240 oldImports = append(oldImports, im) 241 } 242 doc, query := converter.InspectIdent(src, token.Pos(index+1), &converter.Config{ 243 Olds: olds, 244 OldImports: oldImports, 245 DefPrefix: lgoExportPrefix, 246 RefPrefix: lgoExportPrefix, 247 }) 248 if doc != "" { 249 return doc, nil 250 } 251 if query == "" { 252 return "", nil 253 } 254 cmd := exec.CommandContext(ctx, "go", "doc", query) 255 var buf bytes.Buffer 256 cmd.Stdout = &buf 257 if err := cmd.Run(); err != nil { 258 return "", err 259 } 260 return strings.Replace(buf.String(), lgoExportPrefix, "", -1), nil 261 }