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 }