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  }