├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md └── rerun.go /.gitignore: -------------------------------------------------------------------------------- 1 | rerun 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | John Asmuth 2 | Geert-Johan Riemer 3 | zx9597446 4 | Roberto Bampi 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 the AUTHORS. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * The names in AUTHORs may not be used to endorse or promote 10 | products derived from this software without specific prior written 11 | permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 14 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 15 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 16 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 17 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Use like ```rerun github.com/skelterjohn/go.uik/uiktest``` 2 | 3 | Usage: ```rerun [--test] [--build] [--race] [--no-run] [arg]*``` 4 | 5 | For any go executable in a normal GOPATH workspace, rerun will watch its source, 6 | rebuild, retest, and rerun. As long as ```go install ``` works, 7 | rerun will be able to find it. 8 | 9 | Along with the target's source, rerun also watches the source of all 10 | the target's non-GOROOT dependencies. 11 | 12 | When using flag `--test`, rerun executes `go test`. If tests fail, rerun will not continue to build and/or run the program. 13 | 14 | Flag `--build` makes rerun execute `go build` in the local folder, creating an executable. 15 | 16 | Flag `--no-run` omits actually running the program. This is useful if you only wish to test and/or build. 17 | 18 | Flag `--race` will test/build/run the program with race detection enabled. 19 | -------------------------------------------------------------------------------- /rerun.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The rerun AUTHORS. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "flag" 11 | "fmt" 12 | "github.com/howeyc/fsnotify" 13 | "go/build" 14 | "log" 15 | "os" 16 | "os/exec" 17 | "path" 18 | "path/filepath" 19 | ) 20 | 21 | var ( 22 | do_tests = flag.Bool("test", false, "Run tests (before running program)") 23 | do_build = flag.Bool("build", false, "Build program") 24 | never_run = flag.Bool("no-run", false, "Do not run") 25 | race_detector = flag.Bool("race", false, "Run program and tests with the race detector") 26 | ) 27 | 28 | func install(buildpath, lastError string) (installed bool, errorOutput string, err error) { 29 | cmdline := []string{"go", "get"} 30 | 31 | if *race_detector { 32 | cmdline = append(cmdline, "-race") 33 | } 34 | cmdline = append(cmdline, buildpath) 35 | 36 | // setup the build command, use a shared buffer for both stdOut and stdErr 37 | cmd := exec.Command("go", cmdline[1:]...) 38 | buf := bytes.NewBuffer([]byte{}) 39 | cmd.Stdout = buf 40 | cmd.Stderr = buf 41 | 42 | err = cmd.Run() 43 | 44 | // when there is any output, the go command failed. 45 | if buf.Len() > 0 { 46 | errorOutput = buf.String() 47 | if errorOutput != lastError { 48 | fmt.Print(errorOutput) 49 | } 50 | err = errors.New("compile error") 51 | return 52 | } 53 | 54 | // all seems fine 55 | installed = true 56 | return 57 | } 58 | 59 | func test(buildpath string) (passed bool, err error) { 60 | cmdline := []string{"go", "test"} 61 | 62 | if *race_detector { 63 | cmdline = append(cmdline, "-race") 64 | } 65 | cmdline = append(cmdline, "-v", buildpath) 66 | 67 | // setup the build command, use a shared buffer for both stdOut and stdErr 68 | cmd := exec.Command("go", cmdline[1:]...) 69 | buf := bytes.NewBuffer([]byte{}) 70 | cmd.Stdout = buf 71 | cmd.Stderr = buf 72 | 73 | err = cmd.Run() 74 | passed = err == nil 75 | 76 | if !passed { 77 | fmt.Println(buf) 78 | } else { 79 | log.Println("tests passed") 80 | } 81 | 82 | return 83 | } 84 | 85 | func gobuild(buildpath string) (passed bool, err error) { 86 | cmdline := []string{"go", "build"} 87 | 88 | if *race_detector { 89 | cmdline = append(cmdline, "-race") 90 | } 91 | cmdline = append(cmdline, "-v", buildpath) 92 | 93 | // setup the build command, use a shared buffer for both stdOut and stdErr 94 | cmd := exec.Command("go", cmdline[1:]...) 95 | buf := bytes.NewBuffer([]byte{}) 96 | cmd.Stdout = buf 97 | cmd.Stderr = buf 98 | 99 | err = cmd.Run() 100 | passed = err == nil 101 | 102 | if !passed { 103 | fmt.Println(buf) 104 | } else { 105 | log.Println("build passed") 106 | } 107 | 108 | return 109 | } 110 | 111 | func run(binName, binPath string, args []string) (runch chan bool) { 112 | runch = make(chan bool) 113 | go func() { 114 | cmdline := append([]string{binName}, args...) 115 | var proc *os.Process 116 | for relaunch := range runch { 117 | if proc != nil { 118 | err := proc.Signal(os.Interrupt) 119 | if err != nil { 120 | log.Printf("error on sending signal to process: '%s', will now hard-kill the process\n", err) 121 | proc.Kill() 122 | } 123 | proc.Wait() 124 | } 125 | if !relaunch { 126 | continue 127 | } 128 | cmd := exec.Command(binPath, args...) 129 | cmd.Stdout = os.Stdout 130 | cmd.Stderr = os.Stderr 131 | log.Print(cmdline) 132 | err := cmd.Start() 133 | if err != nil { 134 | log.Printf("error on starting process: '%s'\n", err) 135 | } 136 | proc = cmd.Process 137 | } 138 | }() 139 | return 140 | } 141 | 142 | func getWatcher(buildpath string) (watcher *fsnotify.Watcher, err error) { 143 | watcher, err = fsnotify.NewWatcher() 144 | addToWatcher(watcher, buildpath, map[string]bool{}) 145 | return 146 | } 147 | 148 | func addToWatcher(watcher *fsnotify.Watcher, importpath string, watching map[string]bool) { 149 | pkg, err := build.Import(importpath, "", 0) 150 | if err != nil { 151 | return 152 | } 153 | if pkg.Goroot { 154 | return 155 | } 156 | watcher.Watch(pkg.Dir) 157 | watching[importpath] = true 158 | for _, imp := range pkg.Imports { 159 | if !watching[imp] { 160 | addToWatcher(watcher, imp, watching) 161 | } 162 | } 163 | } 164 | 165 | func rerun(buildpath string, args []string) (err error) { 166 | log.Printf("setting up %s %v", buildpath, args) 167 | 168 | pkg, err := build.Import(buildpath, "", 0) 169 | if err != nil { 170 | return 171 | } 172 | 173 | if pkg.Name != "main" { 174 | err = errors.New(fmt.Sprintf("expected package %q, got %q", "main", pkg.Name)) 175 | return 176 | } 177 | 178 | _, binName := path.Split(buildpath) 179 | var binPath string 180 | if gobin := os.Getenv("GOBIN"); gobin != "" { 181 | binPath = filepath.Join(gobin, binName) 182 | } else { 183 | binPath = filepath.Join(pkg.BinDir, binName) 184 | } 185 | 186 | var runch chan bool 187 | if !(*never_run) { 188 | runch = run(binName, binPath, args) 189 | } 190 | 191 | no_run := false 192 | if *do_tests { 193 | passed, _ := test(buildpath) 194 | if !passed { 195 | no_run = true 196 | } 197 | } 198 | 199 | if *do_build && !no_run { 200 | gobuild(buildpath) 201 | } 202 | 203 | var errorOutput string 204 | _, errorOutput, ierr := install(buildpath, errorOutput) 205 | if !no_run && !(*never_run) && ierr == nil { 206 | runch <- true 207 | } 208 | 209 | var watcher *fsnotify.Watcher 210 | watcher, err = getWatcher(buildpath) 211 | if err != nil { 212 | return 213 | } 214 | 215 | for { 216 | // read event from the watcher 217 | we, _ := <-watcher.Event 218 | // other files in the directory don't count - we watch the whole thing in case new .go files appear. 219 | if filepath.Ext(we.Name) != ".go" { 220 | continue 221 | } 222 | 223 | log.Print(we.Name) 224 | 225 | // close the watcher 226 | watcher.Close() 227 | // to clean things up: read events from the watcher until events chan is closed. 228 | go func(events chan *fsnotify.FileEvent) { 229 | for _ = range events { 230 | 231 | } 232 | }(watcher.Event) 233 | // create a new watcher 234 | log.Println("rescanning") 235 | watcher, err = getWatcher(buildpath) 236 | if err != nil { 237 | return 238 | } 239 | 240 | // we don't need the errors from the new watcher. 241 | // we continiously discard them from the channel to avoid a deadlock. 242 | go func(errors chan error) { 243 | for _ = range errors { 244 | 245 | } 246 | }(watcher.Error) 247 | 248 | var installed bool 249 | // rebuild 250 | installed, errorOutput, _ = install(buildpath, errorOutput) 251 | if !installed { 252 | continue 253 | } 254 | 255 | if *do_tests { 256 | passed, _ := test(buildpath) 257 | if !passed { 258 | continue 259 | } 260 | } 261 | 262 | if *do_build { 263 | gobuild(buildpath) 264 | } 265 | 266 | // rerun. if we're only testing, sending 267 | if !(*never_run) { 268 | runch <- true 269 | } 270 | } 271 | return 272 | } 273 | 274 | func main() { 275 | flag.Parse() 276 | 277 | if len(flag.Args()) < 1 { 278 | log.Fatal("Usage: rerun [--test] [--no-run] [--build] [--race] [arg]*") 279 | } 280 | 281 | buildpath := flag.Args()[0] 282 | args := flag.Args()[1:] 283 | err := rerun(buildpath, args) 284 | if err != nil { 285 | log.Print(err) 286 | } 287 | } 288 | --------------------------------------------------------------------------------