github.com/yunabe/lgo@v0.0.0-20190709125917-42c42d410fdf/cmd/lgo-internal/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"go/importer"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"os/signal"
    13  	"path"
    14  	"path/filepath"
    15  	"runtime/debug"
    16  	"strings"
    17  	"syscall"
    18  
    19  	"github.com/golang/glog"
    20  	"github.com/yunabe/lgo/cmd/lgo-internal/liner"
    21  	"github.com/yunabe/lgo/cmd/runner"
    22  	"github.com/yunabe/lgo/converter"
    23  	"github.com/yunabe/lgo/core"
    24  	"golang.org/x/sys/unix"
    25  )
    26  
    27  var (
    28  	subcomandFlag  = flag.String("subcommand", "", "lgo subcommand")
    29  	sessIDFlag     = flag.String("sess_id", "", "lgo session id")
    30  	connectionFile = flag.String("connection_file", "", "jupyter kernel connection file path. This flag is used with kernel subcommand")
    31  )
    32  
    33  type printer struct{}
    34  
    35  func (*printer) Println(args ...interface{}) {
    36  	for _, arg := range args {
    37  		fmt.Println(arg)
    38  	}
    39  }
    40  
    41  func createRunContext(parent context.Context, sigint <-chan os.Signal) (ctx context.Context, cancel func()) {
    42  	ctx, cancel = context.WithCancel(parent)
    43  	go func() {
    44  		select {
    45  		case <-sigint:
    46  			cancel()
    47  		case <-ctx.Done():
    48  		}
    49  	}()
    50  	return
    51  }
    52  
    53  func exitProcess() {
    54  	// Programs should call Flush before exiting to guarantee all log output is written.
    55  	// https://godoc.org/github.com/golang/glog
    56  	glog.Flush()
    57  	os.Exit(0)
    58  }
    59  
    60  func createProcessContext(withSigint bool) context.Context {
    61  	// Use SIGUSR1 to notify the death of the parent process.
    62  	unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(syscall.SIGUSR1), 0, 0, 0)
    63  
    64  	sigch := make(chan os.Signal)
    65  	ctx, cancel := context.WithCancel(context.Background())
    66  	signals := []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP, syscall.SIGUSR1}
    67  	if withSigint {
    68  		signals = append(signals, syscall.SIGINT)
    69  	}
    70  	signal.Notify(sigch, signals...)
    71  	go func() {
    72  		sig := <-sigch
    73  		if sig == syscall.SIGUSR1 {
    74  			glog.Info("The parent process died. Cancelling the internal process")
    75  		} else {
    76  			glog.Infof("Received a signal (%s). Cancelling the internal process", sig)
    77  		}
    78  		cancel()
    79  	}()
    80  	return ctx
    81  }
    82  
    83  func fromFiles(ctx context.Context, rn *runner.LgoRunner) {
    84  	for _, path := range flag.Args() {
    85  		src, err := ioutil.ReadFile(path)
    86  		if err != nil {
    87  			glog.Errorf("Failed to read %s: %v", path, err)
    88  			return
    89  		}
    90  		if err = rn.Run(core.LgoContext{Context: ctx}, string(src)); err != nil {
    91  			glog.Error(err)
    92  			return
    93  		}
    94  	}
    95  }
    96  
    97  func fromStdin(ctx context.Context, rn *runner.LgoRunner) {
    98  	ln := liner.NewLiner()
    99  	ln.SetCompleter(func(lines []string) []string {
   100  		if len(lines) == 0 {
   101  			return nil
   102  		}
   103  		src := strings.Join(lines, "\n")
   104  		last := lines[len(lines)-1]
   105  		matches, start, end := rn.Complete(ctx, src, len(src))
   106  		if len(matches) == 0 {
   107  			return nil
   108  		}
   109  		start = start - (len(src) - len(last))
   110  		end = end - (len(src) - len(last))
   111  		if start < 0 || start > len(src) || end < 0 || end > len(src) {
   112  			return nil
   113  		}
   114  		for i, m := range matches {
   115  			matches[i] = last[:start] + m + last[end:]
   116  		}
   117  		return matches
   118  	})
   119  	sigint := make(chan os.Signal)
   120  	signal.Notify(sigint, syscall.SIGINT)
   121  loop:
   122  	for {
   123  		select {
   124  		case <-ctx.Done():
   125  			break loop
   126  		default:
   127  		}
   128  		src, err := ln.Next()
   129  		if err != nil {
   130  			if err != io.EOF {
   131  				glog.Errorf("liner returned non-EOF error unexpectedly: %v", err)
   132  			}
   133  			return
   134  		}
   135  		runCtx, cancel := context.WithCancel(ctx)
   136  		go func() {
   137  			select {
   138  			case <-sigint:
   139  				cancel()
   140  			case <-runCtx.Done():
   141  			}
   142  		}()
   143  		func() {
   144  			defer func() {
   145  				cancel()
   146  				p := recover()
   147  				if p != nil {
   148  					// The return value of debug.Stack() ends with \n.
   149  					fmt.Fprintf(os.Stderr, "panic: %v\n\n%s", p, debug.Stack())
   150  				}
   151  			}()
   152  			if err := rn.Run(core.LgoContext{Context: runCtx}, src); err != nil {
   153  				glog.Error(err)
   154  			}
   155  		}()
   156  	}
   157  }
   158  
   159  func installPkgArchive(pkgDir string, paths []string) error {
   160  	return exec.Command("go", append([]string{"install", "-linkshared", "-pkgdir", pkgDir}, paths...)...).Run()
   161  }
   162  
   163  type packageArchiveInstaller struct{ pkgDir string }
   164  
   165  func (in *packageArchiveInstaller) Install(pkgs []string) error {
   166  	return installPkgArchive(in.pkgDir, pkgs)
   167  }
   168  
   169  func main() {
   170  	flag.Parse()
   171  	if *sessIDFlag == "" {
   172  		glog.Fatal("--sess_id is not set")
   173  	}
   174  	var sessID runner.SessionID
   175  	if err := sessID.Unmarshal(*sessIDFlag); err != nil {
   176  		glog.Fatalf("--sess_id=%s is invalid: %v", *sessIDFlag, err)
   177  	}
   178  	if *subcomandFlag == "" {
   179  		glog.Fatal("--subcomand is not set")
   180  	}
   181  
   182  	lgopath := os.Getenv("LGOPATH")
   183  	if lgopath == "" {
   184  		glog.Fatal("LGOPATH is empty")
   185  	}
   186  	lgopath, err := filepath.Abs(lgopath)
   187  	if err != nil {
   188  		glog.Fatalf("Failed to get the absolute path of LGOPATH: %v", err)
   189  	}
   190  	core.RegisterLgoPrinter(&printer{})
   191  	pkgDir := path.Join(lgopath, "pkg")
   192  	// Fom go1.10, go install does not install .a files into GOPATH.
   193  	// We need to read package information from .a files installed in LGOPATH instead.
   194  	converter.SetLGOImporter(importer.For("gc", func(path string) (io.ReadCloser, error) {
   195  		abs := filepath.Join(lgopath, "pkg", path+".a")
   196  		if _, err := os.Stat(abs); os.IsNotExist(err) {
   197  			installPkgArchive(pkgDir, []string{path})
   198  		}
   199  		return os.Open(abs)
   200  	}))
   201  	converter.SetPackageArchiveInstaller(&packageArchiveInstaller{
   202  		pkgDir: pkgDir,
   203  	})
   204  
   205  	if *subcomandFlag == "kernel" {
   206  		kernelMain(lgopath, &sessID)
   207  		exitProcess()
   208  	}
   209  
   210  	rn := runner.NewLgoRunner(lgopath, &sessID)
   211  	useFiles := len(flag.Args()) > 0
   212  	ctx := createProcessContext(useFiles)
   213  
   214  	if len(flag.Args()) > 0 {
   215  		fromFiles(ctx, rn)
   216  	} else {
   217  		fromStdin(ctx, rn)
   218  	}
   219  
   220  	// clean-up
   221  	glog.Infof("Clean the session: %s", sessID.Marshal())
   222  	runner.CleanSession(lgopath, &sessID)
   223  	exitProcess()
   224  }