github.com/lunny/gop@v0.0.0-20190322013459-2be48bbe64f7/cmd/run.go (about)

     1  // Copyright 2017 The Gop Authors. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cmd
     6  
     7  import (
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/urfave/cli"
    18  	fsnotify "gopkg.in/fsnotify.v1"
    19  )
    20  
    21  // CmdRun represents
    22  var CmdRun = cli.Command{
    23  	Name:            "run",
    24  	Usage:           "Run the target and monitor the source file changes",
    25  	Description:     `Run the target and monitor the source file changes`,
    26  	Action:          runRun,
    27  	SkipFlagParsing: true,
    28  }
    29  
    30  var process *os.Process
    31  var processLock sync.Mutex
    32  
    33  func runBinary(exePath string, wait bool) error {
    34  	attr := &os.ProcAttr{
    35  		Dir:   filepath.Dir(exePath),
    36  		Env:   os.Environ(),
    37  		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
    38  	}
    39  
    40  	var err error
    41  	process, err = os.StartProcess(filepath.Base(exePath), []string{exePath}, attr)
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	if wait {
    47  		_, err = process.Wait()
    48  	}
    49  	return err
    50  }
    51  
    52  func killOldProcess(done chan bool) {
    53  	fmt.Println("=== Killing the old process")
    54  	if process != nil {
    55  		if err := process.Kill(); err != nil {
    56  			log.Println("Killing old process error:", err)
    57  			done <- false
    58  			processLock.Unlock()
    59  			return
    60  		}
    61  		process = nil
    62  	}
    63  }
    64  
    65  func reBuildAndRun(ctx *cli.Context, args cli.Args, isWindows, ensureFlag bool, exePath string, done chan bool) {
    66  	processLock.Lock()
    67  	if isWindows {
    68  		killOldProcess(done)
    69  	}
    70  
    71  	fmt.Printf("=== Rebuilding %s ...\n", args)
    72  	err := runBuildNoCtx(ctx, args, isWindows, ensureFlag)
    73  	if err != nil {
    74  		log.Println("Build error:", err)
    75  	} else {
    76  		if !isWindows {
    77  			killOldProcess(done)
    78  		}
    79  
    80  		fmt.Printf("=== Running %s ...\n", exePath)
    81  		err = runBinary(exePath, false)
    82  		if err != nil {
    83  			log.Println("Run binary error:", err)
    84  		}
    85  	}
    86  	processLock.Unlock()
    87  }
    88  
    89  const (
    90  	noNeedReBuildAndRun = iota
    91  	needReBuildAndRun
    92  	needReRun
    93  )
    94  
    95  func needReBuild(projectRoot, fileName string) int {
    96  	if strings.HasSuffix(fileName, ".go") {
    97  		return needReBuildAndRun
    98  	} else if strings.HasSuffix(fileName, ".log") {
    99  		return noNeedReBuildAndRun
   100  	}
   101  
   102  	for _, f := range curTarget.Monitors {
   103  		if filepath.Join(projectRoot, "src", curTarget.Dir, f) == fileName {
   104  			return needReRun
   105  		}
   106  	}
   107  	return noNeedReBuildAndRun
   108  }
   109  
   110  func runRun(ctx *cli.Context) error {
   111  	var (
   112  		watchFlagIdx  = -1
   113  		ensureFlagIdx = -1
   114  		args          = ctx.Args()
   115  	)
   116  	for i, arg := range args {
   117  		if arg == "-v" {
   118  			showLog = true
   119  		} else if arg == "-w" {
   120  			watchFlagIdx = i
   121  		} else if arg == "-e" {
   122  			ensureFlagIdx = i
   123  		}
   124  	}
   125  
   126  	if watchFlagIdx > -1 {
   127  		args = append(args[:watchFlagIdx], args[watchFlagIdx+1:]...)
   128  		if ensureFlagIdx > watchFlagIdx {
   129  			ensureFlagIdx = ensureFlagIdx - 1
   130  		}
   131  	}
   132  	if ensureFlagIdx > -1 {
   133  		args = append(args[:ensureFlagIdx], args[ensureFlagIdx+1:]...)
   134  	}
   135  
   136  	var isWindows = runtime.GOOS == "windows"
   137  	// gop run don't support cross compile
   138  	err := runBuildNoCtx(ctx, args, isWindows, ensureFlagIdx > -1)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	_, projectRoot, err := analysisDirLevel()
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	var ext string
   149  	if isWindows {
   150  		ext = ".exe"
   151  	}
   152  
   153  	exePath := filepath.Join(projectRoot, "src", curTarget.Dir, curTarget.Name+ext)
   154  	exePath, _ = filepath.Abs(exePath)
   155  
   156  	if watchFlagIdx <= -1 {
   157  		return runBinary(exePath, true)
   158  	}
   159  
   160  	go func() {
   161  		processLock.Lock()
   162  		err := runBinary(exePath, false)
   163  		if err != nil {
   164  			Println("Run failed:", err)
   165  			process = nil
   166  		}
   167  		processLock.Unlock()
   168  	}()
   169  
   170  	watcher, err := fsnotify.NewWatcher()
   171  	if err != nil {
   172  		return err
   173  	}
   174  	defer watcher.Close()
   175  
   176  	err = filepath.Walk(filepath.Join(projectRoot, "src"), func(path string, info os.FileInfo, err error) error {
   177  		if err != nil {
   178  			return err
   179  		}
   180  		if info.IsDir() {
   181  			watcher.Add(path)
   182  		}
   183  		return nil
   184  	})
   185  	if err != nil {
   186  		return err
   187  	}
   188  
   189  	done := make(chan bool)
   190  	var lastTimeLock sync.Mutex
   191  	var lastTime time.Time
   192  	var needChangeType int
   193  
   194  	go func() {
   195  		for {
   196  			select {
   197  			case event := <-watcher.Events:
   198  				if event.Op&fsnotify.Write == fsnotify.Write {
   199  					needChange := needReBuild(projectRoot, event.Name)
   200  					if needChange == needReBuildAndRun || needChange == needReRun {
   201  						exist, _ := isFileExist(event.Name)
   202  						if exist {
   203  							lastTimeLock.Lock()
   204  							lastTime = time.Now()
   205  							needChangeType = needChange
   206  							lastTimeLock.Unlock()
   207  						}
   208  					}
   209  				} else if event.Op&fsnotify.Rename == fsnotify.Rename {
   210  					needChange := needReBuild(projectRoot, event.Name)
   211  					if needChange == needReBuildAndRun || needChange == needReRun {
   212  						lastTimeLock.Lock()
   213  						lastTime = time.Now()
   214  						needChangeType = needChange
   215  						lastTimeLock.Unlock()
   216  					}
   217  				} else if event.Op&fsnotify.Create == fsnotify.Create {
   218  					exist, _ := isDirExist(event.Name)
   219  					if exist {
   220  						watcher.Add(event.Name)
   221  					}
   222  				} else if event.Op&fsnotify.Remove == fsnotify.Remove {
   223  					watcher.Remove(event.Name)
   224  					needChange := needReBuild(projectRoot, event.Name)
   225  					if needChange == needReBuildAndRun || needChange == needReRun {
   226  						lastTimeLock.Lock()
   227  						lastTime = time.Now()
   228  						needChangeType = needChange
   229  						lastTimeLock.Unlock()
   230  					}
   231  				}
   232  			case err := <-watcher.Errors:
   233  				log.Println("error:", err)
   234  				done <- false
   235  				return
   236  			case <-time.After(200 * time.Millisecond):
   237  				var reBuild bool
   238  				var reType int
   239  				now := time.Now()
   240  				lastTimeLock.Lock()
   241  				reBuild = !lastTime.IsZero() && now.Unix()-lastTime.Unix() >= 1
   242  				if reBuild {
   243  					lastTime = time.Time{}
   244  					reType = needChangeType
   245  				}
   246  				lastTimeLock.Unlock()
   247  				if reBuild {
   248  					switch reType {
   249  					case needReBuildAndRun:
   250  						reBuildAndRun(ctx, args, isWindows, ensureFlagIdx > -1, exePath, done)
   251  					case needReRun:
   252  						processLock.Lock()
   253  						killOldProcess(done)
   254  
   255  						fmt.Printf("=== Running %s ...\n", exePath)
   256  						err = runBinary(exePath, false)
   257  						if err != nil {
   258  							log.Println("Run binary error:", err)
   259  						}
   260  						processLock.Unlock()
   261  					}
   262  				}
   263  			}
   264  		}
   265  	}()
   266  
   267  	<-done
   268  
   269  	if process != nil {
   270  		if err := process.Kill(); err != nil {
   271  			log.Println("error:", err)
   272  		}
   273  	}
   274  
   275  	return nil
   276  }