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 }