github.com/golazy/golazy@v0.0.7-0.20221012133820-968fe65a0b65/lazydev/parent.go (about)

     1  package lazydev
     2  
     3  import (
     4  	"log"
     5  	"net"
     6  	"net/http"
     7  	"os"
     8  	"os/exec"
     9  	"os/signal"
    10  	"path/filepath"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/golazy/golazy/lazydev/filewatcher"
    16  	"github.com/golazy/golazy/lazydev/runner"
    17  )
    18  
    19  type parent struct {
    20  	listenAddr string
    21  }
    22  
    23  var defaultListenAddr = ":3000"
    24  
    25  func (s *parent) Serve(h http.Handler) error {
    26  	// listen Addr
    27  	addr := os.Getenv("PORT")
    28  	if addr == "" {
    29  		addr = defaultListenAddr
    30  	} else {
    31  		if !strings.Contains(addr, ":") {
    32  			addr = ":" + addr
    33  		}
    34  	}
    35  
    36  	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
    37  	if err != nil {
    38  		return err
    39  	}
    40  	l, err := net.ListenTCP("tcp", tcpAddr)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	// Working directory
    45  	cmd, err := childCmd(l)
    46  	if err != nil {
    47  		return err
    48  	}
    49  	r := runner.New(cmd, nil)
    50  
    51  	running := false
    52  	err = r.Start()
    53  	if err != nil {
    54  		return err
    55  	}
    56  	defer func() {
    57  		if running {
    58  			r.Stop()
    59  		}
    60  	}()
    61  
    62  	fw, err := filewatcher.New("")
    63  	if err != nil {
    64  		return err
    65  	}
    66  	changeSet, err := fw.Watch()
    67  	if err != nil {
    68  		return err
    69  	}
    70  	defer func() {
    71  		fw.Close()
    72  	}()
    73  
    74  	intSignal := make(chan os.Signal, 1)
    75  	signal.Notify(intSignal, os.Interrupt)
    76  
    77  	for {
    78  		select {
    79  		case event := <-r.Events:
    80  			if _, ok := event.(runner.EventStarted); ok {
    81  				running = true
    82  			}
    83  			if _, ok := event.(runner.EventStopped); ok {
    84  				running = false
    85  			}
    86  		case cs := <-changeSet:
    87  			log.Printf("%T %+v", cs, cs)
    88  			err := r.Restart()
    89  			if err != nil {
    90  				log.Println("Error while restarting:", err)
    91  			}
    92  
    93  		case <-intSignal:
    94  			if running == false {
    95  				return nil
    96  			}
    97  			r.Stop()
    98  			wait := time.After(time.Second * 2)
    99  			intCount := 1
   100  			for wait != nil {
   101  				select {
   102  				case <-wait:
   103  					wait = nil
   104  				case event := <-r.Events:
   105  					log.Printf("%T %+v", event, event)
   106  					if _, ok := event.(runner.EventStopped); ok {
   107  						running = false
   108  						return nil
   109  					}
   110  				case <-intSignal:
   111  					if intCount == 1 {
   112  						log.Println("Killing the process")
   113  						intCount += 1
   114  						r.Signal(syscall.SIGKILL)
   115  						continue
   116  					}
   117  					return nil
   118  				}
   119  			}
   120  			return nil
   121  		}
   122  	}
   123  }
   124  
   125  func childCmd(l *net.TCPListener) (*exec.Cmd, error) {
   126  	wd, err := os.Getwd()
   127  	if err != nil {
   128  		wd = "."
   129  	}
   130  
   131  	p := "*.go"
   132  	if wd != "" {
   133  		p = filepath.Join(wd, p)
   134  	}
   135  	files, err := filepath.Glob(p)
   136  	if err != nil {
   137  		log.Fatal(err)
   138  	}
   139  
   140  	args := []string{"run"}
   141  	for _, file := range files {
   142  		if strings.HasSuffix(file, "_test.go") {
   143  			continue
   144  		}
   145  		args = append(args, file)
   146  	}
   147  
   148  	file, err := l.File()
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	log.Println("Listener is", file)
   153  	cmd := exec.Command("go", args...)
   154  	cmd.ExtraFiles = []*os.File{file}
   155  	cmd.Env = os.Environ()
   156  	cmd.Env = append(cmd.Env, childEnvKey+"=true")
   157  
   158  	logger := log.New(os.Stdout, "child:  ", 0)
   159  	logW := logWriter{logger}
   160  	cmd.Stdout = logW
   161  	cmd.Stderr = logW
   162  
   163  	return cmd, nil
   164  }
   165  
   166  type logWriter struct {
   167  	*log.Logger
   168  }
   169  
   170  func (l logWriter) Write(args []byte) (int, error) {
   171  	l.Println(string(args))
   172  	return len(args), nil
   173  }